<?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_datetime
 *
 * @package   surveyprofield_datetime
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace surveyprofield_datetime;

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

use mod_surveypro\itembase;
use mod_surveypro\utility_dates;
use mod_surveypro\utility_item;

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

/**
 * Class to manage each aspect of the datetime item
 *
 * @package   surveyprofield_datetime
 * @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 int Step for minutes drop down menu
     */
    protected $step;

    /**
     * @var string Value of the default setting (invite, custom...)
     */
    protected $defaultoption;

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

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

    /**
     * @var int Lowerbound for the datetime in unixtime
     */
    protected $lowerbound;

    /**
     * @var int Upperbound for the datetime in unixtime
     */
    protected $upperbound;

    // Service variables.

    /**
     * @var int Year of the defaultvalue for the datetime in unixtime
     */
    protected $defaultvalueyear;

    /**
     * @var int Month of the defaultvalue for the datetime in unixtime
     */
    protected $defaultvaluemonth;

    /**
     * @var int Day of the defaultvalue for the datetime in unixtime
     */
    protected $defaultvalueday;

    /**
     * @var int Hour of the defaultvalue for the datetime in unixtime
     */
    protected $defaultvaluehour;

    /**
     * @var int Minute of the defaultvalue for the datetime in unixtime
     */
    protected $defaultvalueminute;

    /**
     * @var int Year of the lowerbound for the datetime in unixtime
     */
    protected $lowerboundyear;

    /**
     * @var int Month of the lowerbound for the datetime in unixtime
     */
    protected $lowerboundmonth;

    /**
     * @var int Day of the lowerbound for the datetime in unixtime
     */
    protected $lowerboundday;

    /**
     * @var int Hour of the lowerbound for the datetime in unixtime
     */
    protected $lowerboundhour;

    /**
     * @var int Minute of the lowerbound for the datetime in unixtime
     */
    protected $lowerboundminute;

    /**
     * @var int Year of the upperbound for the datetime in unixtime
     */
    protected $upperboundyear;

    /**
     * @var int Month of the upperbound for the datetime in unixtime
     */
    protected $upperboundmonth;

    /**
     * @var int Day of the upperbound for the datetime in unixtime
     */
    protected $upperboundday;

    /**
     * @var int Hour of the upperbound for the datetime in unixtime
     */
    protected $upperboundhour;

    /**
     * @var int Minute of the upperbound for the datetime in unixtime
     */
    protected $upperboundminute;

    /**
     * @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 = false;

    /**
     * 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 = 'datetime';

        // 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.
        $this->lowerbound = $this->item_datetime_to_unix_time($this->surveypro->startyear, 1, 1, 0, 0);
        $this->upperbound = $this->item_datetime_to_unix_time($this->surveypro->stopyear, 12, 31, 23, 59);
        $this->defaultvalue = $this->lowerbound;

        // 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.

        // 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->step = 1;
        $record->defaultoption = SURVEYPRO_INVITEDEFAULT;
        $record->defaultvalue = 0;
        $record->downloadformat = 'strftime01';
        $record->lowerbound = 0;
        $record->upperbound = 1609459140;
    }

    /**
     * Change $year, $month, $day, $hour, $minute to unixtime.
     *
     * @param int $year
     * @param int $month
     * @param int $day
     * @param int $hour
     * @param int $minute
     * @return int unixtime
     */
    public function item_datetime_to_unix_time($year, $month, $day, $hour, $minute) {
        $unixtime = gmmktime($hour, $minute, 0, $month, $day, $year);

        return $unixtime;
    }

    /**
     * Verify the validity of contents of the record
     * for instance: datetime not greater than maximum datetime
     *
     * @param \stdClass $record
     * @return void
     */
    public function item_force_coherence($record) {
        if (isset($record->defaultvalue)) {
            $mindatetime = $this->item_datetime_to_unix_time($this->surveypro->startyear, 1, 1, 0, 0);
            if ($record->defaultvalue < $mindatetime) {
                $record->defaultvalue = $mindatetime;
            }
            $maxdatetime = $this->item_datetime_to_unix_time($this->surveypro->stopyear, 12, 31, 23, 59);
            if ($record->defaultvalue > $maxdatetime) {
                $record->defaultvalue = $maxdatetime;
            }
        }
    }

    /**
     * Prepare values for the mform of this item.
     *
     * translates the datetime class property $fieldlist in $field.'year' and $field.'month' and so forth
     *
     * @return void
     */
    public function item_custom_fields_to_form() {
        // 1. Special management for composite fields.
        $fieldlist = $this->get_composite_fields();
        foreach ($fieldlist as $field) {
            $datetimearray = $this->item_split_unix_time($this->{$field});
            $this->{$field . 'year'} = $datetimearray['year'];
            $this->{$field . 'month'} = $datetimearray['mon'];
            $this->{$field . 'day'} = $datetimearray['mday'];
            $this->{$field . 'hour'} = $datetimearray['hours'];
            $this->{$field . 'minute'} = $datetimearray['minutes'];
        }
    }

    /**
     * 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.
        $fieldlist = $this->get_composite_fields();
        foreach ($fieldlist as $field) {
            if (
                isset($record->{$field . 'year'}) && isset($record->{$field . 'month'}) && isset($record->{$field . 'day'}) &&
                isset($record->{$field . 'hour'}) && isset($record->{$field . 'minute'})
            ) {
                $year = $record->{$field . 'year'};
                $month = $record->{$field . 'month'};
                $day = $record->{$field . 'day'};
                $hour = $record->{$field . 'hour'};
                $minute = $record->{$field . 'minute'};
                $record->{$field} = $this->item_datetime_to_unix_time($year, $month, $day, $hour, $minute);
                unset($record->{$field . 'year'});
                unset($record->{$field . 'month'});
                unset($record->{$field . 'day'});
                unset($record->{$field . 'hour'});
                unset($record->{$field . 'minute'});
            } else {
                $record->{$field} = null;
            }
        }

        // 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.
        // Nothing to do: no checkboxes in this plugin item form.

        // 4. Other.
    }

    // MARK set.

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

    /**
     * Set defaultoption.
     *
     * @param string $defaultoption
     * @return void
     */
    public function set_defaultoption($defaultoption) {
        $condition = false;
        $condition = $condition || ($defaultoption == SURVEYPRO_CUSTOMDEFAULT);
        $condition = $condition || ($defaultoption == SURVEYPRO_TIMENOWDEFAULT);
        $condition = $condition || ($defaultoption == SURVEYPRO_INVITEDEFAULT);
        $condition = $condition || ($defaultoption == SURVEYPRO_LIKELASTDEFAULT);
        $condition = $condition || ($defaultoption == SURVEYPRO_NOANSWERDEFAULT);
        if (!$condition) {
            throw new \coding_exception('Passed parameter defaultoption is not allowed.');
        }

        $this->defaultoption = $defaultoption;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // MARK get.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * Get the list of composite fields.
     *
     * @return void
     */
    public function get_composite_fields() {
        return ['defaultvalue', 'lowerbound', 'upperbound'];
    }

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

        foreach ($this->get_strftime_format_keys() as $strname) {
            $options[$strname] = userdate($timenow, get_string($strname, 'surveyprofield_datetime'));
        }
        $options['unixtime'] = get_string('unixtime', 'mod_surveypro');

        return $options;
    }

    /**
     * Return the list of supported strftime format keys.
     *
     * @return array
     */
    protected function get_strftime_format_keys(): array {
        $keys = [];
        for ($i = 1; $i < 13; $i++) {
            $keys[] = 'strftime' . str_pad($i, 2, '0', STR_PAD_LEFT);
        }
        return $keys;
    }

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

    /**
     * Prepare presets for itemsetuprform with the help of the parent class too.
     *
     * @return array $data
     */
    public function get_plugin_presets() {
        $pluginproperties = [
            'step', 'defaultoption', 'defaultvalue', 'downloadformat', 'lowerbound', 'upperbound',
            'defaultvalueyear', 'defaultvaluemonth', 'defaultvalueday', 'defaultvaluehour', 'defaultvalueminute',
            'lowerboundyear', 'lowerboundmonth', 'lowerboundday', 'lowerboundhour', 'lowerboundminute',
            'upperboundyear', 'upperboundmonth', 'upperboundday', 'upperboundhour', 'upperboundminute',
        ];
        $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_datetime'] = [];

        return $fieldlist;
    }

    /**
     * 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_datetime">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="step" type="xs:int" minOccurs="0"/>
                <xs:element name="defaultoption" type="xs:int"/>
                <xs:element name="defaultvalue" type="unixtime" minOccurs="0"/>
                <xs:element name="downloadformat" type="xs:string" minOccurs="0"/>
                <xs:element name="lowerbound" type="unixtime" minOccurs="0"/>
                <xs:element name="upperbound" type="unixtime" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:simpleType name="unixtime">
        <xs:restriction base="xs:string">
            <xs:pattern value="-?\d{0,10}"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>
EOS;

        return $schema;
    }

    // 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) {
        global $DB, $USER, $PAGE;

        if ($this->position == SURVEYPRO_POSITIONLEFT) {
            $elementlabel = $this->get_contentwithnumber();
        } else {
            $elementlabel = '&nbsp;';
        }

        // Begin of: element values.
        $days = [];
        $months = [];
        $years = [];
        $hours = [];
        $minutes = [];
        if (!$searchformelementscount) {
            if ($this->defaultoption == SURVEYPRO_INVITEDEFAULT) {
                $days[SURVEYPRO_INVITEVALUE] = get_string('inviteday', 'surveyprofield_datetime');
                $months[SURVEYPRO_INVITEVALUE] = get_string('invitemonth', 'surveyprofield_datetime');
                $years[SURVEYPRO_INVITEVALUE] = get_string('inviteyear', 'surveyprofield_datetime');
                $hours[SURVEYPRO_INVITEVALUE] = get_string('invitehour', 'surveyprofield_datetime');
                $minutes[SURVEYPRO_INVITEVALUE] = get_string('inviteminute', 'surveyprofield_datetime');
            }
        }
        // Condition limiting days.
        $condition = true;
        $condition = $condition && ($this->lowerboundyear == $this->upperboundyear);
        $condition = $condition && ($this->lowerboundmonth == $this->upperboundmonth);
        if ($condition) {
            $daysrange = range($this->lowerboundday, $this->upperboundday);
        } else {
            $daysrange = range(1, 31);
        }
        $days += array_combine($daysrange, $daysrange);
        // Condition limiting months.
        if ($this->lowerboundyear == $this->upperboundyear) {
            for ($i = $this->lowerboundmonth; $i <= $this->upperboundmonth; $i++) {
                $months[$i] = userdate(mktime(12, 0, 0, $i, 1, 2000), "%B"); // January, February, March...
            }
        } else {
            for ($i = 1; $i <= 12; $i++) {
                $months[$i] = userdate(mktime(12, 0, 0, $i, 1, 2000), "%B"); // January, February, March...
            }
        }
        // No condition limiting years.
        $yearsrange = range($this->lowerboundyear, $this->upperboundyear);
        $years += array_combine($yearsrange, $yearsrange);
        // Condition limiting hours.
        $condition = true;
        $condition = $condition && ($this->lowerboundyear == $this->upperboundyear);
        $condition = $condition && ($this->lowerboundmonth == $this->upperboundmonth);
        $condition = $condition && ($this->lowerboundday == $this->upperboundday);
        if ($condition) {
            for ($i = $this->lowerboundhour; $i < $this->upperboundhour; $i++) {
                $val = sprintf("%02d", $i);
                $hours[$val] = $val;
            }
        } else {
            for ($i = 0; $i < 24; $i++) {
                $val = sprintf("%02d", $i);
                $hours[$val] = $val;
            }
        }
        // Condition limiting minutes.
        $condition = true;
        $condition = $condition && ($this->lowerboundyear == $this->upperboundyear);
        $condition = $condition && ($this->lowerboundmonth == $this->upperboundmonth);
        $condition = $condition && ($this->lowerboundday == $this->upperboundday);
        $condition = $condition && ($this->lowerboundhour == $this->upperboundhour);
        if ($condition) {
            for ($i = $this->lowerboundminute; $i <= $this->upperboundminute; $i += $this->step) {
                $val = sprintf("%02d", $i);
                $minutes[$val] = $val;
            }
        } else {
            for ($i = 0; $i <= 59; $i += $this->step) {
                $val = sprintf("%02d", $i);
                $minutes[$val] = $val;
            }
        }
        // End of: element values.

        // Begin of: mform element.
        $attributes = [];
        $elementgroup = [];
        $class = ['class' => 'indent-' . $this->indent];
        $baseid = 'id_field_datetime_' . $this->sortindex;
        $basename = $this->itemname;

        // Get the order of elements based on the current language.
        $elementsorder = utility_dates::get_date_elements_order('strftimedate'); // ['day','month','year'] oppure altro ordine.
        foreach ($elementsorder as $element) {
            switch ($element) {
                case 'day':
                    $attributes['id'] = $baseid . '_day';
                    $attributes['aria-label'] = get_string('day');
                    $elementgroup[] = $mform->createElement('select', $basename . '_day', '', $days, $attributes);
                    break;
                case 'month':
                    $attributes['id'] = $baseid . '_month';
                    $attributes['aria-label'] = get_string('month');
                    $elementgroup[] = $mform->createElement('select', $basename . '_month', '', $months, $attributes);
                    break;
                case 'year':
                    $attributes['id'] = $baseid . '_year';
                    $attributes['aria-label'] = get_string('year');
                    $elementgroup[] = $mform->createElement('select', $basename . '_year', '', $years, $attributes);
            }
        }
        $attributes['id'] = $baseid . '_hour';
        $attributes['aria-label'] = get_string('hour');
        $elementgroup[] = $mform->createElement('select', $basename . '_hour', '', $hours, $attributes);
        $attributes['id'] = $baseid . '_minute';
        $attributes['aria-label'] = get_string('minute');
        $elementgroup[] = $mform->createElement('select', $basename . '_minute', '', $minutes, $attributes);

        $separator = [' ', ' '];
        $nextseparator = \html_writer::tag('div', ',', ['class' => 'datetime_separator_comma']);
        $separator[] = $nextseparator;
        $nextseparator = \html_writer::tag('div', ':', ['class' => 'datetime_separator_colon']);
        $separator[] = $nextseparator;

        if ($this->required) {
            if (!$searchformelementscount) {
                $mform->addGroup($elementgroup, $basename . '_group', $elementlabel, $separator, false, $class);

                // 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;
            } else {
                if ($searchformelementscount > 1) {
                    $starstr = get_string('star', 'mod_surveypro');
                    $attributes['id'] = $baseid . '_ignoreme';
                    $elementgroup[] = $mform->createElement('checkbox', $basename . '_ignoreme', '', $starstr, $attributes);
                }

                $mform->addGroup($elementgroup, $basename . '_group', $elementlabel, ' ', false, $class);

                if ($searchformelementscount > 1) {
                    $mform->disabledIf($basename . '_group', $basename . '_ignoreme', 'checked');
                    $mform->setDefault($basename . '_ignoreme', '1');
                }
            }
        } else {
            $attributes['id'] = $baseid . '_noanswer';
            $noanswerstr = get_string('noanswer', 'mod_surveypro');
            $elementgroup[] = $mform->createElement('checkbox', $basename . '_noanswer', '', $noanswerstr, $attributes);
            $separator[] = ' ';

            if (!$searchformelementscount) {
                $mform->addGroup($elementgroup, $basename . '_group', $elementlabel, $separator, false, $class);
                $mform->disabledIf($basename . '_group', $basename . '_noanswer', 'checked');
            } else {
                if ($searchformelementscount > 1) {
                    $starstr = get_string('star', 'mod_surveypro');
                    $attributes['id'] = $baseid . '_ignoreme';
                    $elementgroup[] = $mform->createElement('checkbox', $basename . '_ignoreme', '', $starstr, $attributes);
                }

                $mform->addGroup($elementgroup, $basename . '_group', $elementlabel, ' ', false, $class);

                if ($searchformelementscount > 1) {
                    $mform->disabledIf($basename . '_group', $basename . '_ignoreme', 'checked');
                    $mform->setDefault($basename . '_ignoreme', '1');
                }
            }
        }
        // End of: mform element.

        // Begin of: datepicker.
        if (!$searchformelementscount && !$readonly) {
            $mindate = mktime(
                $this->lowerboundhour,
                $this->lowerboundminute,
                0,
                $this->lowerboundmonth,
                $this->lowerboundday,
                $this->lowerboundyear
            );
            $maxdate = mktime(
                $this->upperboundhour,
                $this->upperboundminute,
                0,
                $this->upperboundmonth,
                $this->upperboundday,
                $this->upperboundyear
            );

            $PAGE->requires->js_call_amd('surveyprofield_datetime/datepicker', 'init', [[
                'baseid'  => $baseid,
                'mindate' => $mindate,
                'maxdate' => $maxdate,
                'step'    => $this->step,
            ]]);
        }
        // End of: datepicker.

        // Begin of: default section.
        // Defaults have a serious issue.
        // I need to apply the default ONLY IF
        // $mode = SURVEYPRO_NOMODE, SURVEYPRO_NEWRESPONSEMODE, SURVEYPRO_EDITMODE, SURVEYPRO_PREVIEWMODE
        // whereas if $mode = SURVEYPRO_READONLYMODE, I just need to display what’s in the database.
        // If the answer is not present in the database
        // because it’s a child field and its parent prevented the input
        // I need to leave the field empty without applying the default.
        if (!$searchformelementscount) {
            if (!$readonly) {
                switch ($this->defaultoption) {
                    case SURVEYPRO_INVITEDEFAULT:
                        $datetimearray['mday'] = SURVEYPRO_INVITEVALUE;
                        $datetimearray['mon'] = SURVEYPRO_INVITEVALUE;
                        $datetimearray['year'] = SURVEYPRO_INVITEVALUE;
                        $datetimearray['hours'] = SURVEYPRO_INVITEVALUE;
                        $datetimearray['minutes'] = SURVEYPRO_INVITEVALUE;
                        break;
                    case SURVEYPRO_NOANSWERDEFAULT:
                        $mform->setDefault($basename . '_noanswer', '1');
                        // No break here. SURVEYPRO_CUSTOMDEFAULT case is a subset of the SURVEYPRO_NOANSWERDEFAULT case.
                    case SURVEYPRO_CUSTOMDEFAULT:
                        $datetimearray = $this->item_split_unix_time($this->defaultvalue);
                        break;
                    case SURVEYPRO_TIMENOWDEFAULT:
                        $datetimearray = $this->item_split_unix_time(time());
                        break;
                    case SURVEYPRO_LIKELASTDEFAULT:
                        // Look for my last submission.
                        $sql = 'userid = :userid ORDER BY timecreated DESC LIMIT 1';
                        $where = ['userid' => $USER->id];
                        $mylastsubmissionid = $DB->get_field_select('surveypro_submission', 'id', $sql, $where, IGNORE_MISSING);
                        $where = ['itemid' => $this->itemid, 'submissionid' => $mylastsubmissionid];
                        $time = $DB->get_field('surveypro_answer', 'content', $where, IGNORE_MISSING);
                        if ($time === false) {
                            // There is not a previous answer.
                            // Apply defaults only in SURVEYPRO_NEWRESPONSEMODE
                            $datearray = $this->item_split_unix_time(time());
                        } else {
                            $datearray = $this->item_split_unix_time($time);
                        }
                        break;
                    default:
                        $message = 'Unexpected $this->defaultoption = ' . $this->defaultoption;
                        debugging('Error at line ' . __LINE__ . ' of ' . __FILE__ . '. ' . $message, DEBUG_DEVELOPER);
                }
                $mform->setDefault($basename . '_day', $datetimearray['mday']);
                $mform->setDefault($basename . '_month', $datetimearray['mon']);
                $mform->setDefault($basename . '_year', $datetimearray['year']);
                $mform->setDefault($basename . '_hour', $datetimearray['hours']);
                $mform->setDefault($basename . '_minute', $datetimearray['minutes']);
            }
        } else {
            if ($searchformelementscount > 1) {
                $mform->setDefault($basename . '_ignoreme', '1');
            }
            if (!$this->required) {
                $mform->setDefault($basename . '_noanswer', '0');
            }
        }
        // End of: default section.
    }

    /**
     * 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) {
        // This plugin displays as dropdown menu. It will never return empty values.
        // If ($this->required) { if (empty($data[$this->itemname])) { is useless.

        if (isset($data[$this->itemname . '_noanswer'])) {
            return $errors; // Nothing to validate.
        }

        // Make validation in the search form too.
        // I can not use if ($searchform) { return; because I still need to validate the correcteness of the date.
        if (isset($data[$this->itemname . '_ignoreme'])) {
            return $errors; // Nothing to validate.
        }

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

        $userday = $data[$this->itemname . '_day'];
        $usermonth = $data[$this->itemname . '_month'];
        $useryear = $data[$this->itemname . '_year'];
        $userhour = $data[$this->itemname . '_hour'];
        $userminute = $data[$this->itemname . '_minute'];

        // Begin of: verify the content of each drop down menu.
        if (!$searchform) {
            $testpassed = true;
            $testpassed = $testpassed && ($userday != SURVEYPRO_INVITEVALUE);
            $testpassed = $testpassed && ($usermonth != SURVEYPRO_INVITEVALUE);
            $testpassed = $testpassed && ($useryear != SURVEYPRO_INVITEVALUE);
            $testpassed = $testpassed && ($userhour != SURVEYPRO_INVITEVALUE);
            $testpassed = $testpassed && ($userminute != SURVEYPRO_INVITEVALUE);
        } else {
            // All five drop down menues are allowed to be == SURVEYPRO_IGNOREMEVALUE.
            // But not only 4, 3, 2 or 1.
            $testpassed = true;
            if ($userday == SURVEYPRO_IGNOREMEVALUE) {
                $testpassed = $testpassed && ($usermonth == SURVEYPRO_IGNOREMEVALUE);
                $testpassed = $testpassed && ($useryear == SURVEYPRO_IGNOREMEVALUE);
                $testpassed = $testpassed && ($userhour == SURVEYPRO_IGNOREMEVALUE);
                $testpassed = $testpassed && ($userminute == SURVEYPRO_IGNOREMEVALUE);
            } else {
                $testpassed = $testpassed && ($usermonth != SURVEYPRO_IGNOREMEVALUE);
                $testpassed = $testpassed && ($useryear != SURVEYPRO_IGNOREMEVALUE);
                $testpassed = $testpassed && ($userhour != SURVEYPRO_IGNOREMEVALUE);
                $testpassed = $testpassed && ($userminute != SURVEYPRO_IGNOREMEVALUE);
            }
        }
        if (!$testpassed) {
            if ($this->required) {
                $errors[$errorkey] = get_string('uerr_datetimenotsetrequired', 'surveyprofield_datetime');
            } else {
                $a = get_string('noanswer', 'mod_surveypro');
                $errors[$errorkey] = get_string('uerr_datetimenotset', 'surveyprofield_datetime', $a);
            }
            return $errors;
        }
        // End of: verify the content of each drop down menu.

        if (!utility_item::date_is_valid($userday, $usermonth, $useryear)) {
            $errors[$errorkey] = get_string('ierr_invalidinput', 'mod_surveypro');
            return $errors;
        }

        if ($searchform) {
            // Stop here your investigation. I don't need further validations.
            return $errors;
        }

        $haslowerbound = ($this->lowerbound != $this->item_datetime_to_unix_time($this->surveypro->startyear, 1, 1, 0, 0));
        $hasupperbound = ($this->upperbound != $this->item_datetime_to_unix_time($this->surveypro->stopyear, 12, 31, 23, 59));

        $userinput = $this->item_datetime_to_unix_time($useryear, $usermonth, $userday, $userhour, $userminute);

        if ($haslowerbound && $hasupperbound) {
            // Internal range.
            if (($userinput < $this->lowerbound) || ($userinput > $this->upperbound)) {
                $errors[$errorkey] = get_string('uerr_outofinternalrange', 'surveyprofield_datetime');
            }
        } else {
            if ($haslowerbound && ($userinput < $this->lowerbound)) {
                $errors[$errorkey] = get_string('uerr_lowerthanminimum', 'surveyprofield_datetime');
            }
            if ($hasupperbound && ($userinput > $this->upperbound)) {
                $errors[$errorkey] = get_string('uerr_greaterthanmaximum', 'surveyprofield_datetime');
            }
        }

        return $errors;
    }

    /**
     * Prepare the string with the filling instruction.
     *
     * @return string $fillinginstruction
     */
    public function userform_get_filling_instructions() {
        $haslowerbound = ($this->lowerbound != $this->item_datetime_to_unix_time($this->surveypro->startyear, 1, 1, 0, 0));
        $hasupperbound = ($this->upperbound != $this->item_datetime_to_unix_time($this->surveypro->stopyear, 12, 31, 23, 59));

        $formatkey = $this->get_friendlyformat();
        $format = get_string($formatkey, 'surveyprofield_datetime');

        if ($haslowerbound && $hasupperbound) {
            $a = new \stdClass();
            $a->lowerbound = userdate($this->lowerbound, $format, 0);
            $a->upperbound = userdate($this->upperbound, $format, 0);

            $fillinginstruction = get_string('restriction_lowerupper', 'surveyprofield_datetime', $a);
        } else {
            $fillinginstruction = '';
            if ($haslowerbound) {
                $a = userdate($this->lowerbound, $format, 0);
                $fillinginstruction = get_string('restriction_lower', 'surveyprofield_datetime', $a);
            }
            if ($hasupperbound) {
                $a = userdate($this->upperbound, $format, 0);
                $fillinginstruction = get_string('restriction_upper', 'surveyprofield_datetime', $a);
            }
        }

        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'])) { // This is correct for input and search form both.
            $olduseranswer->content = SURVEYPRO_NOANSWERVALUE;
        } else {
            $year = $answer['year'];
            $month = $answer['month'];
            $day = $answer['day'];
            $hour = $answer['hour'];
            $minute = $answer['minute'];
            if (!$searchform) {
                $condition = ($year == SURVEYPRO_INVITEVALUE);
                $condition = $condition || ($month == SURVEYPRO_INVITEVALUE);
                $condition = $condition || ($day == SURVEYPRO_INVITEVALUE);
                $condition = $condition || ($hour == SURVEYPRO_INVITEVALUE);
                $condition = $condition || ($minute == SURVEYPRO_INVITEVALUE);
                if ($condition) {
                    $olduseranswer->content = null;
                } else {
                    $olduseranswer->content = $this->item_datetime_to_unix_time($year, $month, $day, $hour, $minute);
                }
            } else {
                $condition = ($year == SURVEYPRO_IGNOREMEVALUE);
                $condition = $condition || ($month == SURVEYPRO_IGNOREMEVALUE);
                $condition = $condition || ($day == SURVEYPRO_IGNOREMEVALUE);
                $condition = $condition || ($hour == SURVEYPRO_IGNOREMEVALUE);
                $condition = $condition || ($minute == SURVEYPRO_IGNOREMEVALUE);
                if ($condition) {
                    $olduseranswer->content = null;
                } else {
                    $olduseranswer->content = $this->item_datetime_to_unix_time($year, $month, $day, $hour, $minute);
                }
            }
        }
    }

    /**
     * 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;
            }

            $datetimearray = $this->item_split_unix_time($fromdb->content);
            $prefill[$this->itemname . '_day'] = $datetimearray['mday'];
            $prefill[$this->itemname . '_month'] = $datetimearray['mon'];
            $prefill[$this->itemname . '_year'] = $datetimearray['year'];
            $prefill[$this->itemname . '_hour'] = $datetimearray['hours'];
            $prefill[$this->itemname . '_minute'] = $datetimearray['minutes'];
        }

        // If the "No answer" checkbox is part of the element GUI...
        if ($this->defaultoption = SURVEYPRO_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.
        if ($format == 'unixtime') {
            $return = $content;
        } else {
            // Last param "0" means: don't care of time zone.
            // The date and time of my plane from NY to Paris is the same all around the world.
            $return = userdate($content, get_string($format, 'surveyprofield_datetime'), 0);
        }

        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'];
    }
}
