Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

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

namespace mod_questionnaire\feedback;

use invalid_parameter_exception;
use coding_exception;

/**
 * Class for describing a feedback section.
 *
 * @package    mod_questionnaire
 * @copyright  2018 onward Mike Churchward (mike.churchward@poetopensource.org)
 * @author     Mike Churchward
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class section {

    /** @var int */
    public $id = 0;
    /** @var int */
    public $surveyid = 0;
    /** @var int */
    public $section = 1;
    /** @var array */
    public $scorecalculation = [];
    /** @var string */
    public $sectionlabel = '';
    /** @var string */
    public $sectionheading = '';
    /** @var string */
    public $sectionheadingformat = FORMAT_HTML;
    /** @var array */
    public $sectionfeedback = [];
    /** @var array */
    public $questions = [];

    /** The table name. */
    const TABLE = 'questionnaire_fb_sections';
    /** Represents the "no score" setting. */
    const NOSCORE = -1;

    /**
     * Section constructor.
     * if $params is provided, loads the entire feedback section record from the specified parameters. Parameters can be:
     *  'id' - the id field of the fb_sections table (required if no 'surveyid' field),
     *  'surveyid' - the surveyid field of the fb_sections table (required if no 'id' field),
     *  'sectionnum' - the section field of the fb_sections table (ignored if 'id' is present; defaults to 1).
     *
     * @param array $questions Array of mod_questionnaire\question objects.
     * @param array $params As above
     * @throws \dml_exception
     * @throws coding_exception
     * @throws invalid_parameter_exception
     */
    public function __construct($questions, $params = []) {

        if (!is_array($params) || !is_array($questions)) {
            throw new coding_exception('Invalid data provided.');
        }

        $this->questions = $questions;

        // Return a new section based on the data parameters if present.
        if (isset($params['id']) || isset($params['surveyid'])) {
            $this->load_section($params);
        }
    }

    /**
     * Factory method to create a new, empty section and return an instance.
     * @param int $surveyid
     * @param string $sectionlabel
     * @return section
     */
    public static function new_section($surveyid, $sectionlabel = '') {
        global $DB;

        $newsection = new self([], []);
        if (empty($sectionlabel)) {
            $sectionlabel = get_string('feedbackdefaultlabel', 'questionnaire');
        }
        $maxsection = $DB->get_field(self::TABLE, 'MAX(section)', ['surveyid' => $surveyid]);
        $newsection->surveyid = $surveyid;
        $newsection->section = $maxsection + 1;
        $newsection->sectionlabel = $sectionlabel;
        $newsection->scorecalculation = $newsection->encode_scorecalculation([]);
        $newsecid = $DB->insert_record(self::TABLE, $newsection);
        $newsection->id = $newsecid;
        $newsection->scorecalculation = [];
        return $newsection;
    }

    /**
     * Loads the entire feedback section record from the specified parameters. Parameters can be:
     *  'id' - the id field of the fb_sections table (required if no 'surveyid' field),
     *  'surveyid' - the surveyid field of the fb_sections table (required if no 'id' field),
     *  'sectionnum' - the section field of the fb_sections table (ignored if 'id' is present; defaults to 1).
     *
     * @param array $params
     * @throws \dml_exception
     * @throws coding_exception
     * @throws invalid_parameter_exception
     */
    public function load_section($params) {
        global $DB;

        if (!is_array($params)) {
            throw new coding_exception('Invalid data provided.');
        } else if (isset($params['id'])) {
            $where = 'WHERE fs.id = :id ';
        } else if (isset($params['surveyid'])) {
            $where = 'WHERE fs.surveyid = :surveyid AND fs.section = :sectionnum ';
            if (!isset($params['sectionnum'])) {
                $params['sectionnum'] = 1;
            }
        } else {
            throw new coding_exception('No valid data parameters provided.');
        }

        $select = 'SELECT f.id as fbid, fs.*, f.feedbacklabel, f.feedbacktext, f.feedbacktextformat, f.minscore, f.maxscore ';
        $from = 'FROM {' . self::TABLE . '} fs LEFT JOIN {' . sectionfeedback::TABLE . '} f ON fs.id = f.sectionid ';
        $order = 'ORDER BY minscore DESC';

        if (!($feedbackrecs = $DB->get_records_sql($select . $from . $where . $order, $params))) {
            throw new invalid_parameter_exception('No feedback sections exists for that data.');
        }
        foreach ($feedbackrecs as $fbid => $feedbackrec) {
            if (empty($this->id)) {
                $this->id = $feedbackrec->id;
                $this->surveyid = $feedbackrec->surveyid;
                $this->section = $feedbackrec->section;
                $this->scorecalculation = $this->get_valid_scorecalculation($feedbackrec->scorecalculation);
                $this->sectionlabel = $feedbackrec->sectionlabel;
                $this->sectionheading = $feedbackrec->sectionheading;
                $this->sectionheadingformat = $feedbackrec->sectionheadingformat;
            }
            if (!empty($fbid)) {
                $feedbackrec->id = $fbid;
                $this->sectionfeedback[$fbid] = new sectionfeedback(0, $feedbackrec);
            }
        }
    }

    /**
     * Loads the section feedback record into the proper array location.
     *
     * @param \stdClass $feedbackrec
     * @return int The id of the section feedback record.
     */
    public function load_sectionfeedback($feedbackrec) {
        if (!isset($feedbackrec->id) || empty($feedbackrec->id)) {
            $sectionfeedback = sectionfeedback::new_sectionfeedback($feedbackrec);
            $this->sectionfeedback[$sectionfeedback->id] = $sectionfeedback;
            return $sectionfeedback->id;
        } else {
            $this->sectionfeedback[$feedbackrec->id] = new sectionfeedback(0, $feedbackrec);
            return $feedbackrec->id;
        }
    }

    /**
     * Updates the object and data record with a new scorecalculation. If no new score provided, uses what's in the object.
     *
     * @param array $scorecalculation
     * @throws coding_exception
     */
    public function set_new_scorecalculation($scorecalculation = null) {
        global $DB;

        if ($scorecalculation == null) {
            $scorecalculation = $this->scorecalculation;
        }

        if (is_array($scorecalculation)) {
            $newscore = $this->encode_scorecalculation($scorecalculation);
            $DB->set_field(self::TABLE, 'scorecalculation', $newscore, ['id' => $this->id]);
            $this->scorecalculation = $scorecalculation;
        } else {
            throw new coding_exception('Invalid scorecalculation format.');
        }
    }

    /**
     * Removes the question from this section and updates the database.
     *
     * @param int $qid The question id and index.
     * @throws \dml_exception
     * @throws coding_exception
     */
    public function remove_question($qid) {
        if (isset($this->scorecalculation[$qid])) {
            unset($this->scorecalculation[$qid]);
            $this->set_new_scorecalculation();
        }
    }

    /**
     * Deletes this section from the database. Object is invalid after that.
     * This will also adjust the section numbers so that they are sequential and begin at 1.
     */
    public function delete() {
        global $DB;

        $this->delete_sectionfeedback();
        $DB->delete_records(self::TABLE, ['id' => $this->id]);

        // Resequence the section numbers as necessary.
        if ($allsections = $DB->get_records(self::TABLE, ['surveyid' => $this->surveyid], 'section ASC')) {
            $count = 1;
            foreach ($allsections as $id => $section) {
                if ($section->section != $count) {
                    $DB->set_field(self::TABLE, 'section', $count, ['id' => $id]);
                }
                $count++;
            }
        }
    }

    /**
     * Deletes the section feedback records from the database and clears the object array.
     *
     * @throws \dml_exception
     */
    public function delete_sectionfeedback() {
        global $DB;

        // It's quicker to delete all of the records at once then to go through the array and delete each object.
        $DB->delete_records(sectionfeedback::TABLE, ['sectionid' => $this->id]);
        $this->sectionfeedback = [];
    }

    /**
     * Updates the data record with what is currently in the object instance.
     *
     * @throws \dml_exception
     * @throws coding_exception
     */
    public function update() {
        global $DB;

        $this->scorecalculation = $this->encode_scorecalculation($this->scorecalculation);
        $DB->update_record(self::TABLE, $this);
        $this->scorecalculation = $this->get_valid_scorecalculation($this->scorecalculation);

        foreach ($this->sectionfeedback as $sectionfeedback) {
            $sectionfeedback->update();
        }
    }

    /**
     * Decode and ensure scorecalculation is what we expect.
     * @param string|null $codedstring
     * @return array
     * @throws coding_exception
     */
    public static function decode_scorecalculation(?string $codedstring): array {
        // Expect a serialized data string.
        if (($codedstring == null)) {
            $codedstring = '';
        }
        if (!is_string($codedstring)) {
            throw new coding_exception('Invalid scorecalculation format.');
        }
        if (!empty($codedstring)) {
            $scorecalculation = unserialize_array($codedstring) ?: [];
        } else {
            $scorecalculation = [];
        }

        if (!is_array($scorecalculation)) {
            throw new coding_exception('Invalid scorecalculation format.');
        }

        foreach ($scorecalculation as $score) {
            if (!empty($score) && !is_numeric($score)) {
                throw new coding_exception('Invalid scorecalculation format.');
            }
        }

        return $scorecalculation;
    }

    /**
     * Return the decoded and validated calculation array.
     * @param string $codedstring
     * @return mixed
     * @throws coding_exception
     */
    protected function get_valid_scorecalculation($codedstring) {
        $scorecalculation = static::decode_scorecalculation($codedstring);

        // Check for deleted questions and questions that don't support scores.
        foreach ($scorecalculation as $qid => $score) {
            if (!isset($this->questions[$qid])) {
                unset($scorecalculation[$qid]);
            } else if (!$this->questions[$qid]->supports_feedback_scores()) {
                $scorecalculation[$qid] = self::NOSCORE;
            }
        }

        return $scorecalculation;
    }

    /**
     * Return the encoded score array as a serialized string.
     * @param string $scorearray
     * @return mixed
     * @throws coding_exception
     */
    protected function encode_scorecalculation($scorearray) {
        // Expect an array.
        if (!is_array($scorearray)) {
            throw new coding_exception('Invalid scorearray format.');
        }

        $scorecalculation = serialize($scorearray);

        return $scorecalculation;
    }
}