<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * This file contains the surveyprofield_checkbox
 *
 * @package   surveyprofield_checkbox
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace surveyprofield_checkbox;

defined('MOODLE_INTERNAL') || die();

use core_text;
use mod_surveypro\itembase;
use mod_surveypro\utility_item;

require_once($CFG->dirroot . '/mod/surveypro/field/checkbox/lib.php');

/**
 * Class to manage each aspect of the checkbox item
 *
 * @package   surveyprofield_checkbox
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class item extends itembase
{
    // Itembase properties.

    /**
     * @var string List of options in the form of "$value SURVEYPRO_VALUELABELSEPARATOR $label"
     */
    protected $options;

    /**
     * @var string Text label for the optional option "other" in the form of "$value SURVEYPRO_OTHERSEPARATOR $label"
     */
    protected $labelother;

    /**
     * @var int Defaultvalue for the item answer
     */
    protected $defaultvalue;

    /**
     * @var bool $noanswerdefault
     */
    protected $noanswerdefault;

    /**
     * @var string Format of the content once downloaded
     */
    protected $downloadformat;

    /**
     * @var int Mminimum number of checkboxes the user is forced to choose in his/her answer
     */
    protected $minimumrequired;

    /**
     * @var int Maximum number of checkboxes the user is forced to choose in his/her answer
     */
    protected $maximumrequired;

    /**
     * @var int Orientation of the list of bottons. Either: SURVEYPRO_VERTICAL or SURVEYPRO_HORIZONTAL
     */
    protected $adjustment;

    // Service variables.

    /**
     * @var bool Does this item use the child table surveypro(field|format)_plugin?
     */
    protected static $usesplugintable = true;

    /**
     * @var bool Can this item be parent?
     */
    protected static $canbeparent = true;

    /**
     * Class constructor.
     *
     * If itemid is provided, load the object (item + base + plugin) from database
     * If evaluateparentcontent is true, load the parentitem parentcontent property too
     *
     * @param \stdClass $cm
     * @param object $surveypro
     * @param int $itemid Optional item ID
     * @param bool $getparentcontent True to include $item->parentcontent (as decoded by the parent item) too, false otherwise
     */
    public function __construct($cm, $surveypro, $itemid, $getparentcontent) {
        parent::__construct($cm, $surveypro, $itemid, $getparentcontent);

        // List of properties set to static values.
        $this->type = SURVEYPRO_TYPEFIELD;
        $this->plugin = 'checkbox';

        // Override the list of fields using format, whether needed.
        // Nothing to override, here.

        // Other element specific properties.
        // No properties here.

        // Override properties depending from $surveypro settings.
        // No properties here.

        // List of fields of the base form I do not want to have in the item definition.
        // Each (field|format) plugin receive a list of fields (quite) common to each (field|format) plugin.
        // This is the list of the elements of the itembase form fields that this (field|format) plugin does not use.
        // Empty list.

        if (!empty($itemid)) {
            $this->item_load($itemid, $getparentcontent);
        }
    }

    /**
     * Item load.
     *
     * @param int $itemid
     * @param bool $getparentcontent True to include $item->parentcontent (as decoded by the parent item) too, false otherwise
     * @return void
     */
    public function item_load($itemid, $getparentcontent) {
        parent::item_load($itemid, $getparentcontent);

        // Multilang load support for builtin surveypro.
        // Whether executed, the 'content' field is ALWAYS handled.
        $this->item_builtin_string_load_support();

        $this->item_custom_fields_to_form();
    }

    /**
     * Item save.
     *
     * @param object $record
     * @return void
     */
    public function item_save($record) {
        // Set properties at plugin level and then continue to base level.

        // Drop empty rows and trim trailing spaces from each textarea field.
        $fieldlist = ['options', 'defaultvalue'];
        $this->item_clean_textarea_fields($record, $fieldlist);

        // Set custom fields values as defined by this specific plugin.
        $this->add_plugin_properties_to_record($record);

        // Do parent item saving stuff here (mod_surveypro_itembase::item_save($record))).
        return parent::item_save($record);
    }

    /**
     * Item add mandatory plugin fields
     * Copy mandatory fields to $record
     *
     * @param \stdClass $record
     * @return void
     */
    public function item_add_fields_default_to_child_table(&$record) {
        $record->options = "first\nsecond";
        $record->noanswerdefault = 0;
        // $record->labelother
        // $record->defaultvalue
        // $record->noanswerdefault
        $record->downloadformat = SURVEYPRO_ITEMRETURNSLABELS;
        $record->minimumrequired = 0;
        $record->adjustment = 0;
    }

    /**
     * Traslate values from the mform of this item to values for corresponding properties.
     *
     * @param object $record
     * @return void
     */
    public function add_plugin_properties_to_record($record) {
        // 1. Special management for composite fields.
        // Nothing to do: they don't exist in this plugin.

        // 2. Override few values.
        // Nothing to do: no need to overwrite variables.

        // 3. Set values corresponding to checkboxes.
        // Take care: 'required', 'hideinstructions' were already considered in get_common_settings.
        $checkboxes = ['noanswerdefault'];
        foreach ($checkboxes as $checkbox) {
            $record->{$checkbox} = (isset($record->{$checkbox})) ? 1 : 0;
        }

        // 4. Other.
    }

    /**
     * Prepare values for the mform of this item.
     *
     * @return void
     */
    public function item_custom_fields_to_form() {
        // 1. Special management for composite fields.
        // Nothing to do: they don't exist in this plugin.
    }

    /**
     * Make the list of constraints the child has to respect in order to create a valid relation
     *
     * @return list of contraints of the plugin (as parent) in text format
     */
    public function item_list_constraints() {
        $labelsep = get_string('labelsep', 'langconfig'); // Separator usually is ': '.
        $constraints = [];

        $values = $this->get_textarea_content(SURVEYPRO_VALUES, 'options');
        $optionstr = get_string('option', 'surveyprofield_checkbox');
        foreach ($values as $value) {
            $constraints[] = $optionstr . $labelsep . $value;
        }
        if (!empty($this->labelother)) {
            $labelotherstr = get_string('labelother', 'surveyprofield_checkbox');
            $allowedstr = get_string('allowed', 'surveyprofield_checkbox');
            $constraints[] = $labelotherstr . $labelsep . $allowedstr;
        }

        return implode('<br>', $constraints);
    }

    // MARK set.

    /**
     * Set options.
     *
     * @param string $options
     * @return void
     */
    public function set_options($options): void {
        $this->options = $options;
    }

    /**
     * Set labelother.
     *
     * @param string $labelother
     * @return void
     */
    public function set_labelother($labelother) {
        $this->labelother = $labelother;
    }

    /**
     * Set defaultvalue.
     *
     * @param string $defaultvalue
     * @return void
     */
    public function set_defaultvalue($defaultvalue) {
        $this->defaultvalue = $defaultvalue;
    }

    /**
     * Set noanswerdefault.
     *
     * @param string $noanswerdefault
     * @return void
     */
    public function set_noanswerdefault($noanswerdefault) {
        $this->noanswerdefault = $noanswerdefault;
    }

    /**
     * Set downloadformat.
     *
     * @param string $downloadformat
     * @return void
     */
    public function set_downloadformat($downloadformat) {
        $this->downloadformat = $downloadformat;
    }

    /**
     * Set minimumrequired.
     *
     * @param string $minimumrequired
     * @return void
     */
    public function set_minimumrequired($minimumrequired) {
        $this->minimumrequired = $minimumrequired;
    }

    /**
     * Set maximumrequired.
     *
     * @param string $maximumrequired
     * @return void
     */
    public function set_maximumrequired($maximumrequired) {
        $this->maximumrequired = $maximumrequired;
    }

    /**
     * Set adjustment.
     *
     * @param string $adjustment
     * @return void
     */
    public function set_adjustment($adjustment) {
        $this->adjustment = $adjustment;
    }

    // MARK get.

    /**
     * Get options.
     *
     * @return $this->options
     */
    public function get_options() {
        return $this->options;
    }

    /**
     * Get labelother.
     *
     * @return $this->labelother
     */
    public function get_labelother() {
        return $this->labelother;
    }

    /**
     * Get defaultvalue.
     *
     * @return $this->defaultvalue
     */
    public function get_defaultvalue() {
        return $this->defaultvalue;
    }

    /**
     * Get noanswerdefault.
     *
     * @return $this->noanswerdefault
     */
    public function get_noanswerdefault() {
        return $this->noanswerdefault;
    }

    /**
     * Get downloadformat.
     *
     * @return $this->downloadformat
     */
    public function get_downloadformat() {
        return $this->downloadformat;
    }

    /**
     * Get minimumrequired.
     *
     * @return $this->minimumrequired
     */
    public function get_minimumrequired() {
        return $this->minimumrequired;
    }

    /**
     * Get maximumrequired.
     *
     * @return $this->maximumrequired
     */
    public function get_maximumrequired() {
        return $this->maximumrequired;
    }

    /**
     * Get adjustment.
     *
     * @return $this->adjustment
     */
    public function get_adjustment() {
        return $this->adjustment;
    }

    /**
     * Get the content of the downloadformats menu of the item setup form.
     *
     * @return array of downloadformats
     */
    public function get_downloadformats() {
        $options = [];

        $options[SURVEYPRO_ITEMSRETURNSVALUES] = get_string('returnvalues', 'surveyprofield_checkbox');
        $options[SURVEYPRO_ITEMRETURNSLABELS] = get_string('returnlabels', 'surveyprofield_checkbox');
        $options[SURVEYPRO_ITEMRETURNSPOSITION] = get_string('returnposition', 'surveyprofield_checkbox');

        return $options;
    }

    /**
     * Divide $this->labelother in $value and $label.
     *
     * @return $value
     * @return $label
     */
    protected function get_other() {
        if (preg_match('~^(.*)' . SURVEYPRO_OTHERSEPARATOR . '(.*)$~', $this->labelother, $match)) {
            $label = trim($match[1]);
            $value = trim($match[2]);
        } else {
            $label = trim($this->labelother);
            $value = '';
        }

        return [$value, $label];
    }

    /**
     * Get the format recognized (without any really good reason) as friendly.
     *
     * @return the friendly format
     */
    public function get_friendlyformat() {
        return SURVEYPRO_ITEMRETURNSLABELS;
    }

    /**
     * Prepare presets for itemsetuprform with the help of the parent class too.
     *
     * @return array $data
     */
    public function get_plugin_presets() {
        $pluginproperties = [
            'options', 'labelother', 'defaultvalue', 'noanswerdefault',
            'downloadformat', 'minimumrequired', 'maximumrequired', 'adjustment',
        ];
        $data = $this->get_base_presets($pluginproperties);

        return $data;
    }

    /**
     * Make the list of the fields using multilang
     *
     * @param bool $includemetafields
     * @return array of fields
     */
    public function get_multilang_fields($includemetafields = true) {
        $fieldlist['surveypro_item'] = $this->get_base_multilang_fields($includemetafields);
        $fieldlist['surveyprofield_checkbox'] = ['options', 'labelother', 'defaultvalue'];

        return $fieldlist;
    }

    /**
     * Get if the plugin uses the position of options to save user answers.
     *
     * @return bool The plugin uses the position of options to save user answers.
     */
    public function get_uses_positional_answer() {
        return true;
    }

    /**
     * Return the xml schema for surveypro_<<plugin>> table.
     *
     * @return string $schema
     */
    public static function get_plugin_schema() {
        $schema = <<<EOS
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:element name="surveyprofield_checkbox">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="options" type="xs:string"/>
                <xs:element name="labelother" type="xs:string" minOccurs="0"/>
                <xs:element name="defaultvalue" type="xs:string" minOccurs="0"/>
                <xs:element name="noanswerdefault" type="xs:int" minOccurs="0"/>
                <xs:element name="downloadformat" type="xs:int" minOccurs="0"/>
                <xs:element name="minimumrequired" type="xs:int" minOccurs="0"/>
                <xs:element name="maximumrequired" type="xs:int" minOccurs="0"/>
                <xs:element name="adjustment" type="xs:int" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>
EOS;

        return $schema;
    }

    // MARK parent.

    /**
     * Translate the parentcontent of the child item to the corresponding parentvalue.
     *
     * @param string $childparentcontent
     * return string childparentvalue
     */
    public function parent_encode_child_parentcontent($childparentcontent) {
        $utilityitemman = new utility_item($this->cm, $this->surveypro);
        $parentcontents = array_unique($utilityitemman->multilinetext_to_array($childparentcontent));
        $values = $this->get_textarea_content(SURVEYPRO_VALUES, 'options');

        $childparentvalue = array_fill(0, count($values), 0);
        $labels = [];
        foreach ($parentcontents as $parentcontent) {
            $key = array_search($parentcontent, $values);
            if ($key !== false) {
                $childparentvalue[$key] = 1;
            } else {
                // Only garbage, but user wrote it.
                $labels[] = $parentcontent;
            }
        }
        if (!empty($labels)) {
            $childparentvalue[] = '>';
            $childparentvalue = array_merge($childparentvalue, $labels);
        }

        return implode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $childparentvalue);
    }

    /**
     * I can not make ANY assumption about $childparentvalue because of the following explanation:
     * At child save time, I encode its $parentcontent to $parentvalue.
     * The encoding is done through a parent method according to parent values.
     * Once the child is saved, I can return to parent and I can change it as much as I want.
     * For instance by changing the number and the content of its options
     * At parent save time, the child parentvalue is rewritten
     * -> but it may result in a too short or too long list of keys
     * -> or with a wrong number of unrecognized keys so I need to..
     * ...implement all possible checks to avoid crashes/malfunctions during code execution
     *
     * this method decodes parentindex to parentcontent
     *
     * @param string $childparentvalue
     * return string $childparentcontent
     */
    public function parent_decode_child_parentvalue($childparentvalue) {
        $values = $this->get_textarea_content(SURVEYPRO_VALUES, 'options');
        $parentvalues = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $childparentvalue);
        $actualcount = count($parentvalues);

        $childparentcontent = [];
        $key = array_search('>', $parentvalues);
        if ($key !== false) {
            for ($i = 0; $i < $key; $i++) {
                if ($parentvalues[$i] == '1') {
                    if (isset($values[$i])) {
                        $childparentcontent[] = $values[$i];
                    } else {
                        $childparentcontent[] = 1;
                    }
                }
            }

            $key++;
            // Only garbage after the first label, but user wrote it.
            for ($i = $key; $i < $actualcount; $i++) {
                $childparentcontent[] = $parentvalues[$i];
            }
        } else {
            foreach ($parentvalues as $k => $parentvalue) {
                if ($parentvalue == '1') {
                    if (isset($values[$k])) {
                        $childparentcontent[] = $values[$k];
                    } else {
                        $childparentcontent[] = $k;
                    }
                }
            }
        }

        return implode("\n", $childparentcontent);
    }

    /**
     * This method, starting from child parentvalue (index/es), declares if the child could be included in the surveypro.
     *
     * @param string $childparentvalue
     * @return status of child relation
     *     0 = it will never match
     *     1 = OK
     *     2 = $childparentvalue is malformed
     */
    public function parent_validate_child_constraints($childparentvalue) {
        // See parent method for explanation.

        $values = $this->get_textarea_content(SURVEYPRO_VALUES, 'options');
        $expectedcount = count($values);
        $parentvalues = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $childparentvalue);
        $actualcount = count($parentvalues);

        $key = array_search('>', $parentvalues);
        if ($key !== false) {
            $condition = empty($this->labelother) ? empty($parentvalues[$actualcount - 1]) : true;
            $condition = $condition && ($actualcount == ($key + 2)); // Only one label is allowed.
            $condition = $condition && ($expectedcount == $key); // Only $expectedcount checkboxes are allowed.
            $return = ($condition) ? SURVEYPRO_CONDITIONOK : SURVEYPRO_CONDITIONMALFORMED;
        } else {
            $return = ($actualcount == $expectedcount) ? SURVEYPRO_CONDITIONOK : SURVEYPRO_CONDITIONMALFORMED;
        }

        return ($return);
    }

    // MARK userform.

    /**
     * Define the mform element for the userform and the searchform.
     *
     * @param \moodleform $mform
     * @param int $searchformelementscount // 0 means: I am not drawing this element in a search form.
     * @param bool $readonly
     * @return void
     */
    public function userform_mform_element($mform, $searchformelementscount, $readonly) {
        if ($this->position == SURVEYPRO_POSITIONLEFT) {
            $elementlabel = $this->get_contentwithnumber();
        } else {
            $elementlabel = '&nbsp;';
        }

        $attributes = [];
        $elementgroup = [];
        $class = ['class' => 'indent-' . $this->indent];
        $baseid = 'id_field_checkbox_' . $this->sortindex;
        $basename = $this->itemname;

        $labels = $this->get_textarea_content(SURVEYPRO_LABELS, 'options');
        $utilityitemman = new utility_item($this->cm, $this->surveypro);
        $defaults = $utilityitemman->multilinetext_to_array($this->defaultvalue);

        $attributes['group'] = 1;

        $options = ['0', '1'];
        $i = 0;
        // Add each checkbox and set its defaults.
        foreach ($labels as $label) {
            $itemname = $basename . '_' . $i;
            $attributes['id'] = $baseid . '_' . $i;
            $elementgroup[] = $mform->createElement('advcheckbox', $itemname, '', $label, $attributes, $options);

            if ((!$searchformelementscount) && (!$readonly)) {
                if (in_array($label, $defaults)) {
                    $mform->setDefault($itemname, '1');
                }
            }
            $i++;
        }
        // Add "other" field, if required, and set its defaults.
        if (!empty($this->labelother)) {
            [$othervalue, $otherlabel] = $this->get_other();

            $attributes['id'] = $baseid . '_other';
            $elementgroup[] = $mform->createElement('advcheckbox', $basename . '_other', '', $otherlabel, $attributes, $options);

            unset($attributes['group']);
            $attributes['id'] = $baseid . '_text';
            $elementgroup[] = $mform->createElement('text', $basename . '_text', '', $attributes);
            $mform->setType($basename . '_text', PARAM_RAW);

            if ((!$searchformelementscount) && (!$readonly)) {
                $mform->setDefault($basename . '_text', $othervalue);
                if (in_array($otherlabel, $defaults)) {
                    $mform->setDefault($basename . '_other', '1');
                }
            }
            $mform->disabledIf($basename . '_text', $basename . '_other', 'notchecked');
        }

        if (!$this->required) {
            $attributes['id'] = $baseid . '_noanswer';
            $noanswerstr = get_string('noanswer', 'surveypro');
            $options = ['0', '1'];
            $elementgroup[] = $mform->createElement('advcheckbox', $basename . '_noanswer', '', $noanswerstr, $attributes, $options);
            if (!$searchformelementscount) {
                if (!empty($this->noanswerdefault)) {
                    $mform->setDefault($basename . '_noanswer', '1');
                }
            }
        }

        if ($this->adjustment == SURVEYPRO_VERTICAL) {
            $separator = $this->userform_get_separator();
        } else { // SURVEYPRO_HORIZONTAL.
            $separator = ' ';
        }
        $mform->addGroup($elementgroup, $basename . '_group', $elementlabel, $separator, false, $class);

        if (!$this->required) {
            $mform->disabledIf($basename . '_group', $basename . '_noanswer', 'checked');
        }

        if ($searchformelementscount > 1) {
            $this->item_add_color_unifier($mform);

            $elementgroup = [];
            $itemname = $basename . '_ignoreme';
            $attributes['id'] = $baseid . '_ignoreme';
            $elementgroup[] = $mform->createElement('checkbox', $itemname, '', get_string('star', 'mod_surveypro'), $attributes);
            $mform->addGroup($elementgroup, $itemname . '_group', '', '', false, $class);
            $mform->setDefault($itemname, '1');

            $mform->disabledIf($basename . '_group', $itemname, 'checked');
        }

        if ($this->required) {
            if (!$searchformelementscount) {
                // Even if the item is required I CAN NOT ADD ANY RULE HERE because:
                // I do not want JS form validation if the page is submitted through the "previous" button.
                // I do not want JS field validation even if this item is required BUT disabled. See: MDL-34815.
                // Because of this, I simply add a dummy star to the item and the footer note about mandatory fields.
                $starplace = ($this->position == SURVEYPRO_POSITIONTOP) ? $basename . '_extrarow_group' : $basename . '_group';
                $mform->_required[] = $starplace;
            }
        }
    }

    /**
     * Define the separator needed to create the userform element.
     *
     * There is an issue I do not raise up because it is definitely not relevant.
     * Checkbox type items with the "Other" option in addition to a single option
     * cannot be shown vertically unless I miss the alignment between
     * the "Other" option and the corresponding text field.
     *
     * @return array $separator
     */
    public function userform_get_separator(): array {
        $labels = $this->get_textarea_content(SURVEYPRO_LABELS, 'options');
        $optioncount = count($labels);
        $addother = !empty($this->labelother);
        $mandatory = $this->required;

        $separator = [];

        // Options.
        for ($i = 1; $i < $optioncount; $i++) {
            $separator[] = '<br>'; // From "Option i" to "Option i+1".
        }

        // Other and no answer.
        if ($addother) {
            $separator[] = '<br>'; // From "Option N" to "Other".
            $separator[] = ' '; // From "Other" to the corresponding "text field".
        }

        if (!$mandatory) {
            $separator[] = '<br>'; // From "Option N" or "text field" to "No answer".
        }

        if (count($separator) > 1) {
            array_shift($separator); // Bloody workaround: drop first break.
        }

        return $separator;
    }

    /**
     * Perform userform and searchform data validation.
     *
     * @param array $data
     * @param array $errors
     * @param bool $searchform
     * @return void
     */
    public function userform_mform_validation($data, &$errors, $searchform) {
        if ($searchform) {
            return $errors;
        }
        if (isset($data[$this->itemname . '_noanswer']) && ($data[$this->itemname . '_noanswer'] == 1)) {
            return $errors; // Nothing to validate.
        }

        $errorkey = $this->itemname . '_group';

        if (!empty($this->labelother)) {
            if (($data[$this->itemname . '_other']) && empty($data[$this->itemname . '_text'])) {
                $errors[$errorkey] = get_string('uerr_missingothertext', 'surveyprofield_checkbox');
                return $errors;
            }
        }

        // Begin of: get answercount.
        $labels = $this->get_textarea_content(SURVEYPRO_LABELS, 'options');

        $answercount = 0;
        foreach ($labels as $k => $unused) {
            $itemname = $this->itemname . '_' . $k;
            if ($data[$itemname]) { // They are advanced checkbox so I am sure the answer always exist.
                $answercount++;
            }
        }

        if (!empty($this->labelother)) {
            if (($data[$this->itemname . '_other']) && (!empty($data[$this->itemname . '_text']))) {
                $answercount++;
            }
        }
        // End of: get answercount.
        // I don't care if this element is required or not.
        // If the user provides an answer, it has to be compliant with the field validation rules.
        if ($answercount < $this->minimumrequired) {
            if ($this->minimumrequired == 1) {
                $errors[$errorkey] = get_string('uerr_lowerthanminimum_one', 'surveyprofield_checkbox');
            } else {
                $errors[$errorkey] = get_string('uerr_lowerthanminimum_more', 'surveyprofield_checkbox', $this->minimumrequired);
            }
        }
        if (!empty($this->maximumrequired) && ($answercount > $this->maximumrequired)) {
            if ($this->maximumrequired == 1) {
                $errors[$errorkey] = get_string('uerr_greaterthanmaximum_one', 'surveyprofield_checkbox');
            } else {
                $errors[$errorkey] = get_string('uerr_greaterthanmaximum_more', 'surveyprofield_checkbox', $this->maximumrequired);
            }
        }

        return $errors;
    }

    /**
     * From childparentvalue defines syntax for disabledIf.
     *
     * @param string $childparentvalue
     * @return array
     */
    public function userform_get_parent_disabilitation_info($childparentvalue) {
        $disabilitationinfo = [];

        $parentvalues = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $childparentvalue); // For instance: 1;1;0;.

        $indexsubset = [];
        $labelsubset = [];
        $key = array_search('>', $parentvalues);
        if ($key !== false) {
            $indexsubset = array_slice($parentvalues, 0, $key);
            $labelsubset = array_slice($parentvalues, $key + 1);
        } else {
            $indexsubset = $parentvalues;
        }

        foreach ($indexsubset as $k => $unused) {
            $mformelementinfo = new \stdClass();
            $mformelementinfo->parentname = $this->itemname . '_' . $k;
            if ($indexsubset[$k] == 1) {
                $mformelementinfo->content = 'notchecked';
            } else {
                $mformelementinfo->content = 'checked';
            }
            $disabilitationinfo[] = $mformelementinfo;
        }
        // If this item foresees the "No answer" checkbox, provide a directive for it too.
        if (!$this->required) {
            $mformelementinfo = new \stdClass();
            $mformelementinfo->parentname = $this->itemname . '_noanswer';
            $mformelementinfo->content = 'checked';

            $disabilitationinfo[] = $mformelementinfo;
        }

        if ($labelsubset) {
            foreach ($labelsubset as $k => $label) {
                $mformelementinfo = new \stdClass();
                $mformelementinfo->parentname = $this->itemname . '_other';
                $mformelementinfo->content = 'notchecked';
                $disabilitationinfo[] = $mformelementinfo;

                $mformelementinfo = new \stdClass();
                $mformelementinfo->parentname = $this->itemname . '_text';
                $mformelementinfo->operator = 'neq';
                $mformelementinfo->content = $label;
                $disabilitationinfo[] = $mformelementinfo;
            }
        } else {
            // Even if no labels were provided
            // I have to add one more $disabilitationinfo if $this->other is not empty.
            if (!empty($this->labelother)) {
                $mformelementinfo = new \stdClass();
                $mformelementinfo->parentname = $this->itemname . '_other';
                $mformelementinfo->content = 'checked';
                $disabilitationinfo[] = $mformelementinfo;
            }
        }

        return $disabilitationinfo;
    }

    /**
     * Dynamically decide if my child (living in my same page) is allowed or not.
     *
     * This method is called if (and only if) parent item and child item live in the same form page.
     * This method has two purposes:
     * - stop userpageform item validation
     * - drop unexpected returned values from $userpageform->formdata
     *
     * As parentitem I declare whether my child item is allowed to return a value (is enabled) or is not (is disabled).
     *
     * @param string $childparentvalue
     * @param array $data
     * @return boolean: true: if the item is welcome; false: if the item must be dropped out
     */
    public function userform_is_child_allowed_dynamic($childparentvalue, $data) {
        // I need to verify (checkbox per checkbox) if they hold the same value the user entered.
        $parentvalues = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $childparentvalue); // For instance: 2;3;shark.

        // Build the local $parentconstrain variable that will be used to evaluate the status.
        $parentconstrain = [];
        if (!empty($this->labelother)) {
            $parentconstrain[$this->itemname . '_other'] = '0';
        }

        $nextisother = false;
        foreach ($parentvalues as $k => $expectedvalue) {
            if ($expectedvalue == '>') {
                $nextisother = true;
                continue;
            }

            if (!$nextisother) {
                $parentconstrain[$this->itemname . '_' . $k] = $expectedvalue;
            } else {
                $parentconstrain[$this->itemname . '_other'] = '1';
                $parentconstrain[$this->itemname . '_text'] = $expectedvalue;
            }
        }
        if (empty($this->mandatory)) {
            $parentconstrain[$this->itemname . '_noanswer'] = '0';
        }
        // End of: Build the local $parentconstrain variable that will be used to evaluate the status.

        $status = true;
        foreach ($parentconstrain as $k => $expectedvalue) {
            $status = $status && ($data[$k] == $expectedvalue);
        }

        return $status;
    }

    /**
     * Prepare the string with the filling instruction.
     *
     * @return string $fillinginstruction
     */
    public function userform_get_filling_instructions() {

        $arrayinstruction = [];

        if (!empty($this->minimumrequired)) {
            if ($this->minimumrequired == 1) {
                $arrayinstruction[] = get_string('restrictions_minimumrequired_one', 'surveyprofield_checkbox');
            } else {
                $a = $this->minimumrequired;
                $arrayinstruction[] = get_string('restrictions_minimumrequired_more', 'surveyprofield_checkbox', $a);
            }
        }
        if (!empty($this->maximumrequired)) {
            if ($this->maximumrequired == 1) {
                $arrayinstruction[] = get_string('restrictions_maximumrequired_one', 'surveyprofield_checkbox');
            } else {
                $a = $this->maximumrequired;
                $arrayinstruction[] = get_string('restrictions_maximumrequired_more', 'surveyprofield_checkbox', $a);
            }
        }

        if (count($arrayinstruction)) {
            $fillinginstruction = implode('; ', $arrayinstruction);
        } else {
            $fillinginstruction = '';
        }

        return $fillinginstruction;
    }

    /**
     * Starting from the info set by the user in the form
     * this method calculates what to save in the db
     * or what to return for the search form.
     * I don't set $olduseranswer->contentformat in order to accept the default db value.
     *
     * @param array $answer
     * @param object $olduseranswer
     * @param bool $searchform
     * @return void
     */
    public function userform_prepare_user_answer($answer, &$olduseranswer, $searchform) {
        if (isset($answer['noanswer']) && ($answer['noanswer'] == 1)) { // This is correct for input and search form both.
            $olduseranswer->content = SURVEYPRO_NOANSWERVALUE;
            return;
        }

        if (isset($answer['ignoreme']) && ($answer['ignoreme'] == 1)) { // It is an advcheckbox.
            $olduseranswer->content = null;
            return;
        }

        $return = $answer;
        if (!empty($this->labelother)) {
            $return[] = (isset($answer['other']) && ($answer['other'] == 1)) ? $answer['text'] : '';
            unset($return['other']);
            unset($return['text']);
        }
        if (!$this->required) {
            unset($return['noanswer']);
        }
        $olduseranswer->content = implode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $return);
    }

    /**
     * This method is called from get_prefill_data (in formbase.class.php) to set $prefill at user form display time.
     *
     * @param object $fromdb
     * @return associative array with disaggregate element values
     */
    public function userform_get_prefill($fromdb) {
        $prefill = [];

        if (!$fromdb) { // Param $fromdb may be boolean false for not existing data.
            return $prefill;
        }

        if (isset($fromdb->content)) {
            if ($fromdb->content == SURVEYPRO_NOANSWERVALUE) {
                $prefill[$this->itemname . '_noanswer'] = 1;
                return $prefill;
            }

            $answers = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $fromdb->content);

            // Here $answers is an array like: [1,1,0,0,'dummytext'].
            foreach ($answers as $k => $checkboxvalue) {
                $itemname = $this->itemname . '_' . $k;
                $prefill[$itemname] = $checkboxvalue;
            }
            if (!empty($this->labelother)) {
                // Delete last item of $prefill.
                unset($prefill[$itemname]);

                // Add last element of the $prefill.
                $lastanswer = end($answers);

                if (core_text::strlen($lastanswer)) {
                    $prefill[$this->itemname . '_other'] = 1;
                    $prefill[$this->itemname . '_text'] = $lastanswer;
                } else {
                    $prefill[$this->itemname . '_other'] = 0;
                    if ($fromdb->verified) { // If the answer was validated.
                        $prefill[$this->itemname . '_text'] = '';
                    } else {
                        [$othervalue, $otherlabel] = $this->get_other();
                        $prefill[$this->itemname . '_text'] = $othervalue;
                    }
                }
            }
        }

        // If the "No answer" checkbox is part of the element GUI...
        if ($this->noanswerdefault) {
            $prefill[$this->itemname . '_noanswer'] = 0;
        }

        return $prefill;
    }

    /**
     * Starting from the info stored into $answer, this function returns the corresponding content for the export file.
     *
     * @param object $answer
     * @param string $format
     * @return string - the string for the export file
     */
    public function userform_db_to_export($answer, $format = '') {
        // The content of the provided answer.
        $content = $answer->content;

        // Trigger 'answernotsubmitted' and 'answerisnoanswer'.
        $quickresponse = self::userform_standardcontent_to_string($content);
        if (isset($quickresponse)) { // Parent method provided the response.
            return $quickresponse;
        }

        // Format.
        if ($format == SURVEYPRO_FRIENDLYFORMAT) {
            $format = $this->get_friendlyformat();
        }
        if (empty($format)) {
            $format = $this->downloadformat;
        }

        // Output.
        // Here $answers is an array like: [1,1,0,0,'dummytext'].
        switch ($format) {
            case SURVEYPRO_ITEMSRETURNSVALUES:
                $answers = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $content);
                $output = [];
                $values = $this->get_textarea_content(SURVEYPRO_VALUES, 'options');

                foreach ($values as $k => $value) {
                    if ($answers[$k] == 1) {
                        $output[] = $value;
                    }
                }
                if (!empty($this->labelother)) {
                    $value = end($answers);
                    if (!empty($value)) {
                        $output[] = $value; // Last element of the array $answers.
                    }
                }

                if (!empty($output)) {
                    $return = implode(SURVEYPRO_OUTPUTMULTICONTENTSEPARATOR, $output);
                } else {
                    $return = get_string('emptyanswer', 'mod_surveypro');
                }
                break;
            case SURVEYPRO_ITEMRETURNSLABELS:
                $answers = explode(SURVEYPRO_DBMULTICONTENTSEPARATOR, $content);
                $output = [];
                $values = $this->get_textarea_content(SURVEYPRO_LABELS, 'options');

                foreach ($values as $k => $value) {
                    if ($answers[$k] == 1) {
                        $output[] = $value;
                    }
                }
                if (!empty($this->labelother)) {
                    $value = end($answers);
                    if (!empty($value)) {
                        $output[] = $value; // Last element of the array $answers.
                    }
                }

                if (!empty($output)) {
                    $return = implode(SURVEYPRO_OUTPUTMULTICONTENTSEPARATOR, $output);
                } else {
                    $return = get_string('emptyanswer', 'mod_surveypro');
                }
                break;
            case SURVEYPRO_ITEMRETURNSPOSITION:
                // Here I will ALWAYS HAVE 0/1 so each separator is welcome, even ','.
                // I do not like pass the idea that ',' can be a separator so, I do not use it.
                $return = $content;
                break;
            default:
                $message = 'Unexpected $format = ' . $format;
                debugging('Error at line ' . __LINE__ . ' of ' . __FILE__ . '. ' . $message, DEBUG_DEVELOPER);
        }

        return $return;
    }

    /**
     * Returns an array with the names of the mform element added using $mform->addElement or $mform->addGroup.
     *
     * @return array
     */
    public function userform_get_root_elements_name() {
        return [$this->itemname . '_group'];
    }
}
