From 57b13bf4f62e2c8a9bd9171b83a8c90aaa899e3b Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 1 Aug 2017 13:53:59 +0200 Subject: [PATCH 01/41] limit displayed columns on form answers tab of a form (#686) --- inc/form_answer.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/form_answer.class.php b/inc/form_answer.class.php index 94b678645..798b851d6 100644 --- a/inc/form_answer.class.php +++ b/inc/form_answer.class.php @@ -248,7 +248,7 @@ static function showForForm(PluginFormcreatorForm $form, $params = []) { $searchOptions = $item->getSearchOptions(); $filteredOptions = []; foreach ($searchOptions as $key => $value) { - if (is_numeric($key)) { + if (is_numeric($key) && $key <= 7) { $filteredOptions[$key] = $value; } } From ebf13a88e183607a25f083e46a5185807a33387f Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 22 Aug 2017 08:33:32 +0200 Subject: [PATCH 02/41] fix bulleted lists for IE (#702) --- css/styles.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/styles.css b/css/styles.css index ec97ad7d1..001cad57c 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1364,14 +1364,14 @@ span.fc_list_icon { } .form_header ul { - list-style-type: inherit; + list-style-type: disc; margin: inherit; padding: inherit; padding-left: 40px; } .form_section ul { - list-style-type: inherit; + list-style-type: disc; margin: inherit; padding: inherit; padding-left: 40px; From 4356811bb007ac0be4876b45f233187ed451b63f Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 18 Jul 2017 18:13:57 +0200 Subject: [PATCH 03/41] update slinky lib --- js/scripts.js.php | 11 +- lib/slinky/README.md | 73 ++++++-- lib/slinky/assets/css/jquery.slinky.css | 11 +- lib/slinky/assets/css/master.css | 51 +++++- lib/slinky/assets/js/jquery.slinky.js | 210 +++++++++++++++--------- lib/slinky/index.html | 55 ++++++- 6 files changed, 304 insertions(+), 107 deletions(-) diff --git a/js/scripts.js.php b/js/scripts.js.php index 75ce790ec..a5538c8e9 100644 --- a/js/scripts.js.php +++ b/js/scripts.js.php @@ -10,6 +10,7 @@ var tiles = []; var helpdeskHome = 0; var serviceCatalogEnabled = false; +var slinkyCategories; // === MENU === var link = ''; @@ -19,7 +20,7 @@ link += ''; link += ''; -jQuery(document).ready(function($) { +$(function() { var target = $('body'); modalWindow = $("
").dialog({ width: 980, @@ -90,10 +91,10 @@ }); $('#plugin_formcreator_wizard_categories #wizard_seeall').click(function () { - updateCategoriesView(); + slinkyCategories.home(); updateWizardFormsView(0); - $('#plugin_formcreator_wizard_categories .category_active').removeClass('category_active'); - $(this).addClass('category_active'); + $('#plugin_formcreator_wizard_categories .category_active').removeClass('category_active'); + $(this).addClass('category_active'); }); } @@ -183,7 +184,7 @@ function updateCategoriesView() { $('#plugin_formcreator_wizard_categories').append(html); // Setup slinky - $('#plugin_formcreator_wizard_categories div:nth(2)').slinky({ + slinkyCategories = $('#plugin_formcreator_wizard_categories div:nth(2)').slinky({ label: true }); $('#plugin_formcreator_wizard_categories a.back').click( diff --git a/lib/slinky/README.md b/lib/slinky/README.md index 6c79e39b0..c060b7f0e 100644 --- a/lib/slinky/README.md +++ b/lib/slinky/README.md @@ -2,26 +2,77 @@ jQuery sliding menu -A light-weight, responsive, mobile-like menu plugin +A light-weight, responsive, mobile-like navigation menu plugin ### [Demo](http://alizahid.github.io/slinky) -### Installation +## Installation Download the [latest version](https://github.com/alizahid/slinky/archive/gh-pages.zip). The files you need are; -- assets/js/jquery.slinky.js -- assets/css/jquery.slinky.css +- [dist/jquery.slinky.js](dist/jquery.slinky.js) +- [dist/jquery.slinky.css](dist/jquery.slinky.css) -### Usage +### Bower - $('.menu').slinky(options); + bower install jquery-slinky -### Options +Include these files; + + + + + +### NPM + + npm install jquery-slinky + +Include these files; + + + + + +## Usage + + var slinky = $('.menu').slinky(options); + +## Options Option | Default value | Description ------ | ------------- | ----------- -`label` | 'Back' | Label for the back button. Set to `true` to use the link's own label -`title` | `false` | Set to `true` to show title of current menu level -`speed` | `300` | Animation speed in milliseconds -`resize` | `true` | Resize menu height to match content +`label` | 'Back' | Label for the back button. Pass `true` to use the link's own label +`title` | `false` | Pass `true` to show title of current menu level +`speed` | `300` | Animation speed in `milliseconds` +`resize` | `true` | Resize menu height to match content on navigation +`activeClass` | `active` | Class that's applied to the current menu level `
    ` +`headerClass` | `header` | Class name for the container for the back button and heading +`headingTag` | `

    ` | Tag that contains the heading +`backFirst` | `false` | Optionally append the back button before the heading instead of after + +## API + +### .home() + +Navigate back to the main menu + +Option | Default value | Description +------ | ------------- | ----------- +`animate` | `true` | Pass `false` to skip animation + +### .jump() + +Navigate to a sub menu + +Option | Default value | Description +------ | ------------- | ----------- +`to` | | Pass a selector for the `ul` element to jump to +`animate` | `true` | Pass `false` to skip animation + +### .destroy() + +Remove slinky + +## Tips + +- Set `.active` on a `ul` element to jump there on init diff --git a/lib/slinky/assets/css/jquery.slinky.css b/lib/slinky/assets/css/jquery.slinky.css index 3280c4012..66bbd0f2f 100644 --- a/lib/slinky/assets/css/jquery.slinky.css +++ b/lib/slinky/assets/css/jquery.slinky.css @@ -4,8 +4,6 @@ overflow: hidden; transform: translateZ(0); transition: all 300ms ease; - -webkit-transform: translateZ(0); - -webkit-transition: all 300ms ease; } .slinky-menu > ul { @@ -13,8 +11,6 @@ position: relative; transform: translateZ(0); transition: all 300ms ease; - -webkit-transform: translateZ(0); - -webkit-transition: all 300ms ease; } .slinky-menu ul, @@ -56,7 +52,7 @@ /* Default theme */ -.slinky-menu .header { +.slinky-menu { background: #AAA; } @@ -65,8 +61,11 @@ padding: 1em; } +.slinky-menu li { + line-height: 1; +} + .slinky-menu a { - background: #AAA; border: none; color: #FFF; padding: 1em; diff --git a/lib/slinky/assets/css/master.css b/lib/slinky/assets/css/master.css index 0d617dc01..39ab23e00 100644 --- a/lib/slinky/assets/css/master.css +++ b/lib/slinky/assets/css/master.css @@ -1,4 +1,4 @@ -/* Style */ +/* Styles */ *, *:before, @@ -10,12 +10,14 @@ outline: none; padding: 0; } + body { background: #000; color: #000; cursor: default; font: normal 0.75em/1 'Atlas Typewriter', 'Consolas', monospace; } + a { color: #AAA; border-bottom: 1px solid #AAA; @@ -23,26 +25,53 @@ a { text-decoration: none; transition: all 300ms ease; } + a:hover { border-bottom-color: #000; color: #000; } + h1 { - margin: 0; + font-size: 3em; } + h2, h3 { margin: 1em 0; } + +h2 { + font-size: 2em; +} + +h3 { + font-size: 1.5em; +} + +h4 { + background: #000; + color: #FFF; + display: inline-block; + font-size: 1em; + margin-top: 2em; + padding: 1em; +} + +h4:first-of-type { + margin-top: 0; +} + img { border: none; max-width: 100%; vertical-align: top; } + p { line-height: 1.6; margin: 2em 0; } + pre { background: #000; color: #FFF; @@ -51,15 +80,18 @@ pre { padding: 2em; tab-size: 4; } + table { font: inherit; border-collapse: collapse; border-spacing: 0; width: 100%; } + tbody tr:nth-child(2n+1) { background: #EEE; } + th, td { line-height: 1.6; @@ -67,6 +99,13 @@ td { text-align: left; vertical-align: middle; } + +ul li { + line-height: 1.6; + list-style: square; + margin: 1em 0 1em 2em; +} + header, section, footer { @@ -74,6 +113,7 @@ footer { max-width: 90%; width: 40em; } + footer { color: #FFF; margin: 0; @@ -82,21 +122,26 @@ footer { text-align: center; width: 100%; } + footer img { height: 1em; } + footer a:hover { border-bottom-color: #FFF; color: #FFF; } + .wrapper { background: #FFF; overflow: hidden; } + .social { margin: 2em auto; overflow: hidden; } + .github, .tweet { float: left; @@ -108,9 +153,11 @@ footer a:hover { h3 { margin-top: 1em; } + footer { line-height: 2; } + footer img { margin: 0.5em 0; } diff --git a/lib/slinky/assets/js/jquery.slinky.js b/lib/slinky/assets/js/jquery.slinky.js index d0bc64409..0e2b3e538 100644 --- a/lib/slinky/assets/js/jquery.slinky.js +++ b/lib/slinky/assets/js/jquery.slinky.js @@ -1,130 +1,192 @@ /* - - Slinky - A light-weight, responsive, mobile-like navigation menu plugin for jQuery - Built by Ali Zahid - Published under the MIT license - -*/ + * Slinky + * A light-weight, responsive, mobile-like navigation menu plugin for jQuery + * Built by Ali Zahid + * Published under the MIT license + */ ;(function($) { - $.fn.slinky = function(options) { - // Setup plugin defaults + var lastClick; + $.fn.slinky = function(options) { var settings = $.extend({ label: 'Back', title: false, speed: 300, - resize: true + resize: true, + activeClass: 'active', + headerClass: 'header', + headingTag: '

    ', + backFirst: false, }, options); - // Convenience method for navigation animation - - var move = function(menu, next, callback) { - var left = Math.round(parseInt(menu.get(0).style.left)) || 0; + var menu = $(this), + root = menu.children().first(); - // Use multiples of 100% for responsive animation + menu.addClass('slinky-menu'); - menu.css('left', -Math.abs(next ? left - 100 : left + 100) + '%'); + var move = function(depth, callback) { + var left = Math.round(parseInt(root.get(0).style.left)) || 0; - // Callback after animation is finished + root.css('left', left - (depth * 100) + '%'); if (typeof callback === 'function') { setTimeout(callback, settings.speed); } }; - // Convenience method for resizing menu - - var resize = function(menu, content) { + var resize = function(content) { menu.height(content.outerHeight()); }; - return this.each(function() { - // The root node is where animation happens + var transition = function(speed) { + menu.css('transition-duration', speed + 'ms'); + root.css('transition-duration', speed + 'ms'); + }; + + transition(settings.speed); - var menu = $(this), - root = menu.children().first(); + $('a + ul', menu).prev().addClass('next'); - // Set CSS animation duration + $('li > ul', menu).prepend('
  • '); - menu.css('transition-duration', settings.speed + 'ms'); - root.css('transition-duration', settings.speed + 'ms'); + if (settings.title === true) { + $('li > ul', menu).each(function() { + var label = $(this).parent().find('a').first().text(), + title = $(settings.headingTag).text(label); - // Add .next class to links with sub menus + $('> .' + settings.headerClass, this).append(title); + }); + } - $('a + ul', menu).prev().addClass('next'); + if (!settings.title && settings.label === true) { + $('li > ul', menu).each(function() { + var label = $(this).parent().find('a').first().text(), + backLink = $('').text(label).prop('href', '#').addClass('back'); - // Add header for back button and title + if (settings.backFirst) { + $('> .' + settings.headerClass, this).prepend(backLink); + } else { + $('> .' + settings.headerClass, this).append(backLink); + } + }); + } else { + var backLink = $('').text(settings.label).prop('href', '#').addClass('back'); - $('li > ul', menu).prepend('
  • '); + if (settings.backFirst) { + $('.' + settings.headerClass, menu).prepend(backLink); + } else { + $('.' + settings.headerClass, menu).append(backLink); + } + } - // Add title + $('a', menu).on('click', function(e) { + if ((lastClick + settings.speed) > Date.now()) { + return false; + } - if (settings.title === true) { - // Create a label with title from the parent + lastClick = Date.now(); - $('li > ul', menu).each(function() { - var label = $(this).parent().find('a').first().text(), - title = $('

    ').text(label); + var a = $(this); - $('> .header', this).append(title); - }); + if (/\B#/.test(this.href) || a.hasClass('next') || a.hasClass('back')) { + e.preventDefault(); } - // Add back links with appropriate labels + if (a.hasClass('next')) { + menu.find('.' + settings.activeClass).removeClass(settings.activeClass); + + a.next().show().addClass(settings.activeClass); - if (!settings.title && settings.label === true) { - // Create a link with label from parent + move(1); - $('li > ul', menu).each(function() { - var label = $(this).parent().find('a').first().text(), - backLink = $('').text(label).prop('href', '#').addClass('back'); + if (settings.resize) { + resize(a.next()); + } + } else if (a.hasClass('back')) { + move(-1, function() { + menu.find('.' + settings.activeClass).removeClass(settings.activeClass); - $('> .header', this).append(backLink); + a.parent().parent().hide().parentsUntil(menu, 'ul').first().addClass(settings.activeClass); }); + + if (settings.resize) { + resize(a.parent().parent().parentsUntil(menu, 'ul')); + } + } + }); + + this.jump = function(to, animate) { + to = $(to); + + var active = menu.find('.' + settings.activeClass); + + if (active.length > 0) { + active = active.parentsUntil(menu, 'ul').length; } else { - // Create a link with the label from settings + active = 0; + } - var backLink = $('').text(settings.label).prop('href', '#').addClass('back'); + menu.find('ul').removeClass(settings.activeClass).hide(); - $('.header', menu).append(backLink); + var menus = to.parentsUntil(menu, 'ul'); + + menus.show(); + to.show().addClass(settings.activeClass); + + if (animate === false) { + transition(0); } - // Setup navigation + move(menus.length - active); - $('a', menu).on('click', function(e) { - var a = $(this); + if (settings.resize) { + resize(to); + } + + if (animate === false) { + transition(settings.speed); + } + }; + + this.home = function(animate) { + if (animate === false) { + transition(0); + } - // Disable navigation if link has hash - // else proceed to URL + var active = menu.find('.' + settings.activeClass), + count = active.parentsUntil(menu, 'li').length; - if (/#/.test(this.href)) { - e.preventDefault(); + if (count > 0) { + move(-count, function() { + active.removeClass(settings.activeClass); + }); + + if (settings.resize) { + resize($(active.parentsUntil(menu, 'li').get(count - 1)).parent()); } + } + + if (animate === false) { + transition(settings.speed); + } + }; - // Animate forward or backward - // Resize menu height to match content, if required + this.destroy = function() { + $('.' + settings.headerClass, menu).remove(); + $('a', menu).removeClass('next').off('click'); - if (a.hasClass('next')) { - a.next().show(); + menu.removeClass('slinky-menu').css('transition-duration', ''); + root.css('transition-duration', ''); + }; - move(root, true); + var active = menu.find('.' + settings.activeClass); - if (settings.resize) { - resize(menu, a.next()); - } - } else if (a.hasClass('back')) { - move(root, false, function() { - a.parent().parent().hide(); - }); + if (active.length > 0) { + active.removeClass(settings.activeClass); - if (settings.resize) { - resize(menu, a.parent().parent().parents('ul')); - } - } - }); - }); + this.jump(active, false); + } return this; }; diff --git a/lib/slinky/index.html b/lib/slinky/index.html index 94cbbba19..b18c0770d 100644 --- a/lib/slinky/index.html +++ b/lib/slinky/index.html @@ -6,8 +6,8 @@ Slinky - jQuery sliding menu - - + + @@ -204,7 +204,7 @@

    Demo

    Usage

    -
    $('.menu').slinky(options);
    +
    var slinky = $(…).slinky(options);

    Options

    @@ -213,12 +213,12 @@

    Options

    label 'Back' - Label for the back button. Set to true to use the link's own label + Label for the back button. Pass true to use the link's own label title false - Set to true to show title of current menu level + Pass true to show title of current menu level speed @@ -228,23 +228,60 @@

    Options

    resize true - Resize menu height to match content + Resize menu height to match content on navigation
    +
    +

    API

    +

    .home()

    +

    Navigate back to the main menu

    + + + + + + + + +
    animatetruePass false to skip animation
    +

    .jump()

    +

    Navigate to a sub menu

    + + + + + + + + + + + + +
    toPass a selector for the ul element to jump to
    animatetruePass false to skip animation
    +

    .destroy()

    +

    Remove slinky

    +
    +
    +

    Tips

    +
      +
    • Set .active on a ul element to jump there on init
    • +
    +
    - - + + From 752a3a565494fafbf2229ce476c976156d4c8916 Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 22 Aug 2017 09:49:16 +0200 Subject: [PATCH 04/41] 2.6.0 Feature/refactor field edition (#693) * refactor creation / modification of fields * refactor ldap warning handler * rename var --- inc/field.class.php | 9 ++ inc/field.interface.php | 1 + inc/fields/checkboxesfield.class.php | 18 ++++ inc/fields/descriptionfield.class.php | 12 +++ inc/fields/dropdownfield.class.php | 15 +++ inc/fields/floatfield.class.php | 17 +++ inc/fields/glpiselectfield.class.php | 15 +++ inc/fields/integerfield.class.php | 17 +++ inc/fields/ldapselectfield.class.php | 49 +++++++-- inc/fields/radiosfield.class.php | 18 ++++ inc/fields/selectfield.class.php | 18 ++++ inc/fields/textareafield.class.php | 42 +------- inc/fields/textfield.class.php | 7 ++ inc/fields/urgencyfield.class.php | 7 ++ inc/question.class.php | 149 ++------------------------ setup.php | 7 ++ 16 files changed, 211 insertions(+), 190 deletions(-) diff --git a/inc/field.class.php b/inc/field.class.php index ca6273d1f..a1e6323ef 100644 --- a/inc/field.class.php +++ b/inc/field.class.php @@ -13,6 +13,15 @@ public function __construct($fields, $datas = array()) { $this->fields['answer'] = $datas; } + /** + * Transform input to properly save it in the database + * @param array $input data to transform before save + * return array input data to save as is + */ + public function prepareQuestionInputForSave($input) { + return $input; + } + public function show($canEdit = true) { $required = ($canEdit && $this->fields['required']) ? ' required' : ''; diff --git a/inc/field.interface.php b/inc/field.interface.php index fdd867b2c..ae239f9ea 100644 --- a/inc/field.interface.php +++ b/inc/field.interface.php @@ -5,4 +5,5 @@ interface Field public static function getName(); public static function getPrefs(); public static function getJSFields(); + public function prepareQuestionInputForSave($input); } diff --git a/inc/fields/checkboxesfield.class.php b/inc/fields/checkboxesfield.class.php index b968cc894..bd62c9af7 100644 --- a/inc/fields/checkboxesfield.class.php +++ b/inc/fields/checkboxesfield.class.php @@ -99,6 +99,24 @@ public static function getName() { return __('Checkboxes', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['values'])) { + if (empty($input['values'])) { + Session::addMessageAfterRedirect( + __('The field value is required:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return []; + } else { + $input['values'] = addslashes($input['values']); + } + } + if (isset($input['default_values'])) { + $input['default_values'] = addslashes($input['default_values']); + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/descriptionfield.class.php b/inc/fields/descriptionfield.class.php index 90ebba747..5e97508f5 100644 --- a/inc/fields/descriptionfield.class.php +++ b/inc/fields/descriptionfield.class.php @@ -18,6 +18,18 @@ public static function getName() { return __('Description'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['description']) && empty($input['description'])) { + Session::addMessageAfterRedirect( + __('A description field should have a description:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return array(); + } + $input['description'] = addslashes($input['description']); + return $input; + } + public static function getPrefs() { return array( 'required' => 0, diff --git a/inc/fields/dropdownfield.class.php b/inc/fields/dropdownfield.class.php index 6b23710ad..e0d25530f 100644 --- a/inc/fields/dropdownfield.class.php +++ b/inc/fields/dropdownfield.class.php @@ -46,6 +46,21 @@ public static function getName() { return _n('Dropdown', 'Dropdowns', 1); } + public function prepareQuestionInputForSave($input) { + if (isset($input['dropdown_values'])) { + if (empty($input['dropdown_values'])) { + Session::addMessageAfterRedirect( + __('The field value is required:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return array(); + } + $input['values'] = $input['dropdown_values']; + $input['default_values'] = isset($input['dropdown_default_value']) ? $input['dropdown_default_value'] : ''; + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/floatfield.class.php b/inc/fields/floatfield.class.php index eee6a08ad..19c335a84 100644 --- a/inc/fields/floatfield.class.php +++ b/inc/fields/floatfield.class.php @@ -38,6 +38,23 @@ public static function getName() { return __('Float', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['range_min']) + && isset($input['range_max']) + && isset($input['default_values'])) { + $input['default_values'] = !empty($input['default_values']) + ? (float) str_replace(',', '.', $input['default_values']) + : null; + $input['range_min'] = !empty($input['range_min']) + ? (float) str_replace(',', '.', $input['range_min']) + : null; + $input['range_max'] = !empty($input['range_max']) + ? (float) str_replace(',', '.', $input['range_max']) + : null; + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/glpiselectfield.class.php b/inc/fields/glpiselectfield.class.php index 357261e36..49cad39ef 100644 --- a/inc/fields/glpiselectfield.class.php +++ b/inc/fields/glpiselectfield.class.php @@ -5,6 +5,21 @@ public static function getName() { return _n('GLPI object', 'GLPI objects', 1, 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['glpi_objects'])) { + if (empty($input['glpi_objects'])) { + Session::addMessageAfterRedirect( + __('The field value is required:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return array(); + } + $input['values'] = $input['glpi_objects']; + $input['default_values'] = isset($input['dropdown_default_value']) ? $input['dropdown_default_value'] : ''; + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/integerfield.class.php b/inc/fields/integerfield.class.php index 344d09ebe..bd3bc4a53 100644 --- a/inc/fields/integerfield.class.php +++ b/inc/fields/integerfield.class.php @@ -38,6 +38,23 @@ public static function getName() { return __('Integer', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['range_min']) + && isset($input['range_max']) + && isset($input['default_values'])) { + $input['default_values'] = !empty($input['default_values']) + ? (float) str_replace(',', '.', $input['default_values']) + : null; + $input['range_min'] = !empty($input['range_min']) + ? (float) str_replace(',', '.', $input['range_min']) + : null; + $input['range_max'] = !empty($input['range_max']) + ? (float) str_replace(',', '.', $input['range_max']) + : null; + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/ldapselectfield.class.php b/inc/fields/ldapselectfield.class.php index 433c6f0c4..6c68ead05 100644 --- a/inc/fields/ldapselectfield.class.php +++ b/inc/fields/ldapselectfield.class.php @@ -15,15 +15,7 @@ public function getAvailableValues() { return array(); } - if (!function_exists('warning_handler')) { - function warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) { - if (0 === error_reporting()) { - return false; - } - throw new ErrorException($errstr, 0, $errno, $errfile, $errline); - } - } - set_error_handler("warning_handler", E_WARNING); + set_error_handler('plugin_formcreator_ldap_warning_handler', E_WARNING); try { $tab_values = array(); @@ -70,6 +62,45 @@ public static function getName() { return __('LDAP Select', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + // Fields are differents for dropdown lists, so we need to replace these values into the good ones + if (isset($input['ldap_auth']) + && !empty($input['ldap_auth'])) { + + $config_ldap = new AuthLDAP(); + $config_ldap->getFromDB($input['ldap_auth']); + + if (!empty($input['ldap_attribute'])) { + $ldap_dropdown = new RuleRightParameter(); + $ldap_dropdown->getFromDB($input['ldap_attribute']); + $attribute = array($ldap_dropdown->fields['value']); + } else { + $attribute = array(); + } + + set_error_handler('plugin_formcreator_ldap_warning_handler', E_WARNING); + + try { + $ds = $config_ldap->connect(); + ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_control_paged_result($ds, 1); + $sn = ldap_search($ds, $config_ldap->fields['basedn'], $input['ldap_filter'], $attribute); + $entries = ldap_get_entries($ds, $sn); + } catch (Exception $e) { + Session::addMessageAfterRedirect(__('Cannot recover LDAP informations!', 'formcreator'), false, ERROR); + } + + restore_error_handler(); + + $input['values'] = json_encode(array( + 'ldap_auth' => $input['ldap_auth'], + 'ldap_filter' => $input['ldap_filter'], + 'ldap_attribute' => strtolower($input['ldap_attribute']), + )); + } + return $input; + } + public function getAnswer() { $values = $this->getAvailableValues(); $value = $this->getValue(); diff --git a/inc/fields/radiosfield.class.php b/inc/fields/radiosfield.class.php index 05bdd76ae..6f4ee03e7 100644 --- a/inc/fields/radiosfield.class.php +++ b/inc/fields/radiosfield.class.php @@ -46,6 +46,24 @@ public static function getName() { return __('Radios', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['values'])) { + if (empty($input['values'])) { + Session::addMessageAfterRedirect( + __('The field value is required:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return []; + } else { + $input['values'] = addslashes($input['values']); + } + } + if (isset($input['default_values'])) { + $input['default_values'] = addslashes($input['default_values']); + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/selectfield.class.php b/inc/fields/selectfield.class.php index a90c2b171..b8dec5a29 100644 --- a/inc/fields/selectfield.class.php +++ b/inc/fields/selectfield.class.php @@ -52,6 +52,24 @@ public static function getName() { return __('Select', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['values'])) { + if (empty($input['values'])) { + Session::addMessageAfterRedirect( + __('The field value is required:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return []; + } else { + $input['values'] = addslashes($input['values']); + } + } + if (isset($input['default_values'])) { + $input['default_values'] = addslashes($input['default_values']); + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/textareafield.class.php b/inc/fields/textareafield.class.php index 94fa1aae5..ae4aae23c 100644 --- a/inc/fields/textareafield.class.php +++ b/inc/fields/textareafield.class.php @@ -1,5 +1,5 @@ fields['range_min']) && strlen($value) < $this->fields['range_min']) { - Session::addMessageAfterRedirect(sprintf(__('The text is too short (minimum %d characters):', 'formcreator'), $this->fields['range_min']).' '.$this->fields['name'], false, ERROR); - return false; - - // Max range not set or text length shorter than max length - } else if (!empty($this->fields['range_max']) && strlen($value) > $this->fields['range_max']) { - Session::addMessageAfterRedirect(sprintf(__('The text is too long (maximum %d characters):', 'formcreator'), $this->fields['range_max']).' '.$this->fields['name'], false, ERROR); - return false; - - // Specific format not set or well match - } else if (!empty($this->fields['regex']) && !preg_match($this->fields['regex'], $value)) { - Session::addMessageAfterRedirect(__('Specific format does not match:', 'formcreator').' '.$this->fields['name'], false, ERROR); - return false; - } - - // All is OK - return true; - } - public static function getName() { return __('Textarea', 'formcreator'); } - public static function getPrefs() { - return array( - 'required' => 1, - 'default_values' => 1, - 'values' => 0, - 'range' => 1, - 'show_empty' => 0, - 'regex' => 1, - 'show_type' => 1, - 'dropdown_value' => 0, - 'glpi_objects' => 0, - 'ldap_values' => 0, - ); - } - public static function getJSFields() { $prefs = self::getPrefs(); return "tab_fields_fields['textarea'] = 'showFields(".implode(', ', $prefs).");';"; diff --git a/inc/fields/textfield.class.php b/inc/fields/textfield.class.php index d409c2900..f3d334ff8 100644 --- a/inc/fields/textfield.class.php +++ b/inc/fields/textfield.class.php @@ -32,6 +32,13 @@ public static function getName() { return __('Text', 'formcreator'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['default_values'])) { + $input['default_values'] = addslashes($input['default_values']); + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/fields/urgencyfield.class.php b/inc/fields/urgencyfield.class.php index e51985f8f..20a7d8d34 100644 --- a/inc/fields/urgencyfield.class.php +++ b/inc/fields/urgencyfield.class.php @@ -35,6 +35,13 @@ public static function getName() { return __('Urgency'); } + public function prepareQuestionInputForSave($input) { + if (isset($input['values'])) { + $input['values'] = addslashes($input['values']); + } + return $input; + } + public static function getPrefs() { return array( 'required' => 1, diff --git a/inc/question.class.php b/inc/question.class.php index eb1bfe26a..e83311c7c 100644 --- a/inc/question.class.php +++ b/inc/question.class.php @@ -259,146 +259,25 @@ private function checkBeforeSave($input) { if (isset($input['fieldtype']) && empty($input['fieldtype'])) { Session::addMessageAfterRedirect(__('The field type is required', 'formcreator'), false, ERROR); - return array(); + return []; } // - section is required if (isset($input['plugin_formcreator_sections_id']) && empty($input['plugin_formcreator_sections_id'])) { Session::addMessageAfterRedirect(__('The section is required', 'formcreator'), false, ERROR); - return array(); + return []; } - // Values are required for GLPI dropdowns, dropdowns, multiple dropdowns, checkboxes, radios, LDAP - $itemtypes = array('select', 'multiselect', 'checkboxes', 'radios', 'ldap'); - if (in_array($input['fieldtype'], $itemtypes)) { - if (isset($input['values'])) { - if (empty($input['values'])) { - Session::addMessageAfterRedirect( - __('The field value is required:', 'formcreator') . ' ' . $input['name'], - false, - ERROR); - return array(); - } else { - $input['values'] = addslashes($input['values']); - } - } - if (isset($input['default_values'])) { - $input['default_values'] = addslashes($input['default_values']); - } + $fieldTypes = PluginFormcreatorFields::getTypes(); + if (!in_array($input['fieldtype'], array_keys($fieldTypes))) { + Session::addMessageAfterRedirect(__('Unknown field type', 'formcreator'), false, ERROR); + return []; } - // Fields are differents for dropdown lists, so we need to replace these values into the good ones - if (isset($input['fieldtype'])) { - if ($input['fieldtype'] == 'dropdown' - && isset($input['dropdown_values'])) { - if (empty($input['dropdown_values'])) { - Session::addMessageAfterRedirect( - __('The field value is required:', 'formcreator') . ' ' . $input['name'], - false, - ERROR); - return array(); - } - $input['values'] = $input['dropdown_values']; - $input['default_values'] = isset($input['dropdown_default_value']) ? $input['dropdown_default_value'] : ''; - } - - // Fields are differents for GLPI object lists, so we need to replace these values into the good ones - if ($input['fieldtype'] == 'glpiselect' - && isset($input['glpi_objects'])) { - if (empty($input['glpi_objects'])) { - Session::addMessageAfterRedirect( - __('The field value is required:', 'formcreator') . ' ' . $input['name'], - false, - ERROR); - return array(); - } - $input['values'] = $input['glpi_objects']; - $input['default_values'] = isset($input['dropdown_default_value']) ? $input['dropdown_default_value'] : ''; - } - - // A description field should have a description - if ($input['fieldtype'] == 'description') { - if (isset($input['description']) - && empty($input['description'])) { - Session::addMessageAfterRedirect( - __('A description field should have a description:', 'formcreator') . ' ' . $input['name'], - false, - ERROR); - return array(); - } - } - - // format values for numbers - if (isset($input['range_min']) - && isset($input['range_max']) - && isset($input['default_values']) - && ($input['fieldtype'] == 'integer') || ($input['fieldtype'] == 'float')) { - $input['default_values'] = !empty($input['default_values']) - ? (float) str_replace(',', '.', $input['default_values']) - : null; - $input['range_min'] = !empty($input['range_min']) - ? (float) str_replace(',', '.', $input['range_min']) - : null; - $input['range_max'] = !empty($input['range_max']) - ? (float) str_replace(',', '.', $input['range_max']) - : null; - } - - // LDAP fields validation - if ($input['fieldtype'] == 'ldapselect') { - // Fields are differents for dropdown lists, so we need to replace these values into the good ones - if (isset($input['ldap_auth']) - && !empty($input['ldap_auth'])) { - - $config_ldap = new AuthLDAP(); - $config_ldap->getFromDB($input['ldap_auth']); - - if (!empty($input['ldap_attribute'])) { - $ldap_dropdown = new RuleRightParameter(); - $ldap_dropdown->getFromDB($input['ldap_attribute']); - $attribute = array($ldap_dropdown->fields['value']); - } else { - $attribute = array(); - } - - // Set specific error handler to catch LDAP errors - if (!function_exists('warning_handler')) { - function warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) { - if (0 === error_reporting()) { - return false; - } - throw new ErrorException($errstr, 0, $errno, $errfile, $errline); - } - } - set_error_handler("warning_handler", E_WARNING); - - try { - $ds = $config_ldap->connect(); - ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); - ldap_control_paged_result($ds, 1); - $sn = ldap_search($ds, $config_ldap->fields['basedn'], $input['ldap_filter'], $attribute); - $entries = ldap_get_entries($ds, $sn); - } catch (Exception $e) { - Session::addMessageAfterRedirect(__('Cannot recover LDAP informations!', 'formcreator'), false, ERROR); - } - - restore_error_handler(); - - $input['values'] = json_encode(array( - 'ldap_auth' => $input['ldap_auth'], - 'ldap_filter' => $input['ldap_filter'], - 'ldap_attribute' => strtolower($input['ldap_attribute']), - )); - } - } - } - - if ($input['fieldtype'] == 'textarea' || $input['fieldtype'] == 'text') { - if (isset($input['default_values'])) { - $input['default_values'] = addslashes($input['default_values']); - } - } + $fieldType = 'PluginFormcreator' . ucfirst($input['fieldtype']) . 'Field'; + $fieldObject = new $fieldType($this->fields); + $input = $fieldObject->prepareQuestionInputForSave($input); // Add leading and trailing regex marker automaticaly if (isset($input['regex']) @@ -419,16 +298,6 @@ function warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) } } - if (($input['fieldtype'] == 'urgency')) { - if (isset($input['values'])) { - $input['values'] = addslashes($input['values']); - } - } - - if (isset($input['description'])) { - $input['description'] = addslashes($input['description']); - } - return $input; } diff --git a/setup.php b/setup.php index d377321c4..79e926eb5 100644 --- a/setup.php +++ b/setup.php @@ -335,3 +335,10 @@ function plugin_formcreator_upgrade_error(Migration $migration) { $migration->log($error . "\n" . Toolbox::backtrace(false, '', array('Toolbox::backtrace()')), false); die($error . "

    Please, check migration log"); } + +function plugin_formcreator_ldap_warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) { + if (0 === error_reporting()) { + return false; + } + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} From 12d7eee10a5c935d454c637581e143a4ffca06e8 Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 1 Aug 2017 17:24:55 +0200 Subject: [PATCH 05/41] fix #675 dropdown style --- inc/form_answer.class.php | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/inc/form_answer.class.php b/inc/form_answer.class.php index 798b851d6..2863ed419 100644 --- a/inc/form_answer.class.php +++ b/inc/form_answer.class.php @@ -171,21 +171,12 @@ public static function getSpecificValueToSelect($field, $name='', $values='', ar switch ($field) { case 'status' : - $output = ''; - + $elements = [ + 'waiting' => __('waiting', 'formcreator'), + 'accepted' => __('accepted', 'formcreator'), + 'refused' => __('refused', 'formcreator'), + ]; + $output = Dropdown::showFromArray($name, $elements, ['display' => false, 'value' => $values[$field]]); return $output; break; } From 202bda306c4dfed1778eda6ce3d1f44bfcb4bd83 Mon Sep 17 00:00:00 2001 From: btry Date: Mon, 7 Aug 2017 20:47:09 +0200 Subject: [PATCH 06/41] use conventional class and filenames --- inc/field.class.php | 3 +-- inc/{field.interface.php => fieldinterface.class.php} | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) rename inc/{field.interface.php => fieldinterface.class.php} (81%) diff --git a/inc/field.class.php b/inc/field.class.php index a1e6323ef..2fc93b8b6 100644 --- a/inc/field.class.php +++ b/inc/field.class.php @@ -1,8 +1,7 @@ Date: Tue, 22 Aug 2017 11:35:09 +0200 Subject: [PATCH 07/41] give to admins full power of regex (#701) * give to admins full power of regex * revert upgrade * code style --- inc/question.class.php | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/inc/question.class.php b/inc/question.class.php index e83311c7c..0d38af5cd 100644 --- a/inc/question.class.php +++ b/inc/question.class.php @@ -280,21 +280,15 @@ private function checkBeforeSave($input) { $input = $fieldObject->prepareQuestionInputForSave($input); // Add leading and trailing regex marker automaticaly - if (isset($input['regex']) - && !empty($input['regex'])) { - if (substr($input['regex'], 0, 1) != '/') { - if (substr($input['regex'], 0, 1) != '^') { - $input['regex'] = '/^' . $input['regex']; - } else { - $input['regex'] = '/' . $input['regex']; - } - } - if (substr($input['regex'], -1, 1) != '/') { - if (substr($input['regex'], -1, 1) != '$') { - $input['regex'] = $input['regex'] . '$/'; - } else { - $input['regex'] = $input['regex'] . '/'; - } + if (isset($input['regex']) && !empty($input['regex'])) { + // Avoid php notice when validating the regular expression + set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext) {}); + $isValid = !(preg_match($input['regex'], null) === false); + restore_error_handler(); + + if (!$isValid) { + Session::addMessageAfterRedirect(__('The regular expression is invalid', 'formcreator'), false, ERROR); + return []; } } @@ -361,6 +355,10 @@ public function prepareInputForUpdate($input) { $input = $this->checkBeforeSave($input); } + if (!is_array($input) || count($input) == 0) { + return false; + } + // generate a uniq id if (!isset($input['uuid']) || empty($input['uuid'])) { From b3e97bbe2c6e931b1994b0e595fddbfc237c8699 Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 22 Aug 2017 11:46:41 +0200 Subject: [PATCH 08/41] 2.6 Feature/forms answers refactor (#658) * code refactor * factorize * fix warning * rename var * CS * fix loss if input when validating an incomplete form --- inc/form.class.php | 44 ++++---- inc/form_answer.class.php | 220 +++++++++++++++++++------------------- inc/question.class.php | 5 + 3 files changed, 139 insertions(+), 130 deletions(-) diff --git a/inc/form.class.php b/inc/form.class.php index a3b3690ee..1003fc68e 100644 --- a/inc/form.class.php +++ b/inc/form.class.php @@ -821,11 +821,11 @@ protected function showMyLastForms() { public function displayUserForm(CommonGLPI $item) { global $CFG_GLPI, $DB; - if (isset($_SESSION['formcreator']['datas'])) { - $datas = $_SESSION['formcreator']['datas']; - unset($_SESSION['formcreator']['datas']); + if (isset($_SESSION['formcreator']['data'])) { + $data = $_SESSION['formcreator']['data']; + unset($_SESSION['formcreator']['data']); } else { - $datas = null; + $data = null; } // Print css media @@ -860,11 +860,11 @@ class='formcreator_form form_horizontal'>"; // Display all fields of the section $questions = $question->find('plugin_formcreator_sections_id = ' . $section_line['id'], '`order` ASC'); foreach ($questions as $question_line) { - if (isset($datas[$question_line['id']])) { + if (isset($data['formcreator_field_' . $question_line['id']])) { // multiple choice question are saved as JSON and needs to be decoded $answer = (in_array($question_line['fieldtype'], array('checkboxes', 'multiselect'))) - ? json_decode($datas[$question_line['id']]) - : $datas[$question_line['id']]; + ? json_decode($data['formcreator_field_' . $question_line['id']]) + : $data['formcreator_field_' . $question_line['id']]; } else { $answer = null; } @@ -1061,7 +1061,7 @@ public function saveForm() { $valid = true; $tab_section = array(); - $datas = array(); + $data = array(); $sections = new PluginFormcreatorSection(); $found_sections = $sections->find('`plugin_formcreator_forms_id` = ' . $this->getID()); foreach ($found_sections as $id => $fields) { @@ -1078,25 +1078,25 @@ public function saveForm() { foreach ($found_questions as $id => $fields) { // If field was not post, it's value is empty if (isset($_POST['formcreator_field_' . $id])) { - $datas[$id] = is_array($_POST['formcreator_field_' . $id]) + $data['formcreator_field_' . $id] = is_array($_POST['formcreator_field_' . $id]) ? json_encode($_POST['formcreator_field_' . $id], JSON_UNESCAPED_UNICODE) : $_POST['formcreator_field_' . $id]; // Replace "," by "." if field is a float field and remove spaces if ($fields['fieldtype'] == 'float') { - $datas[$id] = str_replace(',', '.', $datas[$id]); - $datas[$id] = str_replace(' ', '', $datas[$id]); + $data['formcreator_field_' . $id] = str_replace(',', '.', $data['formcreator_field_' . $id]); + $data['formcreator_field_' . $id] = str_replace(' ', '', $data['formcreator_field_' . $id]); } unset($_POST['formcreator_field_' . $id]); } else { - $datas[$id] = ''; + $data['formcreator_field_' . $id] = ''; } $className = 'PluginFormcreator' . ucfirst($fields['fieldtype']) . 'Field'; if (class_exists($className)) { - $obj = new $className($fields, $datas); - if (PluginFormcreatorFields::isVisible($id, $datas) && !$obj->isValid($datas[$id])) { + $obj = new $className($fields, $data); + if (PluginFormcreatorFields::isVisible($id, $data) && !$obj->isValid($data['formcreator_field_' . $id])) { $valid = false; } } else { @@ -1104,38 +1104,38 @@ public function saveForm() { } } if (isset($_POST) && is_array($_POST)) { - $datas = $datas + $_POST; + $data = $data + $_POST; } // Check required_validator - if ($this->fields['validation_required'] && empty($datas['formcreator_validator'])) { + if ($this->fields['validation_required'] && empty($data['formcreator_validator'])) { Session::addMessageAfterRedirect(__('You must select validator !', 'formcreator'), false, ERROR); $valid = false; } // If not valid back to form if (!$valid) { - foreach ($datas as $key => $value) { + foreach ($data as $key => $value) { if (is_array($value)) { foreach ($value as $key2 => $value2) { - $datas[$key][$key2] = plugin_formcreator_encode($value2); + $data[$key][$key2] = plugin_formcreator_encode($value2); } } else if (is_array(json_decode($value))) { $value = json_decode($value); foreach ($value as $key2 => $value2) { $value[$key2] = plugin_formcreator_encode($value2); } - $datas[$key] = json_encode($value); + $data[$key] = json_encode($value); } else { - $datas[$key] = plugin_formcreator_encode($value); + $data[$key] = plugin_formcreator_encode($value); } } - $_SESSION['formcreator']['datas'] = $datas; + $_SESSION['formcreator']['data'] = $data; // Save form } else { $formanswer = new PluginFormcreatorForm_Answer(); - $formanswer->saveAnswers($datas); + $formanswer->saveAnswers($data); } return $valid; diff --git a/inc/form_answer.class.php b/inc/form_answer.class.php index 2863ed419..dfbeed184 100644 --- a/inc/form_answer.class.php +++ b/inc/form_answer.class.php @@ -471,7 +471,7 @@ public function prepareInputForUpdate($input) { } /** - * ACtions done before deleting an item. In case of failure, prevents + * Actions done before deleting an item. In case of failure, prevents * actual deletion of the item * * @return boolean true if pre_delete actions succeeded, false if not @@ -499,15 +499,8 @@ public function saveAnswers($datas) { ?intval($datas['id']) :-1; - $query = "SELECT q.`id`, q.`fieldtype`, q.`name`, a.`id` as answer_id - FROM glpi_plugin_formcreator_questions q - LEFT JOIN glpi_plugin_formcreator_sections s - ON s.`id` = q.`plugin_formcreator_sections_id` - LEFT JOIN `glpi_plugin_formcreator_answers` AS a - ON a.`plugin_formcreator_forms_answers_id` = $formanswers_id - AND a.`plugin_formcreator_question_id` = q.`id` - WHERE s.`plugin_formcreator_forms_id` = {$datas['formcreator_form']}"; - $result = $DB->query($query); + $question = new PluginFormcreatorQuestion(); + $questions = $question->getQuestionsFromForm($datas['formcreator_form']); // Update form answers if (isset($datas['save_formanswer'])) { @@ -520,58 +513,32 @@ public function saveAnswers($datas) { // Update questions answers if ($status == 'waiting') { - while ($question = $DB->fetch_array($result)) { - if ($question['fieldtype'] != 'file') { - $data_value = $datas['formcreator_field_' . $question['id']]; - if (isset($data_value)) { - if (is_array($data_value)) { - foreach ($data_value as $key => $value) { - $data_value[$key] = $value; - } - $answer_value = json_encode($data_value); - } else { - $answer_value = $data_value; - } + foreach ($questions as $question) { + // unset the answer value + $answer_value = null; + + if ($question->getField('fieldtype') != 'file') { + // If the answer is set, check if it is an array (then implode id). + if (isset($datas['formcreator_field_' . $question->getID()])) { + $answer_value = $this->transformAnswerValue($datas['formcreator_field_' . $question->getID()]); } else { $answer_value = ''; } + } else if (isset($_FILES['formcreator_field_' . $question->getID()]['tmp_name']) + && is_file($_FILES['formcreator_field_' . $question->getID()]['tmp_name'])) { + $answer_value = $this->saveDocument($form, $question, $_FILES['formcreator_field_' . $question->getID()]); + } + // $answer_value may be still null if the field type is file and no file was uploaded + if ($answer_value !== null) { + // Update the answer to the question + $questionId = $question->getID(); + $answer = new PluginFormcreatorAnswer(); + $answer->getFromDBByQuery("WHERE `plugin_formcreator_forms_answers_id` = '$formanswers_id' AND `plugin_formcreator_question_id` = '$questionId'"); $answer->update(array( - 'id' => $question['answer_id'], + 'id' => $answer->getID(), 'answer' => $answer_value, ), 0); - } else if (isset($_FILES['formcreator_field_' . $question['id']]['tmp_name']) - && is_file($_FILES['formcreator_field_' . $question['id']]['tmp_name'])) { - $doc = new Document(); - - $file_datas = array(); - $file_datas["name"] = $form->fields['name'] . ' - ' . $question['name']; - $file_datas["entities_id"] = isset($_SESSION['glpiactive_entity']) - ? $_SESSION['glpiactive_entity'] - : $form->fields['entities_id']; - $file_datas["is_recursive"] = $form->fields['is_recursive']; - Document::uploadDocument($file_datas, $_FILES['formcreator_field_' . $question['id']]); - - if ($docID = $doc->add($file_datas)) { - $docID = intval($docID); - $table = getTableForItemType('Document'); - $filename = $_FILES['formcreator_field_' . $question['id']]['name']; - $query = "UPDATE `$table` SET `filename` = '$filename' - WHERE `id` = $docID"; - $DB->query($query); - - $docItem = new Document_Item(); - $docItemId = $docItem->add(array( - 'documents_id' => $docID, - 'itemtype' => __CLASS__, - 'items_id' => $datas['id'], - )); - - $answer->update(array( - 'id' => $question['answer_id'], - 'answer' => $docID, - ), 0); - } } } } @@ -604,65 +571,32 @@ public function saveAnswers($datas) { )); // Save questions answers - while ($question = $DB->fetch_assoc($result)) { - // If the answer is set, check if it is an array (then implode id). - if (isset($datas[$question['id']])) { - $question_answer = $datas[$question['id']]; - if (is_array(json_decode($question_answer, JSON_UNESCAPED_UNICODE))) { - $question_answer = json_decode($question_answer); - foreach ($question_answer as $key => $value) { - $question_answer[$key] = $value; - } - $question_answer = json_encode($question_answer, JSON_UNESCAPED_UNICODE); + foreach ($questions as $question) { + // unset the answer value + $answer_value = null; + + if ($question->getField('fieldtype') != 'file') { + // If the answer is set, check if it is an array (then implode id). + if (isset($datas['formcreator_field_' . $question->getID()])) { + $answer_value = $this->transformAnswerValue($datas['formcreator_field_' . $question->getID()]); } else { - $question_answer = $question_answer; + $answer_value = ''; } - } else { - $question_answer = ''; + $answer_value = $this->transformAnswerValue($datas['formcreator_field_' . $question->getID()]); + } else if ((isset($_FILES['formcreator_field_' . $question->getID()]['tmp_name'])) + && (is_file($_FILES['formcreator_field_' . $question->getID()]['tmp_name']))) { + $answer_value = $this->saveDocument($form, $question, $_FILES['formcreator_field_' . $question->getID()]); } - $answerID = $answer->add(array( - 'plugin_formcreator_forms_answers_id' => $id, - 'plugin_formcreator_question_id' => $question['id'], - 'answer' => $question_answer, - ), array(), 0); - - // If the question is a file field, save the file as a document - if (($question['fieldtype'] == 'file') - && (isset($_FILES['formcreator_field_' . $question['id']]['tmp_name'])) - && (is_file($_FILES['formcreator_field_' . $question['id']]['tmp_name']))) { - $doc = new Document(); - $file_datas = array(); - $file_datas["name"] = $form->fields['name'] . ' - ' . $question['name']; - $file_datas["entities_id"] = isset($_SESSION['glpiactive_entity']) - ? $_SESSION['glpiactive_entity'] - : $form->fields['entities_id']; - $file_datas["is_recursive"] = $form->fields['is_recursive']; - Document::uploadDocument($file_datas, $_FILES['formcreator_field_' . $question['id']]); - - if ($docID = $doc->add($file_datas)) { - $docID = intval($docID); - $table = getTableForItemType('Document'); - $filename = $_FILES['formcreator_field_' . $question['id']]['name']; - $query = "UPDATE `$table` SET `filename` = '$filename' - WHERE `id` = $docID"; - $DB->query($query); - - $docItem = new Document_Item(); - $docItemId = $docItem->add(array( - 'documents_id' => $docID, - 'itemtype' => __CLASS__, - 'items_id' => $id, - )); - - $answer->update(array( - 'id' => $answerID, - 'answer' => $docID, - ), 0); - } + if ($answer_value !== null) { + // Save the answer to the question + $answerID = $answer->add(array( + 'plugin_formcreator_forms_answers_id' => $id, + 'plugin_formcreator_question_id' => $question->getID(), + 'answer' => $answer_value, + ), array(), 0); } } - $is_newFormAnswer = true; } @@ -790,6 +724,76 @@ public function saveAnswers($datas) { Session::addMessageAfterRedirect(__('The form has been successfully saved!', 'formcreator'), true, INFO); } + /** + * + * @param array|string $value + * @return null|string + */ + private function transformAnswerValue($value = null) { + // unset the answer value + $answer_value = null; + + // If the answer is set, check if it is an array (then implode id). + if ($value !== null) { + $answer_value = $value; + if (is_array(json_decode($answer_value, JSON_UNESCAPED_UNICODE))) { + $answer_value = json_decode($answer_value); + foreach ($answer_value as $key => $value) { + $answer_value[$key] = $value; + } + $answer_value = json_encode($answer_value, JSON_UNESCAPED_UNICODE); + } else { + $answer_value = $answer_value; + } + } else { + $answer_value = ''; + } + + return $answer_value; + } + + /** + * Save an uploaded file into a document object, link it to the answers + * and returns the document ID + * @param PluginFormcreatorForm $form + * @param PluginFormcreatorQuestion $question + * @param array $file an item from $_FILES array + * + * @return integer|NULL + */ + private function saveDocument(PluginFormcreatorForm $form, PluginFormcreatorQuestion $question, $file) { + global $DB; + + $doc = new Document(); + + $file_data = array(); + $file_data["name"] = $form->getField('name'). ' - ' . $question->getField('name'); + $file_data["entities_id"] = isset($_SESSION['glpiactive_entity']) + ? $_SESSION['glpiactive_entity'] + : $form->getField('entities_id'); + $file_data["is_recursive"] = $form->getField('is_recursive'); + Document::uploadDocument($file_data, $file); + + if ($docID = $doc->add($file_data)) { + $docID = intval($docID); + $table = Document::getTable(); + $filename = $file['name']; + $query = "UPDATE `$table` SET `filename` = '$filename' + WHERE `id` = '$docID'"; + $DB->query($query); + + $docItem = new Document_Item(); + $docItemId = $docItem->add([ + 'documents_id' => $docID, + 'itemtype' => __CLASS__, + 'items_id' => $id, + ]); + return $docID; + } + + return null; + } + public function refuseAnswers($datas) { $datas['plugin_formcreator_forms_id'] = intval($datas['formcreator_form']); $datas['status'] = 'refused'; diff --git a/inc/question.class.php b/inc/question.class.php index 0d38af5cd..e2fad0e83 100644 --- a/inc/question.class.php +++ b/inc/question.class.php @@ -1218,6 +1218,11 @@ public function getForm() { } + /** + * return array of question objects belonging to a form + * @param integer $formId + * @return PluginFormcreatorQuestion[] + */ public function getQuestionsFromForm($formId) { global $DB; From 69e130380cac081a521de15eba3437c334118046 Mon Sep 17 00:00:00 2001 From: btry Date: Tue, 22 Aug 2017 14:38:24 +0200 Subject: [PATCH 09/41] refactor destination generation (code factorization) (#704) * mutualize method * refactor --- inc/question.class.php | 4 +-- inc/section.class.php | 4 +-- inc/targetbase.class.php | 57 ++++++++++++++++++++++++++++++++++++ inc/targetchange.class.php | 60 -------------------------------------- inc/targetticket.class.php | 60 -------------------------------------- 5 files changed, 60 insertions(+), 125 deletions(-) diff --git a/inc/question.class.php b/inc/question.class.php index e2fad0e83..51d7544e2 100644 --- a/inc/question.class.php +++ b/inc/question.class.php @@ -1238,7 +1238,7 @@ public function getQuestionsFromForm($formId) { while ($row = $DB->fetch_assoc($result)) { $question = new self(); $question->getFromDB($row['id']); - $questions[] = $question; + $questions[$row['id']] = $question; } return $questions; @@ -1250,7 +1250,7 @@ public function getQuestionsFromSection($sectionId) { foreach ($rows as $questionId => $row) { $question = new self(); $question->getFromDB($questionId); - $questions[] = $question; + $questions[$questionId] = $question; } return $questions; diff --git a/inc/section.class.php b/inc/section.class.php index 8f6647ddc..afe275b8d 100644 --- a/inc/section.class.php +++ b/inc/section.class.php @@ -304,15 +304,13 @@ public function getSectionsFromForm($formId) { foreach ($rows as $sectionId => $row) { $section = new self(); $section->getFromDB($sectionId); - $sections[] = $section; + $sections[$sectionId] = $section; } return $sections; } public function showSubForm($ID) { - global $CFG_GLPI; - if ($ID == 0) { $name = ''; $uuid = ''; diff --git a/inc/targetbase.class.php b/inc/targetbase.class.php index be0173578..63f16f53d 100644 --- a/inc/targetbase.class.php +++ b/inc/targetbase.class.php @@ -704,4 +704,61 @@ function change_tag_type() { echo ''; } } + + /** + * Parse target content to replace TAGS like ##FULLFORM## by the values + * + * @param String $content String to be parsed + * @param PluginFormcreatorForm_Answer $formanswer Formanswer object where answers are stored + * @param String + * @return String Parsed string with tags replaced by form values + */ + protected function parseTags($content, PluginFormcreatorForm_Answer $formanswer, $fullform = "") { + global $DB, $CFG_GLPI; + + if ($fullform == "") { + $fullform = $formanswer->getFullForm(); + } + // retrieve answers + $answers_values = $formanswer->getAnswers($formanswer->getID()); + + $content = str_replace('##FULLFORM##', $fullform, $content); + + $section = new PluginFormcreatorSection(); + $sections = $section->getSectionsFromForm($formanswer->fields['plugin_formcreator_forms_id']); + $sectionsIdString = implode(', ', array_keys($sections)); + + if (count($sections) > 0) { + $query_questions = "SELECT `questions`.*, `answers`.`answer` + FROM `glpi_plugin_formcreator_questions` AS questions + LEFT JOIN `glpi_plugin_formcreator_answers` AS answers + ON `answers`.`plugin_formcreator_question_id` = `questions`.`id` + AND `plugin_formcreator_forms_answers_id` = ".$formanswer->getID()." + WHERE `questions`.`plugin_formcreator_sections_id` IN ($sectionsIdString) + ORDER BY `questions`.`order` ASC"; + $res_questions = $DB->query($query_questions); + while ($question_line = $DB->fetch_assoc($res_questions)) { + $id = $question_line['id']; + if (!PluginFormcreatorFields::isVisible($question_line['id'], $answers_values)) { + $name = ''; + $value = ''; + } else { + $name = $question_line['name']; + $value = PluginFormcreatorFields::getValue($question_line, $question_line['answer']); + } + if (is_array($value)) { + if ($CFG_GLPI['use_rich_text']) { + $value = '
    ' . implode('
    ', $value); + } else { + $value = "\r\n" . implode("\r\n", $value); + } + } + + $content = str_replace('##question_' . $id . '##', $name, $content); + $content = str_replace('##answer_' . $id . '##', $value, $content); + } + } + + return $content; + } } diff --git a/inc/targetchange.class.php b/inc/targetchange.class.php index 4a0eac15b..bf067bf96 100644 --- a/inc/targetchange.class.php +++ b/inc/targetchange.class.php @@ -1249,66 +1249,6 @@ public function save(PluginFormcreatorForm_Answer $formanswer) { return true; } - /** - * Parse target content to replace TAGS like ##FULLFORM## by the values - * - * @param String $content String to be parsed - * @param PluginFormcreatorForm_Answer $formanswer Formanswer object where answers are stored - * @param String - * @return String Parsed string with tags replaced by form values - */ - private function parseTags($content, PluginFormcreatorForm_Answer $formanswer, $fullform = "") { - global $DB, $CFG_GLPI; - - if ($fullform == "") { - $fullform = $formanswer->getFullForm(); - } - // retrieve answers - $answers_values = $formanswer->getAnswers($formanswer->getID()); - - $content = str_replace('##FULLFORM##', $fullform, $content); - $section = new PluginFormcreatorSection(); - $found = $section->find('plugin_formcreator_forms_id = '.$formanswer->fields['plugin_formcreator_forms_id'], - '`order` ASC'); - $tab_section = array(); - foreach ($found as $section_item) { - $tab_section[] = $section_item['id']; - } - - if (!empty($tab_section)) { - $query_questions = "SELECT `questions`.*, `answers`.`answer` - FROM `glpi_plugin_formcreator_questions` AS questions - LEFT JOIN `glpi_plugin_formcreator_answers` AS answers - ON `answers`.`plugin_formcreator_question_id` = `questions`.`id` - AND `plugin_formcreator_forms_answers_id` = ".$formanswer->getID()." - WHERE `questions`.`plugin_formcreator_sections_id` IN (".implode(', ', $tab_section).") - ORDER BY `questions`.`order` ASC"; - $res_questions = $DB->query($query_questions); - while ($question_line = $DB->fetch_assoc($res_questions)) { - $id = $question_line['id']; - if (!PluginFormcreatorFields::isVisible($question_line['id'], $answers_values)) { - $name = ''; - $value = ''; - } else { - $name = $question_line['name']; - $value = PluginFormcreatorFields::getValue($question_line, $question_line['answer']); - } - if (is_array($value)) { - if ($CFG_GLPI['use_rich_text']) { - $value = '
    ' . implode('
    ', $value); - } else { - $value = "\r\n" . implode("\r\n", $value); - } - } - - $content = str_replace('##question_' . $id . '##', $name, $content); - $content = str_replace('##answer_' . $id . '##', $value, $content); - } - } - - return $content; - } - private static function getDeleteImage($id) { global $CFG_GLPI; diff --git a/inc/targetticket.class.php b/inc/targetticket.class.php index 3e1cc8f80..939fd9aad 100644 --- a/inc/targetticket.class.php +++ b/inc/targetticket.class.php @@ -1195,66 +1195,6 @@ protected function setTargetUrgency($data, $formanswer) { return $data; } - /** - * Parse target content to replace TAGS like ##FULLFORM## by the values - * - * @param String $content String to be parsed - * @param PluginFormcreatorForm_Answer $formanswer Formanswer object where answers are stored - * @param String - * @return String Parsed string with tags replaced by form values - */ - private function parseTags($content, PluginFormcreatorForm_Answer $formanswer, $fullform = "") { - global $DB, $CFG_GLPI; - - if ($fullform == "") { - $fullform = $formanswer->getFullForm(); - } - // retrieve answers - $answers_values = $formanswer->getAnswers($formanswer->getID()); - - $content = str_replace('##FULLFORM##', $fullform, $content); - $section = new PluginFormcreatorSection(); - $found = $section->find('plugin_formcreator_forms_id = '.$formanswer->fields['plugin_formcreator_forms_id'], - '`order` ASC'); - $tab_section = array(); - foreach ($found as $section_item) { - $tab_section[] = $section_item['id']; - } - - if (!empty($tab_section)) { - $query_questions = "SELECT `questions`.*, `answers`.`answer` - FROM `glpi_plugin_formcreator_questions` AS questions - LEFT JOIN `glpi_plugin_formcreator_answers` AS answers - ON `answers`.`plugin_formcreator_question_id` = `questions`.`id` - AND `plugin_formcreator_forms_answers_id` = ".$formanswer->getID()." - WHERE `questions`.`plugin_formcreator_sections_id` IN (".implode(', ', $tab_section).") - ORDER BY `questions`.`order` ASC"; - $res_questions = $DB->query($query_questions); - while ($question_line = $DB->fetch_assoc($res_questions)) { - $id = $question_line['id']; - if (!PluginFormcreatorFields::isVisible($question_line['id'], $answers_values)) { - $name = ''; - $value = ''; - } else { - $name = $question_line['name']; - $value = PluginFormcreatorFields::getValue($question_line, $question_line['answer']); - } - if (is_array($value)) { - if ($CFG_GLPI['use_rich_text']) { - $value = '
    ' . implode('
    ', $value); - } else { - $value = "\r\n" . implode("\r\n", $value); - } - } - - $content = str_replace('##question_' . $id . '##', $name, $content); - $content = str_replace('##answer_' . $id . '##', $value, $content); - } - } - - return $content; - } - private static function getDeleteImage($id) { global $CFG_GLPI; From 4550391d269c155700c34faec948be8e6dde50b9 Mon Sep 17 00:00:00 2001 From: btry Date: Thu, 24 Aug 2017 18:07:50 +0200 Subject: [PATCH 10/41] 2.6.0 filter categories if helpdesk, filter categories with depth and type criterias (#690) filter categories by typre (request / incident) or depth at question level --- inc/fields/dropdownfield.class.php | 58 +++++++++- inc/question.class.php | 101 ++++++++++++++--- inc/targetbase.class.php | 7 +- install/install.php | 5 + install/update_0.0_2.5.php | 170 ++++++++++++++--------------- install/update_2.5_2.6.php | 29 +++++ setup.php | 4 +- 7 files changed, 266 insertions(+), 108 deletions(-) create mode 100644 install/update_2.5_2.6.php diff --git a/inc/fields/dropdownfield.class.php b/inc/fields/dropdownfield.class.php index e0d25530f..f7e36bb42 100644 --- a/inc/fields/dropdownfield.class.php +++ b/inc/fields/dropdownfield.class.php @@ -7,7 +7,12 @@ public function displayField($canEdit = true) { if (!empty($this->fields['values'])) { $rand = mt_rand(); $required = $this->fields['required'] ? ' required' : ''; - $itemtype = $this->fields['values']; + $decodedValues = json_decode($this->fields['values'], JSON_OBJECT_AS_ARRAY); + if ($decodedValues === null) { + $itemtype = $this->fields['values']; + } else { + $itemtype = $decodedValues['itemtype']; + } $dparams = array('name' => 'formcreator_field_' . $this->fields['id'], 'value' => $this->getValue(), @@ -16,6 +21,24 @@ public function displayField($canEdit = true) { if ($itemtype == "User") { $dparams['right'] = 'all'; + } else if ($itemtype == "ITILCategory") { + $dparams['condition'] = '1'; + if (isset ($_SESSION['glpiactiveprofile']['interface']) + && $_SESSION['glpiactiveprofile']['interface'] == 'helpdesk') { + $dparams['condition'] .= " AND `is_helpdeskvisible` = '1'"; + } + switch ($decodedValues['show_ticket_categories']) { + case 'request': + $dparams['condition'] .= " AND `is_request` = '1'"; + break; + case 'incident': + $dparams['condition'] .= " AND `is_incident` = '1'"; + break; + } + if (isset($decodedValues['show_ticket_categories_depth']) + && $decodedValues['show_ticket_categories_depth'] > 0) { + $dparams['condition'] .= " AND `level` <= '" . $decodedValues['show_ticket_categories_depth'] . "'"; + } } $itemtype::dropdown($dparams); @@ -38,7 +61,12 @@ public function getAnswer() { if ($this->fields['values'] == 'User') { return getUserName($value); } else { - return Dropdown::getDropdownName(getTableForItemType($this->fields['values']), $value); + $decodedValues = json_decode($this->fields['values'], JSON_OBJECT_AS_ARRAY); + if (!isset($decodedValues['itemtype'])) { + return Dropdown::getDropdownName(getTableForItemType($this->fields['values']), $value); + } else { + return Dropdown::getDropdownName(getTableForItemType($decodedValues['itemtype']), $value); + } } } @@ -55,7 +83,31 @@ public function prepareQuestionInputForSave($input) { ERROR); return array(); } - $input['values'] = $input['dropdown_values']; + $allowedDropdownValues = []; + foreach (Dropdown::getStandardDropdownItemTypes() as $categoryOfTypes) { + $allowedDropdownValues = array_merge($allowedDropdownValues, array_keys($categoryOfTypes)); + } + if (!in_array($input['dropdown_values'], $allowedDropdownValues)) { + Session::addMessageAfterRedirect( + __('Invalid dropdown type:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return []; + } + $input['values'] = [ + 'itemtype' => $input['dropdown_values'], + ]; + if ($input['dropdown_values'] == 'ITILCategory') { + $input['values']['show_ticket_categories'] = $input['show_ticket_categories']; + if ($input['show_ticket_categories_depth'] != (int) $input['show_ticket_categories_depth']) { + $input['values']['show_ticket_categories_depth'] = 0; + } else { + $input['values']['show_ticket_categories_depth'] = $input['show_ticket_categories_depth']; + } + } + $input['values'] = json_encode($input['values']); + unset($input['show_ticket_categories']); + unset($input['show_ticket_categories_depth']); $input['default_values'] = isset($input['dropdown_default_value']) ? $input['dropdown_default_value'] : ''; } return $input; diff --git a/inc/question.class.php b/inc/question.class.php index 51d7544e2..dd7eff8f6 100644 --- a/inc/question.class.php +++ b/inc/question.class.php @@ -269,10 +269,23 @@ private function checkBeforeSave($input) { return []; } - $fieldTypes = PluginFormcreatorFields::getTypes(); - if (!in_array($input['fieldtype'], array_keys($fieldTypes))) { - Session::addMessageAfterRedirect(__('Unknown field type', 'formcreator'), false, ERROR); - return []; + // Values are required for GLPI dropdowns, dropdowns, multiple dropdowns, checkboxes, radios, LDAP + $itemtypes = array('select', 'multiselect', 'checkboxes', 'radios', 'ldap'); + if (in_array($input['fieldtype'], $itemtypes)) { + if (isset($input['values'])) { + if (empty($input['values'])) { + Session::addMessageAfterRedirect( + __('The field value is required:', 'formcreator') . ' ' . $input['name'], + false, + ERROR); + return array(); + } else { + $input['values'] = addslashes($input['values']); + } + } + if (isset($input['default_values'])) { + $input['default_values'] = addslashes($input['default_values']); + } } $fieldType = 'PluginFormcreator' . ucfirst($input['fieldtype']) . 'Field'; @@ -313,7 +326,10 @@ public function prepareInputForAdd($input) { // Decode (if already encoded) and encode strings to avoid problems with quotes foreach ($input as $key => $value) { - $input[$key] = plugin_formcreator_encode($value); + if ($input['fieldtype'] != 'dropdown' + || $input['fieldtype'] != 'dropdown' && $key != 'values') { + $input[$key] = plugin_formcreator_encode($value); + } } // generate a uniq id @@ -367,7 +383,10 @@ public function prepareInputForUpdate($input) { // Decode (if already encoded) and encode strings to avoid problems with quotes foreach ($input as $key => $value) { - $input[$key] = plugin_formcreator_encode($value); + if ($input['fieldtype'] != 'dropdown' + || $input['fieldtype'] != 'dropdown' && $key != 'values') { + $input[$key] = plugin_formcreator_encode($value); + } } if (!empty($input) @@ -630,11 +649,12 @@ public function showForm($ID, $options=array()) { echo ''; echo ''; echo '
    '; @@ -667,7 +687,7 @@ public function showForm($ID, $options=array()) { 'Group' => _n("Group", "Groups", 2), 'Entity' => _n("Entity", "Entities", 2), 'Profile' => _n("Profile", "Profiles", 2)) - );; + ); array_unshift($optgroup, '---'); Dropdown::showFromArray('glpi_objects', $optgroup, array( 'value' => $this->fields['values'], @@ -718,6 +738,39 @@ public function showForm($ID, $options=array()) { echo ''; echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + $ticketCategoriesOptions = [ + 'request' => __('Request categories', 'formcreator'), + 'incident' => __('Incident categories', 'formcreator'), + 'both' => __('Both', 'formcreator'), + ]; + dropdown::showFromArray('show_ticket_categories', $ticketCategoriesOptions, [ + 'rand' => $rand, + 'value' => $decodedValues['show_ticket_categories'] + ]); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + dropdown::showNumber('show_ticket_categories_depth', [ + 'rand' => $rand, + 'value' => $decodedValues['show_ticket_categories_depth'], + 'min' => 1, + 'max' => 16, + 'toadd' => [0 => __('No limit', 'formcreator')], + ]); + echo ''; + echo ''; + echo ''; echo ''; echo '