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 tool_courserating;

use core_customfield\data_controller;
use core_customfield\field_controller;
use tool_courserating\external\stars_exporter;

/**
 * Additional helper functions
 *
 * @package     tool_courserating
 * @copyright   2022 Marina Glancy <marina.glancy@gmail.com>
 * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class helper {
    /**
     * Temporary function
     *
     * @return void
     */
    private function wordings() {
        // @codingStandardsIgnoreStart
        // Udemy.
        'You\'ve finished the last lesson in this course! Would you like to leave a review?';
        [
            1 => 'Awful, not what I was expecting at all',
            1.5 => 'Awful / Poor',
            2 => 'Poor, pretty disappointed',
            2.5 => 'Poor / Average',
            3 => 'Average, could be better',
            3.5 => 'Average / Good',
            4 => 'Good, what I expected',
            4.5 => 'Good / Amazing',
            5 => 'Amazing, above expectations',
        ];
        'Tell us about your own personal experience taking this course. Was it a good match for you?';

        'Report'; 'Report abuse';
        'Flagged content is reviewed by Udemy staff to determine whether it violates Terms of Service or Community Guidelines. If you have a question or technical issue, please contact our Support team here.';
        'Issue type';
        [
            'Inappropriate Course Content',
            'Inappropriate Behavior',
            'Udemy Policy Violation',
            'Spammy Content',
            'Other',
        ];
        'Issue details';
        // @codingStandardsIgnoreEnd
    }

    /**
     * Checks if we are on a main course page
     *
     * @return int
     */
    public static function is_course_page(): int {
        global $PAGE, $CFG;
        if ($PAGE->course && $PAGE->url->out_omit_querystring() === $CFG->wwwroot . '/course/view.php') {
            return $PAGE->course->id;
        }
        return 0;
    }

    /**
     * Checks if we are on a main page of single-activity course
     *
     * @return int
     */
    public static function is_single_activity_course_page(): int {
        global $PAGE, $CFG;
        if ($PAGE->context->contextlevel == CONTEXT_MODULE && $PAGE->course->format === 'singleactivity' &&
            $PAGE->url->out_omit_querystring() === $CFG->wwwroot . '/mod/' . $PAGE->cm->modname . '/view.php') {
            return $PAGE->course->id;
        }
        return 0;
    }

    /**
     * Checks if we are on a course edit page
     *
     * @return int
     */
    public static function is_course_edit_page(): int {
        global $PAGE, $CFG;
        if ($PAGE->course && $PAGE->url->out_omit_querystring() === $CFG->wwwroot . '/course/edit.php') {
            return $PAGE->course->id;
        }
        return 0;
    }

    /**
     * Are course ratings enabled (or could be enabled) in any courses? Do we need to have a course rating field
     *
     * @return bool
     */
    public static function course_ratings_enabled_anywhere(): bool {
        if (self::get_setting(constants::SETTING_RATINGMODE) == constants::RATEBY_NOONE &&
                !self::get_setting(constants::SETTING_PERCOURSE)) {
            return false;
        }
        return true;
    }

    /**
     * Options for the review editor form element
     *
     * @param \context $context
     * @return array
     */
    public static function review_editor_options(\context $context) {
        global $CFG;
        require_once($CFG->dirroot.'/lib/formslib.php');
        return [
            'subdirs' => 0,
            'maxbytes' => $CFG->maxbytes,
            'maxfiles' => EDITOR_UNLIMITED_FILES,
            'changeformat' => 0,
            'context' => $context,
        ];
    }

    /**
     * Retrieve and clean plugin setting
     *
     * @param string $name
     * @return bool|mixed|object|string
     */
    public static function get_setting(string $name) {
        $value = get_config('tool_courserating', $name);
        static $defaults = [
            constants::SETTING_STARCOLOR => constants::SETTING_STARCOLOR_DEFAULT,
            constants::SETTING_RATINGCOLOR => constants::SETTING_RATINGCOLOR_DEFAULT,
            constants::SETTING_DISPLAYEMPTY => false,
            constants::SETTING_PERCOURSE => false,
            constants::SETTING_RATINGMODE => constants::RATEBY_ANYTIME,
            constants::SETTING_USEHTML => false,
        ];
        if (!isset($value) && array_key_exists($name, $defaults)) {
            // Can only happen if there is unfinished upgrade.
            return $defaults[$name];
        }

        if ($name === constants::SETTING_DISPLAYEMPTY || $name === constants::SETTING_PERCOURSE
                || $name === constants::SETTING_USEHTML) {
            return !empty($value);
        }
        if ($name === constants::SETTING_STARCOLOR || $name === constants::SETTING_RATINGCOLOR) {
            $color = strtolower($value ?? '');
            return (preg_match('/^#[a-f0-9]{6}$/', $color)) ? $color : $defaults[$name];
        }
        if ($name === constants::SETTING_RATINGMODE) {
            static $available = [constants::RATEBY_NOONE, constants::RATEBY_ANYTIME, constants::RATEBY_COMPLETED];
            return in_array($value, $available) ? $value : $defaults[$name];
        }
        return $value;
    }

    /**
     * CSS for the stars colors to be added to the page
     *
     * @return string
     */
    public static function get_rating_colour_css() {
        return '.tool_courserating-stars { color: '.self::get_setting(constants::SETTING_STARCOLOR).'; }'."\n".
            '.tool_courserating-ratingcolor { color: '.self::get_setting(constants::SETTING_RATINGCOLOR).';}'."\n".
            '.tool_courserating-norating .tool_courserating-stars { color: '.constants::COLOR_GRAY.';}'."\n".
            '.tool_courserating-barcolor { background-color: '.self::get_setting(constants::SETTING_STARCOLOR).';}'."\n";
    }

    /**
     * Finds a field by its shortname
     *
     * @param string $shortname
     * @return field_controller|null
     */
    protected static function find_custom_field_by_shortname(string $shortname): ?field_controller {
        $handler = \core_course\customfield\course_handler::create();
        $categories = $handler->get_categories_with_fields();
        foreach ($categories as $category) {
            foreach ($category->get_fields() as $field) {
                if ($field->get('shortname') === $shortname) {
                    return $field;
                }
            }
        }
        return null;
    }

    /**
     * Create a custom course field if it does not exist
     *
     * @param string $shortname
     * @param string $type i.e. 'textarea', 'select', 'text
     * @param null|\lang_string $displayname
     * @param array $config additional field configuration, for example, options for 'select' element
     * @param string $description
     * @return field_controller|null
     */
    protected static function create_custom_field(string $shortname, string $type = 'text', ?\lang_string $displayname = null,
                                               array $config = [], string $description = ''): ?field_controller {
        $handler = \core_course\customfield\course_handler::create();
        $categories = $handler->get_categories_with_fields();
        if (empty($categories)) {
            $categoryid = $handler->create_category();
            $category = \core_customfield\category_controller::create($categoryid);
        } else {
            $category = reset($categories);
        }

        $config += [
            'defaultvalue' => '',
            'defaultvalueformat' => 1,
            'visibility' => \core_course\customfield\course_handler::VISIBLETOALL,
            'required' => 0,
            'uniquevalues' => 0,
            'locked' => 0,
        ];
        $record = (object)[
            'type' => $type,
            'shortname' => $shortname,
            'name' => $displayname ? (string)$displayname : $shortname,
            'descriptionformat' => FORMAT_HTML,
            'description' => $description,
            'configdata' => json_encode($config),
        ];

        try {
            $field = \core_customfield\field_controller::create(0, $record, $category);
        } catch (\moodle_exception $e) {
            return null;
        }

        $handler->save_field_configuration($field, $record);

        // Fetch the field again because the categories cache was rebuilt.
        return self::find_custom_field_by_shortname($shortname);
    }

    /**
     * Retrieve course custom field responsible for storing course ratings, create if not found
     *
     * @return field_controller|null
     */
    public static function get_course_rating_field(): ?field_controller {
        $shortname = constants::CFIELD_RATING;
        $field = self::find_custom_field_by_shortname($shortname);

        if (!self::course_ratings_enabled_anywhere()) {
            if ($field) {
                $field->get_handler()->delete_field_configuration($field);
            }
            return null;
        }

        return $field ?? self::create_custom_field($shortname,
            'textarea',
            new \lang_string('ratinglabel', 'tool_courserating'),
            ['locked' => 1],
            get_string('cfielddescription', 'tool_courserating'));
    }

    /**
     * Retrieve course custom field responsible for configuring per-course course rating mode, create if needed
     *
     * @return field_controller|null
     */
    public static function get_course_rating_mode_field(): ?field_controller {
        $shortname = constants::CFIELD_RATINGMODE;
        $field = self::find_custom_field_by_shortname($shortname);
        if (!self::get_setting(constants::SETTING_PERCOURSE)) {
            if ($field) {
                $field->get_handler()->delete_field_configuration($field);
            }
            return null;
        }

        $options = constants::rated_courses_options();
        $description = get_string('ratebydefault', 'tool_courserating',
            $options[self::get_setting(constants::SETTING_RATINGMODE)]);
        $field = $field ?? self::create_custom_field($shortname,
            'select',
            new \lang_string('ratingmode', 'tool_courserating'),
            [
                'visibility' => \core_course\customfield\course_handler::NOTVISIBLE,
                'options' => join("\n", $options),
            ],
            $description);
        if ($field && $field->get('description') !== $description) {
            $field->set('description', $description);
            $field->save();
        }
        return $field;
    }

    /**
     * Delete all course custom fields created by this plugin (on uninstall)
     *
     * @return void
     */
    public static function delete_all_custom_fields() {
        $shortname = constants::CFIELD_RATINGMODE;
        if ($field = self::find_custom_field_by_shortname($shortname)) {
            $field->get_handler()->delete_field_configuration($field);
        }
        $shortname = constants::CFIELD_RATING;
        if ($field = self::find_custom_field_by_shortname($shortname)) {
            $field->get_handler()->delete_field_configuration($field);
        }
    }

    /**
     * Retireve data stored in a course custom field
     *
     * @param int $courseid
     * @param string $shortname
     * @return data_controller|null
     */
    protected static function get_custom_field_data(int $courseid, string $shortname): ?data_controller {
        if ($f = self::find_custom_field_by_shortname($shortname)) {
            $fields = \core_customfield\api::get_instance_fields_data([$f->get('id') => $f], $courseid);
            foreach ($fields as $data) {
                if (!$data->get('id')) {
                    $data->set('contextid', \context_course::instance($courseid)->id);
                }
                return $data;
            }
        }
        return null;
    }

    /**
     * Retrieve data stored in a course rating course custom field
     *
     * @param int $courseid
     * @return data_controller|null
     */
    public static function get_course_rating_data_in_cfield(int $courseid): ?data_controller {
        return self::get_custom_field_data($courseid, constants::CFIELD_RATING);
    }

    /**
     * Retireve data stored in a rating mode custom course field
     *
     * @param int $courseid
     * @return data_controller|null
     */
    public static function get_course_rating_enabled_data_in_cfield(int $courseid): ?data_controller {
        return self::get_custom_field_data($courseid, constants::CFIELD_RATINGMODE);
    }

    /**
     * Calculate the rating mode for a specific course
     *
     * @param int $courseid
     * @return int
     */
    public static function get_course_rating_mode(int $courseid): int {
        $mode = self::get_setting(constants::SETTING_RATINGMODE);
        if (self::get_setting(constants::SETTING_PERCOURSE)) {
            if ($data = self::get_course_rating_enabled_data_in_cfield($courseid)) {
                $modecourse = (int)$data->get('intvalue');
                if (array_key_exists($modecourse, constants::rated_courses_options())) {
                    // Value is overridden for this course.
                    return $modecourse;
                }
            }
        }
        return $mode;
    }

    /**
     * Formatter for average rating
     *
     * @param float|null $avgrating
     * @param string $default
     * @return string
     */
    public static function format_avgrating(?float $avgrating, string $default = ''): string {
        return $avgrating ? sprintf("%.1f", $avgrating) : $default;
    }

    /**
     * Formatter for stars
     *
     * @param float|null $avgrating
     * @param \renderer_base|null $output
     * @return string
     */
    public static function stars(?float $avgrating, ?\renderer_base $output = null): string {
        global $PAGE;
        if (!$avgrating) {
            return '';
        }
        $output = $output ?? $PAGE->get_renderer('tool_courserating');
        return $output->render_from_template('tool_courserating/stars',
            (new stars_exporter($avgrating))->export($output));
    }

    /**
     * Formatter for date
     * @param int $value
     * @return string
     */
    public static function format_date($value): string {
        return $value ? userdate($value, get_string('strftimedatetimeshort', 'core_langconfig')) : '';
    }

    /**
     * Formatter for review
     *
     * @param string $value
     * @param \stdClass $row
     * @return string
     */
    public static function format_review($value, \stdClass $row): string {
        if (empty($row->id) || !strlen($row->review ?? '')) {
            return '';
        }
        $context = !empty($row->courseid) ? \context_course::instance($row->courseid) : \context_system::instance();
        $formatparams = [
            'options' => [],
            'striplinks' => true,
            'component' => 'tool_courserating',
            'filearea' => 'review',
            'itemid' => $row->id,
            'context' => $context,
        ];
        if (self::get_setting(constants::SETTING_USEHTML)) {
            list($text, $format) = external_format_text($row->review, FORMAT_HTML, $formatparams['context'],
                $formatparams['component'], $formatparams['filearea'], $formatparams['itemid'], $formatparams['options']);
            return $text;
        } else {
            return format_text(clean_param($row->review, PARAM_TEXT), FORMAT_MOODLE, ['context' => $context]);
        }
    }

    /**
     * Actions column
     *
     * @param int $id
     * @param \stdClass $row
     * @return string
     */
    public static function format_actions($id, $row): string {
        if (!$id || !permission::can_delete_rating($id, $row->courseid)) {
            return '';
        }
        return "<span data-for=\"tool_courserating-rbcell\" data-ratingid=\"$id\">".
            "<a href=\"#\" data-action=\"tool_courserating-delete-rating\" data-ratingid=\"$id\">".
            get_string('deleterating', 'tool_courserating')."</a></span>";
    }

    /**
     * Format individual student rating in the course report
     *
     * @param int $rating
     * @param \stdClass $row
     * @return string
     */
    public static function format_rating_in_course_report($rating, $row): string {
        if (!$rating) {
            return '';
        }
        return \html_writer::span(
            self::stars((float)$rating).
            \html_writer::span($rating, 'tool_courserating-ratingcolor ml-2'),
            'tool_courserating-reportrating');
    }

    /**
     * Format flags count in course report
     *
     * @param int|null $nofflags
     * @param \stdClass $row
     * @return string
     */
    public static function format_flags_in_course_report(?int $nofflags, \stdClass $row): string {
        return $nofflags ? "<span class=\"badge badge-warning\">$nofflags</span>" : '';
    }
}