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

/**
 * Surveypro templatebase class.
 *
 * @package   mod_surveypro
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace mod_surveypro;

/**
 * The base class for templates
 *
 * @package   mod_surveypro
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class templatebase
{
    /**
     * @var \stdClass Course module object
     */
    protected $cm;

    /**
     * @var \stdClass Context object
     */
    protected $context;

    /**
     * @var \stdClass Surveypro object
     */
    protected $surveypro;

    /**
     * @var string Name of the template
     */
    protected $templatename;

    /**
     * @var \stdClass Form content as submitted by the user
     */
    public $formdata = null;

    /**
     * @var array
     */
    protected $langtree = [];

    /**
     * @var string Outcome of the last minute check before applying templates.
     */
    protected $xmlvalidationoutcome;

    /**
     * Class constructor.
     *
     * @param object $cm
     * @param object $context
     * @param object $surveypro
     */
    public function __construct($cm, $context, $surveypro) {
        $this->cm = $cm;
        $this->context = $context;
        $this->surveypro = $surveypro;
    }

    // MARK set.

    /**
     * Set xmlvalidationoutcome.
     *
     * @param stdClass $xmlvalidationoutcome
     * @return void
     */
    public function set_xmlvalidationoutcome($xmlvalidationoutcome = null) {
        if (is_null($xmlvalidationoutcome)) {
            $xmlvalidationoutcome = new \stdClass();
        }
        $this->xmlvalidationoutcome = $xmlvalidationoutcome;
    }

    // MARK get.

    /**
     * Returns the ordered list of fields for the itembase (or the plugin) table.
     *
     * @param string $type (optional)
     * @param string $plugin (optional)
     * @return void
     */
    public function get_table_structure($type = null, $plugin = null) {
        global $CFG;

        if ((empty($type) && !empty($plugin)) || (!empty($type) && empty($plugin))) {
            $message = '$type and $plugin must be provided both or none.';
            debugging('Error at line ' . __LINE__ . ' of ' . __FILE__ . '. ' . $message, DEBUG_DEVELOPER);
        }

        $fieldlist = [];
        if (empty($type) && empty($plugin)) {
            $installxml = $CFG->dirroot . '/mod/surveypro/db/install.xml';
            $targettable = 'surveypro_item';
        } else {
            $installxml = $CFG->dirroot . '/mod/surveypro/' . $type . '/' . $plugin . '/db/install.xml';
            $targettable = 'surveypro' . $type . '_' . $plugin;

            // Some plugins are missing the install.xml because they don't have attributes.
            if (!file_exists($installxml)) {
                return $fieldlist;
            }
        }

        $xmlall = simplexml_load_file($installxml);
        foreach ($xmlall->children() as $xmltables) { // TABLES opening tag.
            foreach ($xmltables->children() as $xmltable) { // TABLE opening tag.
                $attributes = $xmltable->attributes();
                $tablename = $attributes['NAME'];
                if ($tablename != $targettable) {
                    continue;
                }
                foreach ($xmltable->children() as $xmlfields) { // FIELDS opening tag.
                    foreach ($xmlfields->children() as $xmlfield) { // FIELD opening tag.
                        $attributes = $xmlfield->attributes();
                        $fieldname = $attributes['NAME'];
                        $fieldlist[] = (string)$attributes['NAME'];
                    }
                    break;
                }
                break;
            }
            break;
        }

        return $fieldlist;
    }

    /**
     * Get plugin versions.
     *
     * @return versions of each field and format plugin
     */
    public static function get_subplugin_versions() {
        $versions = [];
        $types = [SURVEYPRO_TYPEFIELD, SURVEYPRO_TYPEFORMAT];

        foreach ($types as $type) {
            $plugins = surveypro_get_plugin_list($type, true);
            foreach ($plugins as $plugin => $unused) {
                $versions[$plugin] = get_config('surveypro' . $plugin, 'version');
            }
        }

        return $versions;
    }

    /**
     * Get xml validation outcome
     *
     * @return xmlvalidationoutcome
     */
    public function get_xmlvalidationoutcome() {
        return $this->xmlvalidationoutcome;
    }

    /**
     * Get user template content.
     *
     * @param int $utemplateid
     * @return void
     */
    public function get_utemplate_content($utemplateid = 0) {
        $fs = get_file_storage();
        if (empty($utemplateid)) {
            $utemplateid = $this->utemplateid;
        }
        $xmlfile = $fs->get_file_by_id($utemplateid);

        return $xmlfile->get_content();
    }

    // MARK other.

    /**
     * Validate the uploaded xml file.
     *
     * @param object $xml File to validate
     * @return object|false error describing the message to show, false if no error is found
     */
    public function validate_xml($xml) {
        global $CFG;

        $debug = false; // Set $debug = true if you want to stop anyway to debug the xml template.

        $this->set_xmlvalidationoutcome();

        $pluginversion = self::get_subplugin_versions();
        if ($debug) {
            $simplexml = new \SimpleXMLElement($xml);
        } else {
            $simplexml = @new \SimpleXMLElement($xml);
        }
        foreach ($simplexml->children() as $xmlitem) {
            foreach ($xmlitem->attributes() as $attribute => $value) {
                // Example: <item type="format" plugin="label" version="2025010100">.
                // Debug: echo 'Found: '.$attribute.' = '.$value.'<br>';
                if ($attribute == 'type') {
                    $currenttype = (string)$value;
                }
                if ($attribute == 'plugin') {
                    $currentplugin = (string)$value;
                }
                if ($attribute == 'version') {
                    $currentversion = (string)$value;
                }
            }
            if (!isset($currenttype)) {
                $error = new \stdClass();
                $error->key = 'missingitemtype';

                $this->set_xmlvalidationoutcome($error);
                break;
            }
            if (!isset($currentplugin)) {
                $error = new \stdClass();
                $error->key = 'missingitemplugin';

                $this->set_xmlvalidationoutcome($error);
                break;
            }
            if (!isset($currentversion)) {
                $error = new \stdClass();
                $error->key = 'missingitemversion';

                $this->set_xmlvalidationoutcome($error);
                break;
            }

            // Ok, $currenttype and $currentplugin are onboard.
            // Do they define correctly a class?
            if (!file_exists($CFG->dirroot . '/mod/surveypro/' . $currenttype . '/' . $currentplugin . '/version.php')) {
                $error = new \stdClass();
                $error->key = 'invalidtypeorplugin';

                $this->set_xmlvalidationoutcome($error);
                break;
            }

            $index = $currenttype . '_' . $currentplugin;
            if ($pluginversion[$index] > $currentversion) {
                $a = new \stdClass();
                $a->type = $currenttype;
                $a->plugin = $currentplugin;
                $a->currentversion = $currentversion;
                $a->versiondisk = $pluginversion[$index];

                $error = new \stdClass();
                $error->a = $a;
                $error->key = 'versionmismatch';

                $this->set_xmlvalidationoutcome($error);
                break;
            }

            foreach ($xmlitem->children() as $xmltable) {
                $tablename = $xmltable->getName();

                // I am assuming that surveypro_item table is ALWAYS before the surveypro_<<plugin>> table.
                if ($tablename == 'surveypro_item') {
                    // I could use a random class here because they all share the same parent get_itembase_schema
                    // but, in spite of this, I need the right class name for the next table
                    // so I choose to load the correct class from the beginning.
                    $classname = 'surveypro' . $currenttype . '_' . $currentplugin . '\item';
                    $xsd = $classname::get_itembase_schema(); // Itembase schema.
                } else {
                    // Classname has already been defined because of the previous loop over surveypro_item fields.
                    if (!isset($classname)) {
                        $error = new \stdClass();
                        $error->key = 'badtablenamefound';
                        $error->a = $tablename;

                        $this->set_xmlvalidationoutcome($error);
                        break 2; // Exit the first foreach and the second too.
                    }
                    $xsd = $classname::get_plugin_schema(); // Plugin schema.
                }

                if (empty($xsd)) {
                    $error = new \stdClass();
                    $error->key = 'xsdnotfound';

                    $this->set_xmlvalidationoutcome($error);
                    break 2; // Exit the first foreach and the second too.
                }

                $mdom = new \DOMDocument();
                $status = $mdom->loadXML($xmltable->asXML());

                // Let's capture errors.
                $olderrormode = libxml_use_internal_errors(true);

                // Clear XML error flag so that we don't incorrectly report failure when a previous xml parse failed.
                libxml_clear_errors();

                if ($debug) {
                    $status = $status && $mdom->schemaValidateSource($xsd);
                    if (!$status) {
                        echo \html_writer::tag('pre', s($xsd));
                        echo \html_writer::tag('pre', s($mdom->saveXML()));
                    }
                } else {
                    $status = $status && @$mdom->schemaValidateSource($xsd);
                }

                // Check for errors.
                $errors = libxml_get_errors();

                // Stop capturing errors.
                libxml_use_internal_errors($olderrormode);

                if (!empty($errors)) {
                    $firsterror = array_shift($errors);
                    if ($tablename == 'surveypro_item') {
                        $messagetemplate = get_string('reportederroritembase', 'mod_surveypro');
                    } else {
                        $messagetemplate = get_string('reportederrorplugin', 'mod_surveypro');
                    }
                    $a = sprintf($messagetemplate, trim($firsterror->message, "\n\r\t ."), $currentplugin);

                    $error = new \stdClass();
                    $error->a = $a;
                    $error->key = 'reportederror';

                    $this->set_xmlvalidationoutcome($error);
                    break 2; // Exit the first foreach and the second too.
                }

                if (!$status) {
                    // Stop here. It is useless to continue.
                    if ($debug) {
                        echo '<hr /><textarea rows="10" cols="100">' . $xmltable->asXML() . '</textarea>';
                        echo '<textarea rows="10" cols="100">' . $xsd . '</textarea>';
                    }

                    $error = new \stdClass();
                    $error->key = 'schemavalidationfailed';

                    $this->set_xmlvalidationoutcome($error);
                    break 2; // Exit the first foreach and the second too.
                }
            }
        }
    }

    /**
     * Tell the user that the usertemplate is obsolete.
     *
     * @return void
     */
    public function lastminute_stop() {
        global $OUTPUT;

        $errormessage = $this->get_xmlvalidationoutcome();
        if (isset($errormessage->key)) {
            if (isset($errormessage->a)) {
                $message = get_string($errormessage->key, 'mod_surveypro', $errormessage->a);
            } else {
                $message = get_string($errormessage->key, 'mod_surveypro');
            }
            $message .= get_string('utemplate_nolongervalid', 'mod_surveypro');

            echo $OUTPUT->notification($message, 'notifyproblem');
            $paramurl = ['s' => $this->surveypro->id, 'area' => 'layout', 'section' => 'itemslist'];
            $url = new \moodle_url('/mod/surveypro/layout.php', $paramurl);
            echo $OUTPUT->continue_button($url);

            echo $OUTPUT->footer();
            die();
        }
    }

    /**
     * Tell the user that the usertemplate is obsolete.
     *
     * @param string $mtemplate
     * @param int $itemid
     * @return void
     */
    public function load_new_files_from_lang($mtemplate, $itemid) {
        global $CFG;

        // I need to push to SURVEYPRO_ITEMCONTENTFILEAREA new files described in current lang file that may be new.

        $context = \context_module::instance($this->cm->id);

        $templatepath = $CFG->dirroot . '/mod/surveypro/template/' . $mtemplate . '/template.xml';
        if (!file_exists($templatepath)) {
            return;
        }

        $fs = get_file_storage();

        $templatecontent = file_get_contents($templatepath);
        $simplexml = new \SimpleXMLElement($templatecontent);
        foreach ($simplexml->children() as $xmlitem) {
            foreach ($xmlitem->children() as $xmltable) { // Surveypro_item and surveypro_<<plugin>>.
                $tablename = $xmltable->getName();
                if ($tablename != 'surveypro_item') {
                    continue;
                }
                foreach ($xmltable->children() as $xmlfield) {
                    $fieldname = $xmlfield->getName();
                    if ($fieldname != 'embedded') {
                        continue;
                    }
                    foreach ($xmlfield->children() as $xmlfileattribute) {
                        $fileattributename = $xmlfileattribute->getName();
                        if ($fileattributename == 'filename') {
                            $attributecontent = (string)$xmlfileattribute;
                            $filename = get_string($attributecontent, 'surveyprotemplate_' . $mtemplate);
                        }
                        if ($fileattributename == 'filecontent') {
                            $attributecontent = (string)$xmlfileattribute;
                            $encodedcontent = get_string($attributecontent, 'surveyprotemplate_' . $mtemplate);
                            $filecontent = base64_decode($encodedcontent);
                        }
                    }
                    if (!$fs->file_exists($context->id, 'mod_surveypro', SURVEYPRO_ITEMCONTENTFILEAREA, $itemid, '/', $filename)) {
                        // Add the new found file to filearea.
                        $filerecord = new \stdClass();
                        $filerecord->contextid = $context->id;
                        $filerecord->component = 'mod_surveypro';
                        $filerecord->filearea = SURVEYPRO_ITEMCONTENTFILEAREA;
                        $filerecord->itemid = $itemid;
                        $filerecord->filepath = '/';
                        $filerecord->filename = $filename;

                        $fileinfo = $fs->create_file_from_string($filerecord, $filecontent);
                    }
                }
            }
        }
    }
}
