first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,65 @@
<?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/>.
/**
* Activity base class.
*
* @package mod_feedback
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_feedback
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* Overwritten to mark as viewed if stats are published.
*
* @param \cm_info $cm
* @param int $contextid
* @param int $userid
* @param int $after
* @return bool
*/
protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) {
// If stats are published any write action counts as viewed feedback.
if (!empty($this->instancedata[$cm->instance]->publish_stats)) {
$user = (object)['id' => $userid];
return $this->any_write_log($contextid, $user);
}
return parent::feedback_viewed($cm, $contextid, $userid, $after);
}
/**
* Returns the name of the field that controls activity availability.
*
* @return null|string
*/
protected function get_timeclose_field() {
return 'timeclose';
}
}
@@ -0,0 +1,62 @@
<?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/>.
/**
* Cognitive depth indicator - feedback.
*
* @package mod_feedback
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - feedback.
*
* @package mod_feedback
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_feedback');
}
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
public function get_cognitive_depth_level(\cm_info $cm) {
$this->fill_instance_data($cm);
if (!empty($this->instancedata[$cm->instance]->publish_stats)) {
// If stats are published we count that the user viewed feedback.
return self::COGNITIVE_LEVEL_3;
}
return self::COGNITIVE_LEVEL_2;
}
}
@@ -0,0 +1,58 @@
<?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/>.
/**
* Social breadth indicator - feedback.
*
* @package mod_feedback
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - feedback.
*
* @package mod_feedback
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_feedback');
}
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
public function get_social_breadth_level(\cm_info $cm) {
$this->fill_instance_data($cm);
return self::SOCIAL_LEVEL_2;
}
}
+574
View File
@@ -0,0 +1,574 @@
<?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/>.
/**
* Contains class mod_feedback_complete_form
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class mod_feedback_complete_form
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_complete_form extends moodleform {
/** @var int */
const MODE_COMPLETE = 1;
/** @var int */
const MODE_PRINT = 2;
/** @var int */
const MODE_EDIT = 3;
/** @var int */
const MODE_VIEW_RESPONSE = 4;
/** @var int */
const MODE_VIEW_TEMPLATE = 5;
/** @var int */
protected $mode;
/** @var mod_feedback_structure|mod_feedback_completion */
protected $structure;
/** @var mod_feedback_completion */
protected $completion;
/** @var int */
protected $gopage;
/** @var bool */
protected $hasrequired = false;
/**
* Constructor
*
* @param int $mode
* @param mod_feedback_structure $structure
* @param string $formid CSS id attribute of the form
* @param array $customdata
*/
public function __construct($mode, mod_feedback_structure $structure, $formid, $customdata = null) {
$this->mode = $mode;
$this->structure = $structure;
$this->gopage = isset($customdata['gopage']) ? $customdata['gopage'] : 0;
$isanonymous = $this->structure->is_anonymous() ? ' ianonymous' : '';
parent::__construct(null, $customdata, 'POST', '',
array('id' => $formid, 'class' => 'feedback_form' . $isanonymous), true);
$this->set_display_vertical();
}
/**
* Form definition
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'id', $this->get_cm()->id);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'courseid', $this->get_current_course_id());
$mform->setType('courseid', PARAM_INT);
$mform->addElement('hidden', 'gopage');
$mform->setType('gopage', PARAM_INT);
$mform->addElement('hidden', 'lastpage');
$mform->setType('lastpage', PARAM_INT);
$mform->addElement('hidden', 'startitempos');
$mform->setType('startitempos', PARAM_INT);
$mform->addElement('hidden', 'lastitempos');
$mform->setType('lastitempos', PARAM_INT);
if (isloggedin() && !isguestuser() && $this->mode != self::MODE_EDIT && $this->mode != self::MODE_VIEW_TEMPLATE &&
$this->mode != self::MODE_VIEW_RESPONSE) {
// Output information about the current mode (anonymous or not) in some modes.
if ($this->structure->is_anonymous()) {
$anonymousmodeinfo = get_string('anonymous', 'feedback');
} else {
$anonymousmodeinfo = get_string('non_anonymous', 'feedback');
}
$element = $mform->addElement('static', 'anonymousmode', '',
get_string('mode', 'feedback') . ': ' . $anonymousmodeinfo);
$element->setAttributes($element->getAttributes() + ['class' => 'feedback_mode']);
}
// Add buttons to go to previous/next pages and submit the feedback.
if ($this->mode == self::MODE_COMPLETE) {
$buttonarray = array();
$buttonarray[] = &$mform->createElement('submit', 'gopreviouspage', get_string('previous_page', 'feedback'));
$buttonarray[] = &$mform->createElement('submit', 'gonextpage', get_string('next_page', 'feedback'),
array('class' => 'form-submit'));
$buttonarray[] = &$mform->createElement('submit', 'savevalues', get_string('save_entries', 'feedback'),
array('class' => 'form-submit'));
$buttonarray[] = &$mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
if ($this->mode == self::MODE_COMPLETE) {
$this->definition_complete();
} else {
$this->definition_preview();
}
// Set data.
$this->set_data(array('gopage' => $this->gopage));
}
/**
* Called from definition_after_data() in the completion mode
*
* This will add only items from a current page to the feedback and adjust the buttons
*/
protected function definition_complete() {
if (!$this->structure instanceof mod_feedback_completion) {
// We should not really be here but just in case.
return;
}
$pages = $this->structure->get_pages();
$gopage = $this->gopage;
$pageitems = $pages[$gopage];
$hasnextpage = $gopage < count($pages) - 1; // Until we complete this page we can not trust get_next_page().
$hasprevpage = $gopage && ($this->structure->get_previous_page($gopage, false) !== null);
// Add elements.
foreach ($pageitems as $item) {
$itemobj = feedback_get_item_class($item->typ);
$itemobj->complete_form_element($item, $this);
}
// Remove invalid buttons (for example, no "previous page" if we are on the first page).
if (!$hasprevpage) {
$this->remove_button('gopreviouspage');
}
if (!$hasnextpage) {
$this->remove_button('gonextpage');
}
if ($hasnextpage) {
$this->remove_button('savevalues');
}
}
/**
* Called from definition_after_data() in all modes except for completion
*
* This will add all items to the form, including pagebreaks as horizontal rules.
*/
protected function definition_preview() {
foreach ($this->structure->get_items() as $feedbackitem) {
$itemobj = feedback_get_item_class($feedbackitem->typ);
$itemobj->complete_form_element($feedbackitem, $this);
}
}
/**
* Removes the button that is not applicable for the current page
*
* @param string $buttonname
*/
private function remove_button($buttonname) {
$el = $this->_form->getElement('buttonar');
foreach ($el->_elements as $idx => $button) {
if ($button instanceof MoodleQuickForm_submit && $button->getName() === $buttonname) {
unset($el->_elements[$idx]);
return;
}
}
}
/**
* Returns value for this element that is already stored in temporary or permanent table,
* usually only available when user clicked "Previous page". Null means no value is stored.
*
* @param stdClass $item
* @return string
*/
public function get_item_value($item) {
if ($this->structure instanceof mod_feedback_completion) {
return $this->structure->get_item_value($item);
}
return null;
}
/**
* Can be used by the items to get the course id for which feedback is taken
*
* This function returns 0 for feedbacks that are located inside the courses.
* $this->get_feedback()->course will return the course where feedback is located.
* $this->get_current_course_id() will return the course where user was before taking the feedback
*
* @return int
*/
public function get_course_id() {
return $this->structure->get_courseid();
}
/**
* Record from 'feedback' table corresponding to the current feedback
* @return stdClass
*/
public function get_feedback() {
return $this->structure->get_feedback();
}
/**
* Current feedback mode, see constants on the top of this class
* @return int
*/
public function get_mode() {
return $this->mode;
}
/**
* Returns whether the form is frozen, some items may prefer to change the element
* type in case of frozen form. For example, text or textarea element does not look
* nice when frozen
*
* @return bool
*/
public function is_frozen() {
return $this->mode == self::MODE_VIEW_RESPONSE;
}
/**
* Returns the current course module
* @return cm_info
*/
public function get_cm() {
return $this->structure->get_cm();
}
/**
* Returns the course where user was before taking the feedback.
*
* For feedbacks inside the course it will be the same as $this->get_feedback()->course.
* For feedbacks on the frontpage it will be the same as $this->get_course_id()
*
* @return int
*/
public function get_current_course_id() {
return $this->structure->get_courseid() ?: $this->get_feedback()->course;
}
/**
* CSS class for the item
* @param stdClass $item
* @return string
*/
protected function get_suggested_class($item) {
$class = "feedback_itemlist feedback-item-{$item->typ}";
if ($item->dependitem) {
$class .= " feedback_is_dependent";
}
if ($item->typ !== 'pagebreak') {
$itemobj = feedback_get_item_class($item->typ);
if ($itemobj->get_hasvalue()) {
$class .= " feedback_hasvalue";
}
}
return $class;
}
/**
* Adds an element to this form - to be used by items in their complete_form_element() method
*
* @param stdClass $item
* @param HTML_QuickForm_element|array $element either completed form element or an array that
* can be passed as arguments to $this->_form->createElement() function
* @param bool $addrequiredrule automatically add 'required' rule
* @param bool $setdefaultvalue automatically set default value for element
* @return HTML_QuickForm_element
*/
public function add_form_element($item, $element, $addrequiredrule = true, $setdefaultvalue = true) {
global $OUTPUT;
if (is_array($element) && $element[0] == 'group') {
// For groups, use the mforms addGroup API.
// $element looks like: ['group', $groupinputname, $name, $objects, $separator, $appendname],
$element = $this->_form->addGroup($element[3], $element[1], $element[2], $element[4], $element[5]);
} else {
// Add non-group element to the form.
if (is_array($element)) {
$element = call_user_func_array(array($this->_form, 'createElement'), $element);
}
$element = $this->_form->addElement($element);
}
// Prepend standard CSS classes to the element classes.
$attributes = $element->getAttributes();
$class = !empty($attributes['class']) ? ' ' . $attributes['class'] : '';
$attributes['class'] = $this->get_suggested_class($item) . $class;
$element->setAttributes($attributes);
// Add required rule.
if ($item->required && $addrequiredrule) {
$this->_form->addRule($element->getName(), get_string('required'), 'required', null, 'client');
}
// Set default value.
if ($setdefaultvalue && ($tmpvalue = $this->get_item_value($item))) {
$this->_form->setDefault($element->getName(), htmlspecialchars_decode($tmpvalue, ENT_QUOTES));
}
// Freeze if needed.
if ($this->is_frozen()) {
$element->freeze();
}
// Add red asterisks on required fields.
if ($item->required) {
$required = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
$element->setLabel($element->getLabel() . $required);
$this->hasrequired = true;
}
// Add different useful stuff to the question name.
$this->add_item_label($item, $element);
$this->add_item_dependencies($item, $element);
$this->add_item_number($item, $element);
if ($this->mode == self::MODE_EDIT) {
$this->enhance_name_for_edit($item, $element);
}
return $element;
}
/**
* Adds a group element to this form - to be used by items in their complete_form_element() method
*
* @param stdClass $item
* @param string $groupinputname name for the form element
* @param string $name question text
* @param array $elements array of arrays that can be passed to $this->_form->createElement()
* @param string $separator separator between group elements
* @param string $class additional CSS classes for the form element
* @return HTML_QuickForm_element
*/
public function add_form_group_element($item, $groupinputname, $name, $elements, $separator,
$class = '') {
$objects = array();
foreach ($elements as $element) {
$object = call_user_func_array(array($this->_form, 'createElement'), $element);
$objects[] = $object;
}
$element = $this->add_form_element($item,
['group', $groupinputname, $name, $objects, $separator, false],
false,
false);
if ($class !== '') {
$attributes = $element->getAttributes();
$attributes['class'] .= ' ' . $class;
$element->setAttributes($attributes);
}
return $element;
}
/**
* Adds an item number to the question name (if feedback autonumbering is on)
* @param stdClass $item
* @param HTML_QuickForm_element $element
*/
protected function add_item_number($item, $element) {
if ($this->get_feedback()->autonumbering && !empty($item->itemnr)) {
$name = $element->getLabel();
$element->setLabel(html_writer::span($item->itemnr. '.', 'itemnr') . ' ' . $name);
}
}
/**
* Adds an item label to the question name
* @param stdClass $item
* @param HTML_QuickForm_element $element
*/
protected function add_item_label($item, $element) {
if (strlen($item->label) && ($this->mode == self::MODE_EDIT || $this->mode == self::MODE_VIEW_TEMPLATE)) {
$name = get_string('nameandlabelformat', 'mod_feedback',
(object)['label' => format_string($item->label), 'name' => $element->getLabel()]);
$element->setLabel($name);
}
}
/**
* Adds a dependency description to the question name
* @param stdClass $item
* @param HTML_QuickForm_element $element
*/
protected function add_item_dependencies($item, $element) {
$allitems = $this->structure->get_items();
if ($item->dependitem && ($this->mode == self::MODE_EDIT || $this->mode == self::MODE_VIEW_TEMPLATE)) {
if (isset($allitems[$item->dependitem])) {
$dependitem = $allitems[$item->dependitem];
$name = $element->getLabel();
$name .= html_writer::span(' ('.format_string($dependitem->label).'-&gt;'.$item->dependvalue.')',
'feedback_depend');
$element->setLabel($name);
}
}
}
/**
* Returns the CSS id attribute that will be assigned by moodleform later to this element
* @param stdClass $item
* @param HTML_QuickForm_element $element
*/
protected function guess_element_id($item, $element) {
if (!$id = $element->getAttribute('id')) {
$attributes = $element->getAttributes();
$id = $attributes['id'] = 'feedback_item_' . $item->id;
$element->setAttributes($attributes);
}
if ($element->getType() === 'group') {
return 'fgroup_' . $id;
}
return 'fitem_' . $id;
}
/**
* Adds editing actions to the question name in the edit mode
* @param stdClass $item
* @param HTML_QuickForm_element $element
*/
protected function enhance_name_for_edit($item, $element) {
global $OUTPUT;
$menu = new action_menu();
$menu->set_owner_selector('#' . $this->guess_element_id($item, $element));
$menu->set_menu_trigger(get_string('edit'));
$menu->prioritise = true;
$itemobj = feedback_get_item_class($item->typ);
$actions = $itemobj->edit_actions($item, $this->get_feedback(), $this->get_cm());
foreach ($actions as $action) {
$menu->add($action);
}
$editmenu = $OUTPUT->render($menu);
$name = $element->getLabel();
$name = html_writer::span('', 'itemdd', array('id' => 'feedback_item_box_' . $item->id)) .
html_writer::span($name, 'itemname') .
html_writer::span($editmenu, 'itemactions');
$element->setLabel(html_writer::span($name, 'itemtitle'));
}
/**
* Sets the default value for form element - alias to $this->_form->setDefault()
* @param HTML_QuickForm_element|string $element
* @param mixed $defaultvalue
*/
public function set_element_default($element, $defaultvalue) {
if ($element instanceof HTML_QuickForm_element) {
$element = $element->getName();
}
$this->_form->setDefault($element, $defaultvalue);
}
/**
* Sets the default value for form element - wrapper to $this->_form->setType()
* @param HTML_QuickForm_element|string $element
* @param int $type
*/
public function set_element_type($element, $type) {
if ($element instanceof HTML_QuickForm_element) {
$element = $element->getName();
}
$this->_form->setType($element, $type);
}
/**
* Adds a validation rule for the given field - wrapper for $this->_form->addRule()
*
* Do not use for 'required' rule!
* Required * will be added automatically, if additional validation is needed
* use method {@link self::add_validation_rule()}
*
* @param string $element Form element name
* @param string $message Message to display for invalid data
* @param string $type Rule type, use getRegisteredRules() to get types
* @param string $format (optional)Required for extra rule data
* @param string $validation (optional)Where to perform validation: "server", "client"
* @param bool $reset Client-side validation: reset the form element to its original value if there is an error?
* @param bool $force Force the rule to be applied, even if the target form element does not exist
*/
public function add_element_rule($element, $message, $type, $format = null, $validation = 'server',
$reset = false, $force = false) {
if ($element instanceof HTML_QuickForm_element) {
$element = $element->getName();
}
$this->_form->addRule($element, $message, $type, $format, $validation, $reset, $force);
}
/**
* Adds a validation rule to the form
*
* @param callable $callback with arguments ($values, $files)
*/
public function add_validation_rule(callable $callback) {
if ($this->mode == self::MODE_COMPLETE) {
$this->_form->addFormRule($callback);
}
}
/**
* Returns a reference to the element - wrapper for function $this->_form->getElement()
*
* @param string $elementname Element name
* @return HTML_QuickForm_element reference to element
*/
public function get_form_element($elementname) {
return $this->_form->getElement($elementname);
}
/**
* Displays the form
*/
public function display() {
global $OUTPUT, $PAGE;
// Finalize the form definition if not yet done.
if (!$this->_definition_finalized) {
$this->_definition_finalized = true;
$this->definition_after_data();
}
$mform = $this->_form;
// Add "This form has required fields" text in the bottom of the form.
if (($mform->_required || $this->hasrequired) &&
($this->mode == self::MODE_COMPLETE || $this->mode == self::MODE_PRINT || $this->mode == self::MODE_VIEW_TEMPLATE)) {
$element = $mform->addElement('static', 'requiredfields', '',
get_string('somefieldsrequired', 'form',
$OUTPUT->pix_icon('req', get_string('requiredelement', 'form'))));
$element->setAttributes($element->getAttributes() + ['class' => 'requirednote']);
}
// Reset _required array so the default red * are not displayed.
$mform->_required = array();
// Move buttons to the end of the form.
if ($this->mode == self::MODE_COMPLETE) {
$mform->addElement('hidden', '__dummyelement');
$buttons = $mform->removeElement('buttonar', false);
$mform->insertElementBefore($buttons, '__dummyelement');
$mform->removeElement('__dummyelement');
}
$this->_form->display();
if ($this->mode == self::MODE_EDIT) {
$PAGE->requires->js_call_amd('mod_feedback/edit', 'setup');
}
}
}
+764
View File
@@ -0,0 +1,764 @@
<?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/>.
/**
* Contains class mod_feedback_completion
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Collects information and methods about feedback completion (either complete.php or show_entries.php)
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_completion extends mod_feedback_structure {
/** @var stdClass */
protected $completed;
/** @var stdClass */
protected $completedtmp = null;
/** @var stdClass[] */
protected $valuestmp = null;
/** @var stdClass[] */
protected $values = null;
/** @var bool */
protected $iscompleted = false;
/** @var mod_feedback_complete_form the form used for completing the feedback */
protected $form = null;
/** @var bool true when the feedback has been completed during the request */
protected $justcompleted = false;
/** @var int the next page the user should jump after processing the form */
protected $jumpto = null;
/**
* Constructor
*
* @param stdClass $feedback feedback object
* @param cm_info $cm course module object corresponding to the $feedback
* (at least one of $feedback or $cm is required)
* @param int $courseid current course (for site feedbacks only)
* @param bool $iscompleted has feedback been already completed? If yes either completedid or userid must be specified.
* @param int $completedid id in the table feedback_completed, may be omitted if userid is specified
* but it is highly recommended because the same user may have multiple responses to the same feedback
* for different courses
* @param int $nonanonymouseuserid - Return only anonymous results or specified user's results.
* If null only anonymous replies will be returned and the $completedid is mandatory.
* If specified only non-anonymous replies of $nonanonymouseuserid will be returned.
* @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
*/
public function __construct($feedback, $cm, $courseid, $iscompleted = false, $completedid = null,
$nonanonymouseuserid = null, $userid = 0) {
global $DB;
parent::__construct($feedback, $cm, $courseid, 0, $userid);
// Make sure courseid is always set for site feedback.
if ($this->feedback->course == SITEID && !$this->courseid) {
$this->courseid = SITEID;
}
if ($iscompleted) {
// Retrieve information about the completion.
$this->iscompleted = true;
$params = array('feedback' => $this->feedback->id);
if (!$nonanonymouseuserid && !$completedid) {
throw new coding_exception('Either $completedid or $nonanonymouseuserid must be specified for completed feedbacks');
}
if ($completedid) {
$params['id'] = $completedid;
}
if ($nonanonymouseuserid) {
// We must respect the anonymousity of the reply that the user saw when they were completing the feedback,
// not the current state that may have been changed later by the teacher.
$params['anonymous_response'] = FEEDBACK_ANONYMOUS_NO;
$params['userid'] = $nonanonymouseuserid;
}
$this->completed = $DB->get_record('feedback_completed', $params, '*', MUST_EXIST);
$this->courseid = $this->completed->courseid;
}
}
/**
* Returns a record from 'feedback_completed' table
* @return stdClass
*/
public function get_completed() {
return $this->completed;
}
/**
* Check if the feedback was just completed.
*
* @return bool true if the feedback was just completed.
* @since Moodle 3.3
*/
public function just_completed() {
return $this->justcompleted;
}
/**
* Return the jumpto property.
*
* @return int the next page to jump.
* @since Moodle 3.3
*/
public function get_jumpto() {
return $this->jumpto;
}
/**
* Returns the temporary completion record for the current user or guest session
*
* @return stdClass|false record from feedback_completedtmp or false if not found
*/
public function get_current_completed_tmp() {
global $DB, $USER;
if ($this->completedtmp === null) {
$params = array('feedback' => $this->get_feedback()->id);
if ($courseid = $this->get_courseid()) {
$params['courseid'] = $courseid;
}
if ((isloggedin() || $USER->id != $this->userid) && !isguestuser($this->userid)) {
$params['userid'] = $this->userid;
} else {
$params['guestid'] = sesskey();
}
$this->completedtmp = $DB->get_record('feedback_completedtmp', $params);
}
return $this->completedtmp;
}
/**
* Can the current user see the item, if dependency is met?
*
* @param stdClass $item
* @return bool whether user can see item or not,
* true if there is no dependency or dependency is met,
* false if dependent question is visible or broken
* and further it is either not answered or the dependency is not met,
* null if dependency is broken.
*/
protected function can_see_item($item) {
if (empty($item->dependitem)) {
return true;
}
if ($this->dependency_has_error($item)) {
return null;
}
$allitems = $this->get_items();
$ditem = $allitems[$item->dependitem];
$itemobj = feedback_get_item_class($ditem->typ);
if ($this->iscompleted) {
$value = $this->get_values($ditem);
} else {
$value = $this->get_values_tmp($ditem);
}
if ($value === null) {
// Cyclic dependencies are no problem here, since they will throw an dependency error above.
if ($this->can_see_item($ditem) === false) {
return false;
}
return null;
}
$check = $itemobj->compare_value($ditem, $value, $item->dependvalue) ? true : false;
if ($check) {
return $this->can_see_item($ditem);
}
return false;
}
/**
* Dependency condition has an error
* @param stdClass $item
* @return bool
*/
protected function dependency_has_error($item) {
if (empty($item->dependitem)) {
// No dependency - no error.
return false;
}
$allitems = $this->get_items();
if (!array_key_exists($item->dependitem, $allitems)) {
// Looks like dependent item has been removed.
return true;
}
$itemids = array_keys($allitems);
$index1 = array_search($item->dependitem, $itemids);
$index2 = array_search($item->id, $itemids);
if ($index1 >= $index2) {
// Dependent item is after the current item in the feedback.
return true;
}
for ($i = $index1 + 1; $i < $index2; $i++) {
if ($allitems[$itemids[$i]]->typ === 'pagebreak') {
return false;
}
}
// There are no page breaks between dependent items.
return true;
}
/**
* Returns a value stored for this item in the feedback (temporary or not, depending on the mode)
* @param stdClass $item
* @return string
*/
public function get_item_value($item) {
if ($this->iscompleted) {
return $this->get_values($item);
} else {
return $this->get_values_tmp($item);
}
}
/**
* Retrieves responses from an unfinished attempt.
*
* @return array the responses (from the feedback_valuetmp table)
* @since Moodle 3.3
*/
public function get_unfinished_responses() {
global $DB;
$responses = array();
$completedtmp = $this->get_current_completed_tmp();
if ($completedtmp) {
$responses = $DB->get_records('feedback_valuetmp', ['completed' => $completedtmp->id]);
}
return $responses;
}
/**
* Returns all temporary values for this feedback or just a value for an item
* @param stdClass $item
* @return array
*/
protected function get_values_tmp($item = null) {
global $DB;
if ($this->valuestmp === null) {
$this->valuestmp = array();
$responses = $this->get_unfinished_responses();
foreach ($responses as $r) {
$this->valuestmp[$r->item] = $r->value;
}
}
if ($item) {
return array_key_exists($item->id, $this->valuestmp) ? $this->valuestmp[$item->id] : null;
}
return $this->valuestmp;
}
/**
* Retrieves responses from an finished attempt.
*
* @return array the responses (from the feedback_value table)
* @since Moodle 3.3
*/
public function get_finished_responses() {
global $DB;
$responses = array();
if ($this->completed) {
$responses = $DB->get_records('feedback_value', ['completed' => $this->completed->id]);
}
return $responses;
}
/**
* Returns all completed values for this feedback or just a value for an item
* @param stdClass $item
* @return array
*/
protected function get_values($item = null) {
global $DB;
if ($this->values === null) {
$this->values = array();
$responses = $this->get_finished_responses();
foreach ($responses as $r) {
$this->values[$r->item] = $r->value;
}
}
if ($item) {
return array_key_exists($item->id, $this->values) ? $this->values[$item->id] : null;
}
return $this->values;
}
/**
* Splits the feedback items into pages
*
* Items that we definitely know at this stage as not applicable are excluded.
* Items that are dependent on something that has not yet been answered are
* still present, as well as items with broken dependencies.
*
* @return array array of arrays of items
*/
public function get_pages() {
$pages = [[]]; // The first page always exists.
$items = $this->get_items();
foreach ($items as $item) {
if ($item->typ === 'pagebreak') {
$pages[] = [];
} else if ($this->can_see_item($item) !== false) {
$pages[count($pages) - 1][] = $item;
}
}
return $pages;
}
/**
* Returns the last page that has items with the value (i.e. not label) which have been answered
* as well as the first page that has items with the values that have not been answered.
*
* Either of the two return values may be null if there are no answered page or there are no
* unanswered pages left respectively.
*
* Two pages may not be directly following each other because there may be empty pages
* or pages with information texts only between them
*
* @return array array of two elements [$lastcompleted, $firstincompleted]
*/
protected function get_last_completed_page() {
$completed = [];
$incompleted = [];
$pages = $this->get_pages();
foreach ($pages as $pageidx => $pageitems) {
foreach ($pageitems as $item) {
if ($item->hasvalue) {
if ($this->get_values_tmp($item) !== null) {
$completed[$pageidx] = true;
} else {
$incompleted[$pageidx] = true;
}
}
}
}
$completed = array_keys($completed);
$incompleted = array_keys($incompleted);
// If some page has both completed and incompleted items it is considered incompleted.
$completed = array_diff($completed, $incompleted);
// If the completed page follows an incompleted page, it does not count.
$firstincompleted = $incompleted ? min($incompleted) : null;
if ($firstincompleted !== null) {
$completed = array_filter($completed, function($a) use ($firstincompleted) {
return $a < $firstincompleted;
});
}
$lastcompleted = $completed ? max($completed) : null;
return [$lastcompleted, $firstincompleted];
}
/**
* Get the next page for the feedback
*
* This is normally $gopage+1 but may be bigger if there are empty pages or
* pages without visible questions.
*
* This method can only be called when questions on the current page are
* already answered, otherwise it may be inaccurate.
*
* @param int $gopage current page
* @param bool $strictcheck when gopage is the user-input value, make sure we do not jump over unanswered questions
* @return int|null the index of the next page or null if this is the last page
*/
public function get_next_page($gopage, $strictcheck = true) {
if ($strictcheck) {
list($lastcompleted, $firstincompleted) = $this->get_last_completed_page();
if ($firstincompleted !== null && $firstincompleted <= $gopage) {
return $firstincompleted;
}
}
$pages = $this->get_pages();
for ($pageidx = $gopage + 1; $pageidx < count($pages); $pageidx++) {
if (!empty($pages[$pageidx])) {
return $pageidx;
}
}
// No further pages in the feedback have any visible items.
return null;
}
/**
* Get the previous page for the feedback
*
* This is normally $gopage-1 but may be smaller if there are empty pages or
* pages without visible questions.
*
* @param int $gopage current page
* @param bool $strictcheck when gopage is the user-input value, make sure we do not jump over unanswered questions
* @return int|null the index of the next page or null if this is the first page with items
*/
public function get_previous_page($gopage, $strictcheck = true) {
if (!$gopage) {
// If we are already on the first (0) page, there is definitely no previous page.
return null;
}
$pages = $this->get_pages();
$rv = null;
// Iterate through previous pages and find the closest one that has any items on it.
for ($pageidx = $gopage - 1; $pageidx >= 0; $pageidx--) {
if (!empty($pages[$pageidx])) {
$rv = $pageidx;
break;
}
}
if ($rv === null) {
// We are on the very first page that has items.
return null;
}
if ($rv > 0 && $strictcheck) {
// Check if this page is actually not past than first incompleted page.
list($lastcompleted, $firstincompleted) = $this->get_last_completed_page();
if ($firstincompleted !== null && $firstincompleted < $rv) {
return $firstincompleted;
}
}
return $rv;
}
/**
* Page index to resume the feedback
*
* When user abandones answering feedback and then comes back to it we should send him
* to the first page after the last page he fully completed.
* @return int
*/
public function get_resume_page() {
list($lastcompleted, $firstincompleted) = $this->get_last_completed_page();
return $lastcompleted === null ? 0 : $this->get_next_page($lastcompleted, false);
}
/**
* Creates a new record in the 'feedback_completedtmp' table for the current user/guest session
*
* @return stdClass record from feedback_completedtmp or false if not found
*/
protected function create_current_completed_tmp() {
global $DB, $USER;
$record = (object)['feedback' => $this->feedback->id];
if ($this->get_courseid()) {
$record->courseid = $this->get_courseid();
}
if ((isloggedin() || $USER->id != $this->userid) && !isguestuser($this->userid)) {
$record->userid = $this->userid;
} else {
$record->guestid = sesskey();
}
$record->timemodified = time();
$record->anonymous_response = $this->feedback->anonymous;
$id = $DB->insert_record('feedback_completedtmp', $record);
$this->completedtmp = $DB->get_record('feedback_completedtmp', ['id' => $id]);
$this->valuestmp = null;
return $this->completedtmp;
}
/**
* If user has already completed the feedback, create the temproray values from last completed attempt
*
* @return stdClass record from feedback_completedtmp or false if not found
*/
public function create_completed_tmp_from_last_completed() {
if (!$this->get_current_completed_tmp()) {
$lastcompleted = $this->find_last_completed();
if ($lastcompleted) {
$this->completedtmp = feedback_set_tmp_values($lastcompleted);
}
}
return $this->completedtmp;
}
/**
* Saves unfinished response to the temporary table
*
* This is called when user proceeds to the next/previous page in the complete form
* and also right after the form submit.
* After the form submit the {@link save_response()} is called to
* move response from temporary table to completion table.
*
* @param stdClass $data data from the form mod_feedback_complete_form
*/
public function save_response_tmp($data) {
global $DB;
if (!$completedtmp = $this->get_current_completed_tmp()) {
$completedtmp = $this->create_current_completed_tmp();
} else {
$currentime = time();
$DB->update_record('feedback_completedtmp',
['id' => $completedtmp->id, 'timemodified' => $currentime]);
$completedtmp->timemodified = $currentime;
}
// Find all existing values.
$existingvalues = $DB->get_records_menu('feedback_valuetmp',
['completed' => $completedtmp->id], '', 'item, id');
// Loop through all feedback items and save the ones that are present in $data.
$allitems = $this->get_items();
foreach ($allitems as $item) {
if (!$item->hasvalue) {
continue;
}
$keyname = $item->typ . '_' . $item->id;
if (!isset($data->$keyname)) {
// This item is either on another page or dependency was not met - nothing to save.
continue;
}
$newvalue = ['item' => $item->id, 'completed' => $completedtmp->id, 'course_id' => $completedtmp->courseid];
// Convert the value to string that can be stored in 'feedback_valuetmp' or 'feedback_value'.
$itemobj = feedback_get_item_class($item->typ);
$newvalue['value'] = $itemobj->create_value($data->$keyname);
// Update or insert the value in the 'feedback_valuetmp' table.
if (array_key_exists($item->id, $existingvalues)) {
$newvalue['id'] = $existingvalues[$item->id];
$DB->update_record('feedback_valuetmp', $newvalue);
} else {
$DB->insert_record('feedback_valuetmp', $newvalue);
}
}
// Reset valuestmp cache.
$this->valuestmp = null;
}
/**
* Saves the response
*
* The form data has already been stored in the temporary table in
* {@link save_response_tmp()}. This function copies the values
* from the temporary table to the completion table.
* It is also responsible for sending email notifications when applicable.
*/
public function save_response() {
global $SESSION, $DB, $USER;
$feedbackcompleted = $this->find_last_completed();
// If no record is found, change false to null for safe use in feedback_save_tmp_values.
$feedbackcompleted = !$feedbackcompleted ? null : $feedbackcompleted;
$feedbackcompletedtmp = $this->get_current_completed_tmp();
if (feedback_check_is_switchrole()) {
// We do not actually save anything if the role is switched, just delete temporary values.
$this->delete_completedtmp();
return;
}
// Save values.
$completedid = feedback_save_tmp_values($feedbackcompletedtmp, $feedbackcompleted);
$this->completed = $DB->get_record('feedback_completed', array('id' => $completedid));
// Send email.
if ($this->feedback->anonymous == FEEDBACK_ANONYMOUS_NO) {
feedback_send_email($this->cm, $this->feedback, $this->cm->get_course(), $this->userid, $this->completed);
} else {
feedback_send_email_anonym($this->cm, $this->feedback, $this->cm->get_course());
}
unset($SESSION->feedback->is_started);
// Update completion state.
$completion = new completion_info($this->cm->get_course());
if ((isloggedin() || $USER->id != $this->userid) && $completion->is_enabled($this->cm) &&
$this->cm->completion == COMPLETION_TRACKING_AUTOMATIC && $this->feedback->completionsubmit) {
$completion->update_state($this->cm, COMPLETION_COMPLETE, $this->userid);
}
}
/**
* Deletes the temporary completed and all related temporary values
*/
protected function delete_completedtmp() {
global $DB;
if ($completedtmp = $this->get_current_completed_tmp()) {
$DB->delete_records('feedback_valuetmp', ['completed' => $completedtmp->id]);
$DB->delete_records('feedback_completedtmp', ['id' => $completedtmp->id]);
$this->completedtmp = null;
}
}
/**
* Retrieves the last completion record for the current user
*
* @return stdClass record from feedback_completed or false if not found
*/
public function find_last_completed() {
global $DB, $USER;
if ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid)) {
// Not possible to retrieve completed feedback for guests.
return false;
}
if ($this->is_anonymous()) {
// Not possible to retrieve completed anonymous feedback.
return false;
}
$params = array('feedback' => $this->feedback->id,
'userid' => $this->userid,
'anonymous_response' => FEEDBACK_ANONYMOUS_NO
);
if ($this->get_courseid()) {
$params['courseid'] = $this->get_courseid();
}
$this->completed = $DB->get_record('feedback_completed', $params);
return $this->completed;
}
/**
* Checks if user has capability to submit the feedback
*
* There is an exception for fully anonymous feedbacks when guests can complete
* feedback without the proper capability.
*
* This should be followed by checking {@link can_submit()} because even if
* user has capablity to complete, they may have already submitted feedback
* and can not re-submit
*
* @return bool
*/
public function can_complete() {
global $CFG, $USER;
$context = context_module::instance($this->cm->id);
if (has_capability('mod/feedback:complete', $context, $this->userid)) {
return true;
}
if (!empty($CFG->feedback_allowfullanonymous)
AND $this->feedback->course == SITEID
AND $this->feedback->anonymous == FEEDBACK_ANONYMOUS_YES
AND ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid))) {
// Guests are allowed to complete fully anonymous feedback without having 'mod/feedback:complete' capability.
return true;
}
return false;
}
/**
* Checks if user is prevented from re-submission.
*
* This must be called after {@link can_complete()}
*
* @return bool
*/
public function can_submit() {
if ($this->get_feedback()->multiple_submit == 0 ) {
if ($this->is_already_submitted()) {
return false;
}
}
return true;
}
/**
* Trigger module viewed event.
*
* @since Moodle 3.3
*/
public function trigger_module_viewed() {
$event = \mod_feedback\event\course_module_viewed::create_from_record($this->feedback, $this->cm, $this->cm->get_course());
$event->trigger();
}
/**
* Mark activity viewed for completion-tracking.
*
* @since Moodle 3.3
*/
public function set_module_viewed() {
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
$completion = new completion_info($this->cm->get_course());
$completion->set_module_viewed($this->cm, $this->userid);
}
/**
* Process a page jump via the mod_feedback_complete_form.
*
* This function initializes the form and process the submission.
*
* @param int $gopage the current page
* @param int $gopreviouspage if the user chose to go to the previous page
* @return string the url to redirect the user (if any)
* @since Moodle 3.3
*/
public function process_page($gopage, $gopreviouspage = false) {
global $CFG, $PAGE, $SESSION;
$urltogo = null;
// Save the form for later during the request.
$this->create_completed_tmp_from_last_completed();
$this->form = new mod_feedback_complete_form(mod_feedback_complete_form::MODE_COMPLETE,
$this, 'feedback_complete_form', array('gopage' => $gopage));
if ($this->form->is_cancelled()) {
// Form was cancelled - return to the course page.
$urltogo = new moodle_url('/mod/feedback/view.php', ['id' => $this->get_cm()->id]);
} else if ($this->form->is_submitted() &&
($this->form->is_validated() || $gopreviouspage)) {
// Form was submitted (skip validation for "Previous page" button).
$data = $this->form->get_submitted_data();
if (!isset($SESSION->feedback->is_started) OR !$SESSION->feedback->is_started == true) {
throw new \moodle_exception('error', '', $CFG->wwwroot.'/course/view.php?id='.$this->courseid);
}
$this->save_response_tmp($data);
if (!empty($data->savevalues) || !empty($data->gonextpage)) {
if (($nextpage = $this->get_next_page($gopage)) !== null) {
if ($PAGE->has_set_url()) {
$urltogo = new moodle_url($PAGE->url, array('gopage' => $nextpage));
}
$this->jumpto = $nextpage;
} else {
$this->save_response();
if (!$this->get_feedback()->page_after_submit) {
\core\notification::success(get_string('entries_saved', 'feedback'));
}
$this->justcompleted = true;
}
} else if (!empty($gopreviouspage)) {
$prevpage = intval($this->get_previous_page($gopage));
if ($PAGE->has_set_url()) {
$urltogo = new moodle_url($PAGE->url, array('gopage' => $prevpage));
}
$this->jumpto = $prevpage;
}
}
return $urltogo;
}
/**
* Render the form with the questions.
*
* @return string the form rendered
* @since Moodle 3.3
*/
public function render_items() {
global $SESSION;
// Print the items.
$SESSION->feedback->is_started = true;
return $this->form->render();
}
}
@@ -0,0 +1,82 @@
<?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/>.
declare(strict_types=1);
namespace mod_feedback\completion;
use core_completion\activity_custom_completion;
/**
* Activity custom completion subclass for the feedback activity.
*
* Class for defining mod_feedback's custom completion rules and fetching the completion statuses
* of the custom completion rules for a given feedback instance and a user.
*
* @package mod_feedback
* @copyright Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_completion extends activity_custom_completion {
/**
* Fetches the completion state for a given completion rule.
*
* @param string $rule The completion rule.
* @return int The completion state.
*/
public function get_state(string $rule): int {
global $DB;
$this->validate_rule($rule);
// Feedback only supports completionsubmit as a custom rule.
$status = $DB->record_exists('feedback_completed', ['feedback' => $this->cm->instance, 'userid' => $this->userid]);
return $status ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
}
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
public static function get_defined_custom_rules(): array {
return ['completionsubmit'];
}
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
public function get_custom_rule_descriptions(): array {
return [
'completionsubmit' => get_string('completiondetail:submit', 'feedback')
];
}
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
public function get_sort_order(): array {
return [
'completionview',
'completionsubmit',
];
}
}
+48
View File
@@ -0,0 +1,48 @@
<?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/>.
/**
* Contains class mod_feedback_course_map_form
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Form for mapping courses to the feedback
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_course_map_form extends moodleform {
/**
* Definition of the form
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$options = array('multiple' => true, 'includefrontpage' => true);
$mform->addElement('course', 'mappedcourses', get_string('courses'), $options);
$this->add_action_buttons();
}
}
@@ -0,0 +1,83 @@
<?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/>.
/**
* Contains class mod_feedback_course_map_form
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Form for mapping courses to the feedback
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_course_select_form extends moodleform {
/** @var moodle_url */
protected $action;
/** @var mod_feedback_structure $feedbackstructure */
protected $feedbackstructure;
/**
* Constructor
*
* @param string|moodle_url $action the action attribute for the form
* @param mod_feedback_structure $feedbackstructure
* @param bool $editable
*/
public function __construct($action, mod_feedback_structure $feedbackstructure, $editable = true) {
$this->action = new moodle_url($action, ['courseid' => null]);
$this->feedbackstructure = $feedbackstructure;
parent::__construct($action, null, 'post', '', ['id' => 'feedback_course_filter'], $editable);
}
/**
* Definition of the form
*/
public function definition() {
$mform = $this->_form;
$feedbackstructure = $this->feedbackstructure;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
if (!$this->_form->_freezeAll && ($courses = $feedbackstructure->get_completed_courses()) && count($courses) > 1) {
$elements = [];
$elements[] = $mform->createElement('autocomplete', 'courseid', get_string('filter_by_course', 'feedback'),
['' => get_string('fulllistofcourses')] + $courses);
$elements[] = $mform->createElement('submit', 'submitbutton', get_string('filter'));
if ($feedbackstructure->get_courseid()) {
$elements[] = $mform->createElement('static', 'showall', '',
html_writer::link($this->action, get_string('show_all', 'feedback')));
}
if (defined('BEHAT_SITE_RUNNING')) {
// TODO MDL-53734 remove this - behat does not recognise autocomplete element inside a group.
foreach ($elements as $element) {
$mform->addElement($element);
}
} else {
$mform->addGroup($elements, 'coursefilter', get_string('filter_by_course', 'feedback'), array(' '), false);
}
}
$this->set_data(['courseid' => $feedbackstructure->get_courseid(), 'id' => $feedbackstructure->get_cm()->id]);
}
}
+70
View File
@@ -0,0 +1,70 @@
<?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/>.
/**
* Contains the class for fetching the important dates in mod_feedback for a given module instance and a user.
*
* @package mod_feedback
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_feedback;
use core\activity_dates;
/**
* Class for fetching the important dates in mod_feedback for a given module instance and a user.
*
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dates extends activity_dates {
/**
* Returns a list of important dates in mod_feedback
*
* @return array
*/
protected function get_dates(): array {
$timeopen = $this->cm->customdata['timeopen'] ?? null;
$timeclose = $this->cm->customdata['timeclose'] ?? null;
$now = time();
$dates = [];
if ($timeopen) {
$openlabelid = $timeopen > $now ? 'activitydate:opens' : 'activitydate:opened';
$dates[] = [
'dataid' => 'timeopen',
'label' => get_string($openlabelid, 'course'),
'timestamp' => (int) $timeopen,
];
}
if ($timeclose) {
$closelabelid = $timeclose > $now ? 'activitydate:closes' : 'activitydate:closed';
$dates[] = [
'dataid' => 'timeclose',
'label' => get_string($closelabelid, 'course'),
'timestamp' => (int) $timeclose,
];
}
return $dates;
}
}
@@ -0,0 +1,38 @@
<?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/>.
/**
* The mod_feedback instance list viewed event.
*
* @package mod_feedback
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_feedback instance list viewed event class.
*
* @package mod_feedback
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
}
@@ -0,0 +1,120 @@
<?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/>.
/**
* The mod_feedback course module viewed event.
*
* @package mod_feedback
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_feedback course module viewed event class.
*
* @property-read array $other {
* Extra information about event.
*
* - int anonymous if feedback is anonymous.
* }
*
* @package mod_feedback
* @since Moodle 2.6
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'feedback';
}
/**
* Creates an instance from feedback record
*
* @param stdClass $feedback
* @param cm_info|stdClass $cm
* @param stdClass $course
* @return course_module_viewed
*/
public static function create_from_record($feedback, $cm, $course) {
$event = self::create(array(
'objectid' => $feedback->id,
'context' => \context_module::instance($cm->id),
'anonymous' => ($feedback->anonymous == FEEDBACK_ANONYMOUS_YES),
'other' => array(
'anonymous' => $feedback->anonymous // Deprecated.
)
));
$event->add_record_snapshot('course_modules', $cm);
$event->add_record_snapshot('course', $course);
$event->add_record_snapshot('feedback', $feedback);
return $event;
}
/**
* Define whether a user can view the event or not. Make sure no one except admin can see details of an anonymous response.
*
* @deprecated since 2.7
*
* @param int|\stdClass $userorid ID of the user.
* @return bool True if the user can view the event, false otherwise.
*/
public function can_view($userorid = null) {
global $USER;
debugging('can_view() method is deprecated, use anonymous flag instead if necessary.', DEBUG_DEVELOPER);
if (empty($userorid)) {
$userorid = $USER;
}
if ($this->anonymous) {
return is_siteadmin($userorid);
} else {
return has_capability('mod/feedback:viewreports', $this->context, $userorid);
}
}
/**
* Custom validations.
*
* @throws \coding_exception in case of any problems.
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['anonymous'])) {
throw new \coding_exception('The \'anonymous\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'feedback', 'restore' => 'feedback');
}
public static function get_other_mapping() {
// No need to map the 'anonymous' flag.
return false;
}
}
@@ -0,0 +1,158 @@
<?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/>.
/**
* The mod_feedback response deleted event.
*
* @package mod_feedback
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace mod_feedback\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_feedback response deleted event class.
*
* This event is triggered when a feedback response is deleted.
*
* @property-read array $other {
* Extra information about event.
*
* - int anonymous: if feedback is anonymous.
* - int cmid: course module id.
* - int instanceid: id of instance.
* }
*
* @package mod_feedback
* @since Moodle 2.6
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class response_deleted extends \core\event\base {
/**
* Set basic properties for the event.
*/
protected function init() {
$this->data['objecttable'] = 'feedback_completed';
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Creates an instance from the record from db table feedback_completed
*
* @param stdClass $completed
* @param stdClass|cm_info $cm
* @param stdClass $feedback
* @return self
*/
public static function create_from_record($completed, $cm, $feedback) {
$event = self::create(array(
'relateduserid' => $completed->userid,
'objectid' => $completed->id,
'courseid' => $cm->course,
'context' => \context_module::instance($cm->id),
'anonymous' => ($completed->anonymous_response == FEEDBACK_ANONYMOUS_YES),
'other' => array(
'cmid' => $cm->id,
'instanceid' => $feedback->id,
'anonymous' => $completed->anonymous_response) // Deprecated.
));
$event->add_record_snapshot('feedback_completed', $completed);
$event->add_record_snapshot('feedback', $feedback);
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventresponsedeleted', 'mod_feedback');
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the feedback for the user with id '$this->relateduserid' " .
"for the feedback activity with course module id '$this->contextinstanceid'.";
}
/**
* Define whether a user can view the event or not. Make sure no one except admin can see details of an anonymous response.
*
* @deprecated since 2.7
*
* @param int|\stdClass $userorid ID of the user.
* @return bool True if the user can view the event, false otherwise.
*/
public function can_view($userorid = null) {
global $USER;
debugging('can_view() method is deprecated, use anonymous flag instead if necessary.', DEBUG_DEVELOPER);
if (empty($userorid)) {
$userorid = $USER;
}
if ($this->anonymous) {
return is_siteadmin($userorid);
} else {
return has_capability('mod/feedback:viewreports', $this->context, $userorid);
}
}
/**
* Custom validations
*
* @throws \coding_exception in case of any problems.
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['anonymous'])) {
throw new \coding_exception('The \'anonymous\' value must be set in other.');
}
if (!isset($this->other['cmid'])) {
throw new \coding_exception('The \'cmid\' value must be set in other.');
}
if (!isset($this->other['instanceid'])) {
throw new \coding_exception('The \'instanceid\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'feedback_completed', 'restore' => 'feedback_completed');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['cmid'] = array('db' => 'course_modules', 'restore' => 'course_module');
$othermapped['instanceid'] = array('db' => 'feedback', 'restore' => 'feedback');
return $othermapped;
}
}
@@ -0,0 +1,172 @@
<?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/>.
/**
* The mod_feedback response submitted event.
*
* @package mod_feedback
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace mod_feedback\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_feedback response submitted event class.
*
* This event is triggered when a feedback response is submitted.
*
* @property-read array $other {
* Extra information about event.
*
* - int anonymous: if feedback is anonymous.
* - int cmid: course module id.
* - int instanceid: id of instance.
* }
*
* @package mod_feedback
* @since Moodle 2.6
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class response_submitted extends \core\event\base {
/**
* Set basic properties for the event.
*/
protected function init() {
global $CFG;
require_once($CFG->dirroot.'/mod/feedback/lib.php');
$this->data['objecttable'] = 'feedback_completed';
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Creates an instance from the record from db table feedback_completed
*
* @param stdClass $completed
* @param stdClass|cm_info $cm
* @return self
*/
public static function create_from_record($completed, $cm) {
$event = self::create(array(
'relateduserid' => $completed->userid,
'objectid' => $completed->id,
'context' => \context_module::instance($cm->id),
'anonymous' => ($completed->anonymous_response == FEEDBACK_ANONYMOUS_YES),
'other' => array(
'cmid' => $cm->id,
'instanceid' => $completed->feedback,
'anonymous' => $completed->anonymous_response // Deprecated.
)
));
$event->add_record_snapshot('feedback_completed', $completed);
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventresponsesubmitted', 'mod_feedback');
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' submitted response for 'feedback' activity with "
. "course module id '$this->contextinstanceid'.";
}
/**
* Returns relevant URL based on the anonymous mode of the response.
* @return \moodle_url
*/
public function get_url() {
if ($this->anonymous) {
return new \moodle_url('/mod/feedback/show_entries.php', array('id' => $this->other['cmid'],
'showcompleted' => $this->objectid));
} else {
return new \moodle_url('/mod/feedback/show_entries.php' , array('id' => $this->other['cmid'],
'userid' => $this->userid, 'showcompleted' => $this->objectid));
}
}
/**
* Define whether a user can view the event or not. Make sure no one except admin can see details of an anonymous response.
*
* @deprecated since 2.7
*
* @param int|\stdClass $userorid ID of the user.
* @return bool True if the user can view the event, false otherwise.
*/
public function can_view($userorid = null) {
global $USER;
debugging('can_view() method is deprecated, use anonymous flag instead if necessary.', DEBUG_DEVELOPER);
if (empty($userorid)) {
$userorid = $USER;
}
if ($this->anonymous) {
return is_siteadmin($userorid);
} else {
return has_capability('mod/feedback:viewreports', $this->context, $userorid);
}
}
/**
* Custom validations.
*
* @throws \coding_exception in case of any problems.
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['anonymous'])) {
throw new \coding_exception('The \'anonymous\' value must be set in other.');
}
if (!isset($this->other['cmid'])) {
throw new \coding_exception('The \'cmid\' value must be set in other.');
}
if (!isset($this->other['instanceid'])) {
throw new \coding_exception('The \'instanceid\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'feedback_completed', 'restore' => 'feedback_completed');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['cmid'] = array('db' => 'course_modules', 'restore' => 'course_module');
$othermapped['instanceid'] = array('db' => 'feedback', 'restore' => 'feedback');
return $othermapped;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,74 @@
<?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/>.
/**
* Class for exporting a feedback completion record.
*
* @package mod_feedback
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for exporting a feedback completion record.
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class feedback_completed_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array list of properties
*/
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The record id.',
),
'feedback' => array(
'type' => PARAM_INT,
'description' => 'The feedback instance id this records belongs to.',
),
'userid' => array(
'type' => PARAM_INT,
'description' => 'The user who completed the feedback (0 for anonymous).',
),
'timemodified' => array(
'type' => PARAM_INT,
'description' => 'The last time the feedback was completed.',
),
'random_response' => array(
'type' => PARAM_INT,
'description' => 'The response number (used when shuffling anonymous responses).',
),
'anonymous_response' => array(
'type' => PARAM_INT,
'description' => 'Whether is an anonymous response.',
),
'courseid' => array(
'type' => PARAM_INT,
'description' => 'The course id where the feedback was completed.',
),
);
}
}
@@ -0,0 +1,78 @@
<?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/>.
/**
* Class for exporting a feedback temporary completion record.
*
* @package mod_feedback
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for exporting a feedback temporary completion record.
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class feedback_completedtmp_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array list of properties
*/
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The record id.',
),
'feedback' => array(
'type' => PARAM_INT,
'description' => 'The feedback instance id this records belongs to.',
),
'userid' => array(
'type' => PARAM_INT,
'description' => 'The user who completed the feedback (0 for anonymous).',
),
'guestid' => array(
'type' => PARAM_RAW,
'description' => 'For guests, this is the session key.',
),
'timemodified' => array(
'type' => PARAM_INT,
'description' => 'The last time the feedback was completed.',
),
'random_response' => array(
'type' => PARAM_INT,
'description' => 'The response number (used when shuffling anonymous responses).',
),
'anonymous_response' => array(
'type' => PARAM_INT,
'description' => 'Whether is an anonymous response.',
),
'courseid' => array(
'type' => PARAM_INT,
'description' => 'The course id where the feedback was completed.',
),
);
}
}
+177
View File
@@ -0,0 +1,177 @@
<?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/>.
/**
* Class for exporting a feedback item (question).
*
* @package mod_feedback
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use core_files\external\stored_file_exporter;
/**
* Class for exporting a feedback item (question).
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class feedback_item_exporter extends exporter {
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The record id.',
),
'feedback' => array(
'type' => PARAM_INT,
'description' => 'The feedback instance id this records belongs to.',
'default' => 0,
),
'template' => array(
'type' => PARAM_INT,
'description' => 'If it belogns to a template, the template id.',
'default' => 0,
),
'name' => array(
'type' => PARAM_RAW,
'description' => 'The item name.',
),
'label' => array(
'type' => PARAM_NOTAGS,
'description' => 'The item label.',
),
'presentation' => array(
'type' => PARAM_RAW,
'description' => 'The text describing the item or the available possible answers.',
),
'typ' => array(
'type' => PARAM_ALPHA,
'description' => 'The type of the item.',
),
'hasvalue' => array(
'type' => PARAM_INT,
'description' => 'Whether it has a value or not.',
'default' => 0,
),
'position' => array(
'type' => PARAM_INT,
'description' => 'The position in the list of questions.',
'default' => 0,
),
'required' => array(
'type' => PARAM_BOOL,
'description' => 'Whether is a item (question) required or not.',
'default' => 0,
),
'dependitem' => array(
'type' => PARAM_INT,
'description' => 'The item id this item depend on.',
'default' => 0,
),
'dependvalue' => array(
'type' => PARAM_RAW,
'description' => 'The depend value.',
),
'options' => array(
'type' => PARAM_ALPHA,
'description' => 'Different additional settings for the item (question).',
),
);
}
protected static function define_related() {
return array(
'context' => 'context',
'itemnumber' => 'int?'
);
}
protected static function define_other_properties() {
return array(
'itemfiles' => array(
'type' => stored_file_exporter::read_properties_definition(),
'multiple' => true
),
'itemnumber' => array(
'type' => PARAM_INT,
'description' => 'The item position number',
'null' => NULL_ALLOWED
),
'otherdata' => array(
'type' => PARAM_RAW,
'description' => 'Additional data that may be required by external functions',
'null' => NULL_ALLOWED
),
);
}
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$itemobj = feedback_get_item_class($this->data->typ);
$values = array(
'itemfiles' => array(),
'itemnumber' => $this->related['itemnumber'],
'otherdata' => $itemobj->get_data_for_external($this->data),
);
$fs = get_file_storage();
$files = array();
$itemfiles = $fs->get_area_files($context->id, 'mod_feedback', 'item', $this->data->id, 'filename', false);
if (!empty($itemfiles)) {
foreach ($itemfiles as $storedfile) {
$fileexporter = new stored_file_exporter($storedfile, array('context' => $context));
$files[] = $fileexporter->export($output);
}
$values['itemfiles'] = $files;
}
return $values;
}
/**
* Get the formatting parameters for the name.
*
* @return array
*/
protected function get_format_parameters_for_name() {
return [
'component' => 'mod_feedback',
'filearea' => 'item',
'itemid' => $this->data->id
];
}
/**
* Get the formatting parameters for the presentation.
*
* @return array
*/
protected function get_format_parameters_for_presentation() {
return [
'component' => 'mod_feedback',
'filearea' => 'item',
'itemid' => $this->data->id
];
}
}
@@ -0,0 +1,190 @@
<?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_feedback\external;
use core\external\exporter;
use renderer_base;
use core_external\util as external_util;
use core_external\external_files;
/**
* Class for exporting partial feedback data (some fields are only viewable by admins).
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package mod_feedback
*/
class feedback_summary_exporter extends exporter {
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The primary key of the record.',
),
'course' => array(
'type' => PARAM_INT,
'description' => 'Course id this feedback is part of.',
),
'name' => array(
'type' => PARAM_TEXT,
'description' => 'Feedback name.',
),
'intro' => array(
'default' => '',
'type' => PARAM_RAW,
'description' => 'Feedback introduction text.',
),
'introformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Feedback intro text format.',
),
'lang' => array(
'type' => PARAM_LANG,
'description' => 'Forced activity language',
'null' => NULL_ALLOWED,
),
'anonymous' => array(
'type' => PARAM_INT,
'description' => 'Whether the feedback is anonymous.',
),
'email_notification' => array(
'type' => PARAM_BOOL,
'optional' => true,
'description' => 'Whether email notifications will be sent to teachers.',
),
'multiple_submit' => array(
'default' => 1,
'type' => PARAM_BOOL,
'description' => 'Whether multiple submissions are allowed.',
),
'autonumbering' => array(
'default' => 1,
'type' => PARAM_BOOL,
'description' => 'Whether questions should be auto-numbered.',
),
'site_after_submit' => array(
'type' => PARAM_TEXT,
'optional' => true,
'description' => 'Link to next page after submission.',
),
'page_after_submit' => array(
'type' => PARAM_RAW,
'optional' => true,
'description' => 'Text to display after submission.',
),
'page_after_submitformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Text to display after submission format.',
),
'publish_stats' => array(
'default' => 0,
'type' => PARAM_BOOL,
'description' => 'Whether stats should be published.',
),
'timeopen' => array(
'type' => PARAM_INT,
'optional' => true,
'description' => 'Allow answers from this time.',
),
'timeclose' => array(
'type' => PARAM_INT,
'optional' => true,
'description' => 'Allow answers until this time.',
),
'timemodified' => array(
'type' => PARAM_INT,
'optional' => true,
'description' => 'The time this record was modified.',
),
'completionsubmit' => array(
'default' => 0,
'type' => PARAM_BOOL,
'description' => 'If this field is set to 1, then the activity will be automatically marked as complete on submission.',
),
);
}
protected static function define_related() {
return array(
'context' => 'context'
);
}
protected static function define_other_properties() {
return array(
'coursemodule' => array(
'type' => PARAM_INT
),
'introfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true
),
'pageaftersubmitfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
'optional' => true
),
);
}
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$values = array(
'coursemodule' => $context->instanceid,
);
$values['introfiles'] = external_util::get_area_files($context->id, 'mod_feedback', 'intro', false, false);
if (!empty($this->data->page_after_submit)) {
$values['pageaftersubmitfiles'] = external_util::get_area_files($context->id, 'mod_feedback', 'page_after_submit');
}
return $values;
}
/**
* Get the formatting parameters for the intro.
*
* @return array
*/
protected function get_format_parameters_for_intro() {
return [
'component' => 'mod_feedback',
'filearea' => 'intro',
'options' => array('noclean' => true),
];
}
/**
* Get the formatting parameters for the page_after_submit.
*
* @return array
*/
protected function get_format_parameters_for_page_after_submit() {
return [
'component' => 'mod_feedback',
'filearea' => 'page_after_submit',
'itemid' => 0
];
}
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* Class for exporting a feedback response.
*
* @package mod_feedback
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for exporting a feedback response.
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class feedback_value_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array list of properties
*/
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The record id.',
),
'course_id' => array(
'type' => PARAM_INT,
'description' => 'The course id this record belongs to.',
),
'item' => array(
'type' => PARAM_INT,
'description' => 'The item id that was responded.',
),
'completed' => array(
'type' => PARAM_INT,
'description' => 'Reference to the feedback_completed table.',
),
'tmp_completed' => array(
'type' => PARAM_INT,
'description' => 'Old field - not used anymore.',
),
'value' => array(
'type' => PARAM_RAW,
'description' => 'The response value.',
),
);
}
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* Class for exporting a feedback tmp response.
*
* @package mod_feedback
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for exporting a feedback tmp response.
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class feedback_valuetmp_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array list of properties
*/
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The record id.',
),
'course_id' => array(
'type' => PARAM_INT,
'description' => 'The course id this record belongs to.',
),
'item' => array(
'type' => PARAM_INT,
'description' => 'The item id that was responded.',
),
'completed' => array(
'type' => PARAM_INT,
'description' => 'Reference to the feedback_completedtmp table.',
),
'tmp_completed' => array(
'type' => PARAM_INT,
'description' => 'Old field - not used anymore.',
),
'value' => array(
'type' => PARAM_RAW,
'description' => 'The response value.',
),
);
}
}
@@ -0,0 +1,115 @@
<?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_feedback\form;
use core_form\dynamic_form;
use moodle_url;
use context;
use context_module;
use context_system;
/**
* Prints the create new template form
*
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package mod_feedback
*/
class create_template_form extends dynamic_form {
/**
* Define the form
*/
public function definition() {
$mform =& $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('text',
'templatename',
get_string('name', 'feedback'),
['maxlength' => '200', 'size' => '50']);
$mform->setType('templatename', PARAM_TEXT);
if (has_capability('mod/feedback:createpublictemplate', context_system::instance())) {
$mform->addElement('checkbox',
'ispublic', '',
get_string('public', 'feedback'));
}
}
/**
* Returns context where this form is used
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
$id = $this->optional_param('id', null, PARAM_INT);
list($course, $cm) = get_course_and_cm_from_cmid($id, 'feedback');
return context_module::instance($cm->id);
}
/**
* Checks if current user has access to this form, otherwise throws exception
*
* @throws \moodle_exception User does not have capability to access the form
*/
protected function check_access_for_dynamic_submission(): void {
$context = $this->get_context_for_dynamic_submission();
if (!has_capability('mod/feedback:edititems', $context) ||
!(has_capability('mod/feedback:createprivatetemplate', $context) ||
has_capability('mod/feedback:createpublictemplate', $context))) {
throw new \moodle_exception('nocapabilitytousethisservice');
}
}
/**
* Process the form submission, used if form was submitted via AJAX
*
* @return array Returns whether a new template was created.
*/
public function process_dynamic_submission(): array {
global $PAGE;
$formdata = $this->get_data();
$ispublic = !empty($formdata->ispublic) ? 1 : 0;
$result = feedback_save_as_template($PAGE->activityrecord, $formdata->templatename, $ispublic);
return [
'result' => $result,
];
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$this->set_data((object)[
'id' => $this->optional_param('id', null, PARAM_INT),
]);
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
$params = [
'id' => $this->optional_param('id', null, PARAM_INT),
];
return new moodle_url('/mod/feedback/edit.php', $params);
}
}
@@ -0,0 +1,120 @@
<?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_feedback\form;
use core_form\dynamic_form;
use moodle_url;
use context;
use context_module;
/**
* Prints the confirm use template form
*
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package mod_feedback
*/
class use_template_form extends dynamic_form {
/**
* Define the form
*/
public function definition() {
$mform =& $this->_form;
$mform->addElement('static', 'generalheader', '', get_string("whatfor", 'feedback'));
$mform->addElement('radio', 'deleteolditems', '', get_string('delete_old_items', 'feedback'), 1);
$mform->addElement('radio', 'deleteolditems', '', get_string('append_new_items', 'feedback'), 0);
$mform->setType('deleteolditems', PARAM_INT);
$mform->setDefault('deleteolditems', 1);
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'templateid');
$mform->setType('templateid', PARAM_INT);
}
/**
* Returns context where this form is used
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
$id = $this->optional_param('id', null, PARAM_INT);
list($course, $cm) = get_course_and_cm_from_cmid($id, 'feedback');
return context_module::instance($cm->id);
}
/**
* Checks if current user has access to this form, otherwise throws exception
*
* @throws \moodle_exception User does not have capability to access the form
*/
protected function check_access_for_dynamic_submission(): void {
if (!has_capability('mod/feedback:edititems', $this->get_context_for_dynamic_submission())) {
throw new \moodle_exception('nocapabilitytousethisservice');
}
}
/**
* Process the form submission, used if form was submitted via AJAX
*
* @return array Returns the following information
* - the template was successfully created/updated from the provided template
* - the redirect url.
*/
public function process_dynamic_submission(): array {
global $PAGE;
$formdata = $this->get_data();
$templateid = $this->optional_param('templateid', null, PARAM_INT);
$id = $this->optional_param('id', null, PARAM_INT);
$response = feedback_items_from_template($PAGE->activityrecord, $templateid, $formdata->deleteolditems);
$url = new moodle_url('/mod/feedback/edit.php', ['id' => $id]);
if ($response !== false) {
// Provide a notification on success as the user will be redirected.
\core\notification::add(get_string('feedbackupdated', 'feedback'), \core\notification::SUCCESS);
}
return [
'result' => $response !== false,
'url' => $url->out()
];
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$this->set_data((object)[
'id' => $this->optional_param('id', null, PARAM_INT),
'templateid' => $this->optional_param('templateid', null, PARAM_INT)
]);
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
$params = [
'id' => $this->optional_param('id', null, PARAM_INT),
'templateid' => $this->optional_param('templateid', null, PARAM_INT)
];
return new moodle_url('/mod/feedback/use_templ.php', $params);
}
}
@@ -0,0 +1,83 @@
<?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_feedback\navigation\views;
use core\navigation\views\secondary as core_secondary;
use settings_navigation;
use navigation_node;
/**
* Custom secondary navigation class
*
* A custom construct of secondary nav for feedback. This rearranges the nodes for the secondary
*
* @package mod_feedback
* @category navigation
* @copyright 2021 onwards Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class secondary extends core_secondary {
protected function get_default_module_mapping(): array {
$basenodes = parent::get_default_module_mapping();
$basenodes[self::TYPE_CUSTOM] += [
'templatenode' => 12,
'mapcourse' => 13,
'feedbackanalysis' => 14,
'responses' => 15,
'nonrespondents' => 15.1
];
return $basenodes;
}
/**
* Custom module construct for feedback
*
* @param settings_navigation $settingsnav The settings navigation object related to the module page
* @param navigation_node|null $rootnode The node where the module navigation nodes should be added into as children.
* If not explicitly defined, the nodes will be added to the secondary root
* node by default.
*/
protected function load_module_navigation(settings_navigation $settingsnav, ?navigation_node $rootnode = null): void {
$rootnode = $rootnode ?? $this;
$mainnode = $settingsnav->find('modulesettings', self::TYPE_SETTING);
$nodes = $this->get_default_module_mapping();
if ($mainnode) {
$url = new \moodle_url('/mod/' . $this->page->activityname . '/view.php', ['id' => $this->page->cm->id]);
$setactive = $url->compare($this->page->url, URL_MATCH_BASE);
$node = $rootnode->add(get_string('modulename', 'feedback'), $url, null, null, 'modulepage');
if ($setactive) {
$node->make_active();
}
// Add the initial nodes.
$nodesordered = $this->get_leaf_nodes($mainnode, $nodes);
$this->add_ordered_nodes($nodesordered, $rootnode);
// Reorder the existing nodes in settings so the active node scan can pick it up.
$existingnode = $settingsnav->find('questionnode', self::TYPE_CUSTOM);
if ($existingnode) {
$node->add_node($existingnode);
$nodes[self::TYPE_CUSTOM] += ['questionnode' => 3];
}
// We have finished inserting the initial structure.
// Populate the menu with the rest of the nodes available.
$this->load_remaining_nodes($mainnode, $nodes, $rootnode);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Event observers supported by this module
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Event observers supported by this module
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_observer {
/**
* Observer for the even course_content_deleted - delete all course templates.
*
* @param \core\event\course_content_deleted $event
*/
public static function course_content_deleted(\core\event\course_content_deleted $event) {
global $DB;
// Delete all templates of given course.
$DB->delete_records('feedback_template', array('course' => $event->objectid));
}
}
@@ -0,0 +1,101 @@
<?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_feedback\output;
use context_module;
use renderable;
use renderer_base;
use templatable;
/**
* Class base_action_bar
*
* Base class to be inherited by any other feedback action bar
*
* @package mod_feedback
* @copyright 2021 onwards Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base_action_bar implements renderable, templatable {
/** @var int $cmid The module id */
protected $cmid;
/** @var object $context The context we are in */
protected $context;
/** @var object $course The course we are in */
protected $course;
/** @var array $urlparams The default params to be used when creating urls */
protected $urlparams;
/** @var object $feedback The activity record that is being viewed */
protected $feedback;
/**
* base_action_bar constructor.
*
* @param int $cmid
*/
public function __construct(int $cmid) {
global $PAGE;
$this->cmid = $cmid;
$this->context = context_module::instance($cmid);
[$course, $cm] = get_course_and_cm_from_cmid($cmid);
$this->course = $course;
$this->urlparams = [
'id' => $cmid
];
$this->feedback = $PAGE->activityrecord;
}
/**
* Recursively iterates through to array of renderables and exports
*
* @param array $items Collection of renderables
* @param renderer_base $output
* @return array $items Data to be used in the mustache template
*/
private function export_items_for_template(array $items, renderer_base $output): array {
$items = array_map(function($item) use ($output) {
if (is_array($item)) {
return $this->export_items_for_template($item, $output);
}
if (is_object($item) && method_exists($item, 'export_for_template')) {
return $item->export_for_template($output);
}
return $item;
}, $items);
return $items;
}
/**
* Export the data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$items = $this->export_items_for_template($this->get_items(), $output);
return $items;
}
/**
* Function to generate a list of renderables to be displayed
* @return array
*/
abstract protected function get_items(): array;
}
@@ -0,0 +1,100 @@
<?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_feedback\output;
use moodle_url;
use action_link;
use single_select;
use url_select;
/**
* Class actionbar - Display the action bar
*
* @package mod_feedback
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class edit_action_bar extends base_action_bar {
/** @var moodle_url $currenturl The current page url */
private $currenturl;
/** @var int|null $lastposition The index of the last question type in the feedback module */
private $lastposition;
/**
* edit_action_bar constructor.
*
* @param int $cmid The course module id
* @param moodle_url $pageurl The current page url
* @param int|null $lastposition Index of the last question in the feedback
*/
public function __construct(int $cmid, moodle_url $pageurl, ?int $lastposition = null) {
parent::__construct($cmid);
$this->currenturl = $pageurl;
$this->lastposition = $lastposition;
}
/**
* Return the items to be used for the tertiary nav
*
* @return array
*/
public function get_items(): array {
global $DB;
$url = new moodle_url('/mod/feedback/view.php', ['id' => $this->cmid]);
$items['left'][]['actionlink'] = new action_link($url, get_string('back'), null, ['class' => 'btn btn-secondary']);
if (has_capability('mod/feedback:edititems', $this->context)) {
$editurl = new moodle_url('/mod/feedback/edit.php', $this->urlparams);
$templateurl = new moodle_url('/mod/feedback/manage_templates.php', $this->urlparams);
$importurl = new moodle_url('/mod/feedback/import.php', $this->urlparams);
$options = [
$editurl->out(false) => get_string('add_item', 'feedback'),
$templateurl->out(false) => get_string('using_templates', 'feedback'),
$importurl->out(false) => get_string('import_questions', 'feedback')
];
$selected = $this->currenturl;
// Template pages can have sub pages, so match these.
if ($this->currenturl->compare(new moodle_url('/mod/feedback/use_templ.php'), URL_MATCH_BASE)) {
$selected = $templateurl;
}
$items['left'][]['urlselect'] = new url_select($options, $selected->out(false), null);
$viewquestions = $editurl->compare($this->currenturl);
if ($viewquestions) {
$select = new single_select(new moodle_url('/mod/feedback/edit_item.php',
['cmid' => $this->cmid, 'position' => $this->lastposition, 'sesskey' => sesskey()]),
'typ', feedback_load_feedback_items_options());
$items['left'][]['singleselect'] = $select;
if ($DB->record_exists('feedback_item', ['feedback' => $this->feedback->id])) {
$items['export'] = new action_link(
new moodle_url('/mod/feedback/export.php', $this->urlparams + ['action' => 'exportfile']),
get_string('export_questions', 'feedback'),
null,
['class' => 'btn btn-secondary'],
);
}
}
}
return $items;
}
}
@@ -0,0 +1,86 @@
<?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_feedback\output;
use confirm_action;
use context_system;
use moodle_url;
use action_link;
/**
* Class actionbar - Display the action bar
*
* @package mod_feedback
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class edit_template_action_bar extends base_action_bar {
/** @var int $templateid The template that is being edited/used */
private $templateid;
/** @var string $mode The type of view we are dealing with */
private $mode;
/**
* edit_template_action_bar constructor.
* @param int $cmid
* @param int $templateid
* @param string $mode
*/
public function __construct(int $cmid, int $templateid, string $mode) {
parent::__construct($cmid);
$this->templateid = $templateid;
$this->mode = $mode;
}
/**
* Return the items to be used in the tertiary nav
*
* @return array
*/
public function get_items(): array {
global $DB;
$additionalparams = ($this->mode ? ['mode' => $this->mode] : []);
$templateurl = new moodle_url('/mod/feedback/manage_templates.php', $this->urlparams + $additionalparams);
$items['left'][]['actionlink'] = new action_link($templateurl, get_string('back'), null, ['class' => 'btn btn-secondary']);
if (has_capability('mod/feedback:edititems', $this->context)) {
$items['usetemplate'] = $this->urlparams + [
'templateid' => $this->templateid
];
}
$template = $DB->get_record('feedback_template', array('id' => $this->templateid), '*', MUST_EXIST);
$systemcontext = context_system::instance();
$showdelete = has_capability('mod/feedback:deletetemplate', $this->context);
if ($template->ispublic) {
$showdelete = has_capability('mod/feedback:createpublictemplate', $systemcontext) &&
has_capability('mod/feedback:deletetemplate', $systemcontext);
}
if ($showdelete) {
$params = $this->urlparams + $additionalparams + [
'deletetemplate' => $this->templateid,
'sesskey' => sesskey()
];
$deleteurl = new moodle_url('/mod/feedback/manage_templates.php', $params);
$deleteaction = new confirm_action(get_string('confirmdeletetemplate', 'feedback'));
$items['export'] = new action_link($deleteurl, get_string('delete'), $deleteaction, ['class' => 'btn btn-secondary']);
}
return $items;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?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_feedback\output;
use plugin_renderer_base;
/**
* Class renderer
*
* @package mod_feedback
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Generate the tertiary nav
*
* @param base_action_bar $actionmenu
* @return bool|string
*/
public function main_action_bar(base_action_bar $actionmenu) {
$context = $actionmenu->export_for_template($this);
return $this->render_from_template('mod_feedback/main_action_menu', $context);
}
/**
* Render the create template form
*
* @param int $id
* @return bool|string
*/
public function create_template_form(int $id) {
return $this->render_from_template('mod_feedback/create_template', ['id' => $id]);
}
}
@@ -0,0 +1,72 @@
<?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_feedback\output;
use moodle_url;
use url_select;
/**
* Class responses_action_bar. The tertiary nav for the responses page
*
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package mod_feedback
*/
class responses_action_bar extends base_action_bar {
/** @var moodle_url $currenturl The current page url */
private $currenturl;
/**
* responses_action_bar constructor.
*
* @param int $cmid The cmid for the module we are operating on
* @param moodle_url $pageurl The current page url
*/
public function __construct(int $cmid, moodle_url $pageurl) {
parent::__construct($cmid);
$this->currenturl = $pageurl;
$this->urlparams['courseid'] = $this->course->id;
}
/**
* Return the items to be used in the tertiary nav
*
* @return array
*/
public function get_items(): array {
$items = [];
if (has_capability('mod/feedback:viewreports', $this->context)) {
$reporturl = new moodle_url('/mod/feedback/show_entries.php', $this->urlparams);
$options[$reporturl->out(false)] = get_string('show_entries', 'feedback');
$selected = $this->currenturl->compare($reporturl, URL_MATCH_BASE) ? $reporturl : $this->currenturl;
if ($this->feedback->anonymous == FEEDBACK_ANONYMOUS_NO && $this->course->id != SITEID) {
$nonrespondenturl = new moodle_url('/mod/feedback/show_nonrespondents.php', $this->urlparams);
$options[$nonrespondenturl->out(false)] = get_string('show_nonrespondents', 'feedback');
$selected = $this->currenturl->compare($nonrespondenturl, URL_MATCH_BASE) ? $nonrespondenturl : $this->currenturl;;
}
// Don't show the dropdown if it's only a single item.
if (count($options) != 1) {
$items['left'][]['urlselect'] = new url_select($options,
$selected->out(false),
null);
}
}
return $items;
}
}
@@ -0,0 +1,100 @@
<?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_feedback\output;
use action_link;
use moodle_url;
/**
* Class standard_action_bar
*
* The default tertiary nav on the module landing page
*
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package mod_feedback
*/
class standard_action_bar extends base_action_bar {
/** @var int $startpage The page to resume with. */
private $startpage;
/** @var int $viewcompletion Whether or not the user can finish the feedback */
private $viewcompletion;
/**
* standard_action_bar constructor.
*
* @param int $cmid
* @param bool $viewcompletion Whether or not the user can finish the feedback
* @param int|null $startpage The page to resume with.
* @param int|null $courseid The course that the feedback is being accessed from. If null, courseid will be
* set via the $cmid relationship
*/
public function __construct(int $cmid, bool $viewcompletion, ?int $startpage = null, ?int $courseid = null) {
parent::__construct($cmid);
$this->startpage = $startpage;
$this->viewcompletion = $viewcompletion;
if ($courseid && $courseid != $this->course->id) {
$this->course = get_course($courseid);
}
$this->urlparams['courseid'] = $this->course->id;
}
/**
* Return the items to be used in the tertiary nav
*
* @return array
*/
public function get_items(): array {
$items = [];
if (has_capability('mod/feedback:edititems', $this->context)) {
$editurl = new moodle_url('/mod/feedback/edit.php', $this->urlparams);
$items['left'][]['actionlink'] = new action_link($editurl, get_string('edit_items', 'feedback'),
null, ['class' => 'btn btn-secondary']);
}
// The preview icon should be displayed only to users with capability to edit or view reports (to include
// non-editing teachers too).
$capabilities = [
'mod/feedback:edititems',
'mod/feedback:viewreports',
];
if (has_any_capability($capabilities, $this->context)) {
$previewlnk = new moodle_url('/mod/feedback/print.php', array('id' => $this->cmid));
if ($this->course->id) {
$previewlnk->param('courseid', $this->course->id);
}
$items['left'][]['actionlink'] = new action_link($previewlnk, get_string('previewquestions', 'feedback'),
null, ['class' => 'btn btn-secondary']);
}
if ($this->viewcompletion) {
// Display a link to complete feedback or resume.
$completeurl = new moodle_url('/mod/feedback/complete.php',
['id' => $this->cmid, 'courseid' => $this->course->id]);
if ($this->startpage) {
$completeurl->param('gopage', $this->startpage);
$label = get_string('continue_the_form', 'feedback');
} else {
$label = get_string('complete_the_form', 'feedback');
}
$items['left'][]['actionlink'] = new action_link($completeurl, $label, null, ['class' => 'btn btn-primary']);
}
return $items;
}
}
+73
View File
@@ -0,0 +1,73 @@
<?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/>.
/**
* Contains class mod_feedback\output\summary
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\output;
use renderable;
use templatable;
use renderer_base;
use stdClass;
use moodle_url;
use mod_feedback_structure;
/**
* Class to help display feedback summary
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class summary implements renderable, templatable {
/** @var mod_feedback_structure */
protected $feedbackstructure;
/** @var int */
protected $mygroupid;
/**
* Constructor.
*
* @param mod_feedback_structure $feedbackstructure
* @param int $mygroupid currently selected group
*/
public function __construct($feedbackstructure, $mygroupid = false) {
$this->feedbackstructure = $feedbackstructure;
$this->mygroupid = $mygroupid;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
$r = new stdClass();
$r->completedcount = $this->feedbackstructure->count_completed_responses($this->mygroupid);
$r->itemscount = count($this->feedbackstructure->get_items(true));
return $r;
}
}
+488
View File
@@ -0,0 +1,488 @@
<?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/>.
/**
* Data provider.
*
* @package mod_feedback
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use context_helper;
use stdClass;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
require_once($CFG->dirroot . '/mod/feedback/lib.php');
/**
* Data provider class.
*
* @package mod_feedback
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns metadata.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$completedfields = [
'userid' => 'privacy:metadata:completed:userid',
'timemodified' => 'privacy:metadata:completed:timemodified',
'anonymous_response' => 'privacy:metadata:completed:anonymousresponse',
];
$collection->add_database_table('feedback_completed', $completedfields, 'privacy:metadata:completed');
$collection->add_database_table('feedback_completedtmp', $completedfields, 'privacy:metadata:completedtmp');
$valuefields = [
'value' => 'privacy:metadata:value:value'
];
$collection->add_database_table('feedback_value', $valuefields, 'privacy:metadata:value');
$collection->add_database_table('feedback_valuetmp', $valuefields, 'privacy:metadata:valuetmp');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$sql = "
SELECT DISTINCT ctx.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE fc.userid = :userid";
$params = ['feedback' => 'feedback', 'modlevel' => CONTEXT_MODULE, 'userid' => $userid];
$contextlist = new contextlist();
$contextlist->add_from_sql(sprintf($sql, 'feedback_completed'), $params);
$contextlist->add_from_sql(sprintf($sql, 'feedback_completedtmp'), $params);
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!is_a($context, \context_module::class)) {
return;
}
// Find users with feedback entries.
$sql = "
SELECT fc.userid
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE ctx.id = :contextid";
$params = ['feedback' => 'feedback', 'modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
$userlist->add_from_sql('userid', sprintf($sql, 'feedback_completed'), $params);
$userlist->add_from_sql('userid', sprintf($sql, 'feedback_completedtmp'), $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
$user = $contextlist->get_user();
$userid = $user->id;
$contextids = array_map(function($context) {
return $context->id;
}, array_filter($contextlist->get_contexts(), function($context) {
return $context->contextlevel == CONTEXT_MODULE;
}));
if (empty($contextids)) {
return;
}
$flushdata = function($context, $data) use ($user) {
$contextdata = helper::get_context_data($context, $user);
helper::export_context_files($context, $user);
$mergeddata = array_merge((array) $contextdata, (array) $data);
// Drop the temporary keys.
if (array_key_exists('submissions', $mergeddata)) {
$mergeddata['submissions'] = array_values($mergeddata['submissions']);
}
writer::with_context($context)->export_data([], (object) $mergeddata);
};
$lastctxid = null;
$data = (object) [];
list($sql, $params) = static::prepare_export_query($contextids, $userid);
$recordset = $DB->get_recordset_sql($sql, $params);
foreach ($recordset as $record) {
if ($lastctxid && $lastctxid != $record->contextid) {
$flushdata(context::instance_by_id($lastctxid), $data);
$data = (object) [];
}
context_helper::preload_from_record($record);
$id = ($record->istmp ? 'tmp' : 'notmp') . $record->submissionid;
if (!isset($data->submissions)) {
$data->submissions = [];
}
if (!isset($data->submissions[$id])) {
$data->submissions[$id] = [
'inprogress' => transform::yesno($record->istmp),
'anonymousresponse' => transform::yesno($record->anonymousresponse == FEEDBACK_ANONYMOUS_YES),
'timemodified' => transform::datetime($record->timemodified),
'answers' => []
];
}
$item = static::extract_item_record_from_record($record);
$value = static::extract_value_record_from_record($record);
$itemobj = feedback_get_item_class($record->itemtyp);
$data->submissions[$id]['answers'][] = [
'question' => format_text($record->itemname, FORMAT_HTML, [
'context' => context::instance_by_id($record->contextid),
'para' => false,
'noclean' => true,
]),
'answer' => $itemobj->get_printval($item, $value)
];
$lastctxid = $record->contextid;
}
if (!empty($lastctxid)) {
$flushdata(context::instance_by_id($lastctxid), $data);
}
$recordset->close();
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
// This should not happen, but just in case.
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
// Prepare SQL to gather all completed IDs.
$completedsql = "
SELECT fc.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
WHERE cm.id = :cmid";
$completedparams = ['cmid' => $context->instanceid, 'feedback' => 'feedback'];
// Delete temp answers and submissions.
$completedtmpids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completedtmp'), $completedparams);
if (!empty($completedtmpids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedtmpids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_valuetmp', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completedtmp', "id $insql", $inparams);
}
// Delete answers and submissions.
$completedids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completed'), $completedparams);
if (!empty($completedids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_value', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completed', "id $insql", $inparams);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$userid = $contextlist->get_user()->id;
// Ensure that we only act on module contexts.
$contextids = array_map(function($context) {
return $context->instanceid;
}, array_filter($contextlist->get_contexts(), function($context) {
return $context->contextlevel == CONTEXT_MODULE;
}));
// Prepare SQL to gather all completed IDs.
list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$completedsql = "
SELECT fc.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
WHERE fc.userid = :userid
AND cm.id $insql";
$completedparams = array_merge($inparams, ['userid' => $userid, 'feedback' => 'feedback']);
// Delete all submissions in progress.
$completedtmpids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completedtmp'), $completedparams);
if (!empty($completedtmpids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedtmpids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_valuetmp', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completedtmp', "id $insql", $inparams);
}
// Delete all final submissions.
$completedids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completed'), $completedparams);
if (!empty($completedids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_value', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completed', "id $insql", $inparams);
}
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
$userids = $userlist->get_userids();
// Prepare SQL to gather all completed IDs.
list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$completedsql = "
SELECT fc.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
WHERE cm.id = :instanceid
AND fc.userid $insql";
$completedparams = array_merge($inparams, ['instanceid' => $context->instanceid, 'feedback' => 'feedback']);
// Delete all submissions in progress.
$completedtmpids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completedtmp'), $completedparams);
if (!empty($completedtmpids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedtmpids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_valuetmp', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completedtmp', "id $insql", $inparams);
}
// Delete all final submissions.
$completedids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completed'), $completedparams);
if (!empty($completedids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_value', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completed', "id $insql", $inparams);
}
}
/**
* Extract an item record from a database record.
*
* @param stdClass $record The record.
* @return The item record.
*/
protected static function extract_item_record_from_record(stdClass $record) {
$newrec = new stdClass();
foreach ($record as $key => $value) {
if (strpos($key, 'item') !== 0) {
continue;
}
$key = substr($key, 4);
$newrec->{$key} = $value;
}
return $newrec;
}
/**
* Extract a value record from a database record.
*
* @param stdClass $record The record.
* @return The value record.
*/
protected static function extract_value_record_from_record(stdClass $record) {
$newrec = new stdClass();
foreach ($record as $key => $value) {
if (strpos($key, 'value') !== 0) {
continue;
}
$key = substr($key, 5);
$newrec->{$key} = $value;
}
return $newrec;
}
/**
* Prepare the query to export all data.
*
* Doing it this way allows for merging all records from both the temporary and final tables
* as most of their columns are shared. It is a lot easier to deal with the records when
* exporting as we do not need to try to manually group the two types of submissions in the
* same reported dataset.
*
* The ordering may affect performance on large datasets.
*
* @param array $contextids The context IDs.
* @param int $userid The user ID.
* @return array With SQL and params.
*/
protected static function prepare_export_query(array $contextids, $userid) {
global $DB;
$makefetchsql = function($istmp) use ($DB, $contextids, $userid) {
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$i = $istmp ? 0 : 1;
$istmpsqlval = $istmp ? 1 : 0;
$prefix = $istmp ? 'idtmp' : 'id';
$uniqid = $DB->sql_concat("'$prefix'", 'fc.id');
$sql = "
SELECT $uniqid AS uniqid,
f.id AS feedbackid,
ctx.id AS contextid,
$istmpsqlval AS istmp,
fc.id AS submissionid,
fc.anonymous_response AS anonymousresponse,
fc.timemodified AS timemodified,
fv.id AS valueid,
fv.course_id AS valuecourse_id,
fv.item AS valueitem,
fv.completed AS valuecompleted,
fv.tmp_completed AS valuetmp_completed,
$ctxfields
FROM {context} ctx
JOIN {course_modules} cm
ON cm.id = ctx.instanceid
JOIN {feedback} f
ON f.id = cm.instance
JOIN {%s} fc
ON fc.feedback = f.id
JOIN {%s} fv
ON fv.completed = fc.id
WHERE ctx.id $insql
AND fc.userid = :userid{$i}";
$params = array_merge($inparams, [
'userid' . $i => $userid,
]);
$completedtbl = $istmp ? 'feedback_completedtmp' : 'feedback_completed';
$valuetbl = $istmp ? 'feedback_valuetmp' : 'feedback_value';
return [sprintf($sql, $completedtbl, $valuetbl), $params];
};
list($nontmpsql, $nontmpparams) = $makefetchsql(false);
list($tmpsql, $tmpparams) = $makefetchsql(true);
// Oracle does not support UNION on text fields, therefore we must get the itemdescription
// and valuevalue after doing the union by joining on the result.
$sql = "
SELECT q.*,
COALESCE(fv.value, fvt.value) AS valuevalue,
fi.id AS itemid,
fi.feedback AS itemfeedback,
fi.template AS itemtemplate,
fi.name AS itemname,
fi.label AS itemlabel,
fi.presentation AS itempresentation,
fi.typ AS itemtyp,
fi.hasvalue AS itemhasvalue,
fi.position AS itemposition,
fi.required AS itemrequired,
fi.dependitem AS itemdependitem,
fi.dependvalue AS itemdependvalue,
fi.options AS itemoptions
FROM ($nontmpsql UNION $tmpsql) q
LEFT JOIN {feedback_value} fv
ON fv.id = q.valueid AND q.istmp = 0
LEFT JOIN {feedback_valuetmp} fvt
ON fvt.id = q.valueid AND q.istmp = 1
JOIN {feedback_item} fi
ON (fi.id = fv.item OR fi.id = fvt.item)
ORDER BY q.contextid, q.istmp, q.submissionid, q.valueid";
$params = array_merge($nontmpparams, $tmpparams);
return [$sql, $params];
}
}
@@ -0,0 +1,129 @@
<?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/>.
/**
* Contains class mod_feedback_responses_anon_table
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class mod_feedback_responses_anon_table
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_responses_anon_table extends mod_feedback_responses_table {
/** @var string */
protected $showallparamname = 'ashowall';
/** @var string */
protected $downloadparamname = 'adownload';
/**
* Initialises table
* @param int $group retrieve only users from this group (optional)
*/
public function init($group = 0) {
$cm = $this->feedbackstructure->get_cm();
$this->uniqueid = 'feedback-showentry-anon-list-' . $cm->instance;
// There potentially can be both tables with anonymouns and non-anonymous responses on
// the same page (for example when feedback anonymity was changed after some people
// already responded). In this case we need to distinguish tables' pagination parameters.
$this->request[TABLE_VAR_PAGE] = 'apage';
$tablecolumns = ['random_response'];
$tableheaders = [get_string('response_nr', 'feedback')];
if ($this->feedbackstructure->get_feedback()->course == SITEID && !$this->feedbackstructure->get_courseid()) {
$tablecolumns[] = 'courseid';
$tableheaders[] = get_string('course');
}
$this->define_columns($tablecolumns);
$this->define_headers($tableheaders);
$this->sortable(true, 'random_response');
$this->collapsible(true);
$this->set_attribute('id', 'showentryanontable');
$params = ['instance' => $cm->instance,
'anon' => FEEDBACK_ANONYMOUS_YES,
'courseid' => $this->feedbackstructure->get_courseid()];
$fields = 'c.id, c.random_response, c.courseid';
$from = '{feedback_completed} c';
$where = 'c.anonymous_response = :anon AND c.feedback = :instance';
if ($this->feedbackstructure->get_courseid()) {
$where .= ' AND c.courseid = :courseid';
}
$group = (empty($group)) ? groups_get_activity_group($this->feedbackstructure->get_cm(), true) : $group;
if ($group) {
$where .= ' AND c.userid IN (SELECT g.userid FROM {groups_members} g WHERE g.groupid = :group)';
$params['group'] = $group;
}
$this->set_sql($fields, $from, $where, $params);
$this->set_count_sql("SELECT COUNT(c.id) FROM $from WHERE $where", $params);
}
/**
* Returns a link for viewing a single response
* @param stdClass $row
* @return \moodle_url
*/
protected function get_link_single_entry($row) {
return new moodle_url($this->baseurl, ['showcompleted' => $row->id]);
}
/**
* Prepares column reponse for display
* @param stdClass $row
* @return string
*/
public function col_random_response($row) {
if ($this->is_downloading()) {
return $row->random_response;
} else {
return html_writer::link($this->get_link_single_entry($row),
get_string('response_nr', 'feedback').': '. $row->random_response);
}
}
/**
* Add data for the external structure that will be returned.
*
* @param stdClass $row a database query record row
* @since Moodle 3.3
*/
protected function add_data_for_external($row) {
$this->dataforexternal[] = [
'id' => $row->id,
'courseid' => $row->courseid,
'number' => $row->random_response,
'responses' => $this->get_responses_for_external($row)
];
}
}
+675
View File
@@ -0,0 +1,675 @@
<?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/>.
/**
* Contains class mod_feedback_responses_table
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/tablelib.php');
/**
* Class mod_feedback_responses_table
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_responses_table extends table_sql {
/**
* Maximum number of feedback questions to display in the "Show responses" table
*/
const PREVIEWCOLUMNSLIMIT = 10;
/**
* Maximum number of feedback questions answers to retrieve in one SQL query.
* Mysql has a limit of 60, we leave 1 for joining with users table.
*/
const TABLEJOINLIMIT = 59;
/**
* When additional queries are needed to retrieve more than TABLEJOINLIMIT questions answers, do it in chunks every x rows.
* Value too small will mean too many DB queries, value too big may cause memory overflow.
*/
const ROWCHUNKSIZE = 100;
/** @var mod_feedback_structure */
protected $feedbackstructure;
/** @var int */
protected $grandtotal = null;
/** @var bool */
protected $showall = false;
/** @var string */
protected $showallparamname = 'showall';
/** @var string */
protected $downloadparamname = 'download';
/** @var int number of columns that were not retrieved in the main SQL query
* (no more than TABLEJOINLIMIT tables with values can be joined). */
protected $hasmorecolumns = 0;
/** @var bool whether we are building this table for a external function */
protected $buildforexternal = false;
/** @var array the data structure containing the table data for the external function */
protected $dataforexternal = [];
/** @var bool true if elements per page > 0, otherwise false. */
protected $pageable;
/**
* Constructor
*
* @param mod_feedback_structure $feedbackstructure
* @param int $group retrieve only users from this group (optional)
*/
public function __construct(mod_feedback_structure $feedbackstructure, $group = 0) {
$this->feedbackstructure = $feedbackstructure;
parent::__construct('feedback-showentry-list-' . $feedbackstructure->get_cm()->instance);
$this->showall = optional_param($this->showallparamname, 0, PARAM_BOOL);
$this->define_baseurl(new moodle_url('/mod/feedback/show_entries.php',
['id' => $this->feedbackstructure->get_cm()->id]));
if ($courseid = $this->feedbackstructure->get_courseid()) {
$this->baseurl->param('courseid', $courseid);
}
if ($this->showall) {
$this->baseurl->param($this->showallparamname, $this->showall);
}
$name = format_string($feedbackstructure->get_feedback()->name);
$this->is_downloadable(true);
$this->is_downloading(optional_param($this->downloadparamname, 0, PARAM_ALPHA),
$name, get_string('responses', 'feedback'));
$this->useridfield = 'userid';
$this->init($group);
}
/**
* Initialises table
* @param int $group retrieve only users from this group (optional)
*/
protected function init($group = 0) {
$tablecolumns = array('userpic', 'fullname', 'groups');
$tableheaders = array(
get_string('userpic'),
get_string('fullnameuser'),
get_string('groups')
);
// TODO Does not support custom user profile fields (MDL-70456).
$userfieldsapi = \core_user\fields::for_identity($this->get_context(), false)->with_userpic();
$ufields = $userfieldsapi->get_sql('u', false, '', $this->useridfield, false)->selects;
$extrafields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
$fields = 'c.id, c.timemodified as completed_timemodified, c.courseid, '.$ufields;
$from = '{feedback_completed} c '
. 'JOIN {user} u ON u.id = c.userid AND u.deleted = :notdeleted';
$where = 'c.anonymous_response = :anon
AND c.feedback = :instance';
if ($this->feedbackstructure->get_courseid()) {
$where .= ' AND c.courseid = :courseid';
}
if ($this->is_downloading()) {
// When downloading data:
// Remove 'userpic' from downloaded data.
array_shift($tablecolumns);
array_shift($tableheaders);
// Add all identity fields as separate columns.
foreach ($extrafields as $field) {
$fields .= ", u.{$field}";
$tablecolumns[] = $field;
$tableheaders[] = \core_user\fields::get_display_name($field);
}
}
if ($this->feedbackstructure->get_feedback()->course == SITEID && !$this->feedbackstructure->get_courseid()) {
$tablecolumns[] = 'courseid';
$tableheaders[] = get_string('course');
}
$tablecolumns[] = 'completed_timemodified';
$tableheaders[] = get_string('date');
$this->define_columns($tablecolumns);
$this->define_headers($tableheaders);
$this->sortable(true, 'lastname', SORT_ASC);
$this->no_sorting('groups');
$this->collapsible(true);
$this->set_attribute('id', 'showentrytable');
$params = array();
$params['anon'] = FEEDBACK_ANONYMOUS_NO;
$params['instance'] = $this->feedbackstructure->get_feedback()->id;
$params['notdeleted'] = 0;
$params['courseid'] = $this->feedbackstructure->get_courseid();
$group = (empty($group)) ? groups_get_activity_group($this->feedbackstructure->get_cm(), true) : $group;
if ($group) {
$where .= ' AND c.userid IN (SELECT g.userid FROM {groups_members} g WHERE g.groupid = :group)';
$params['group'] = $group;
}
$this->set_sql($fields, $from, $where, $params);
$this->set_count_sql("SELECT COUNT(c.id) FROM $from WHERE $where", $params);
}
/**
* Current context
* @return context_module
*/
public function get_context(): context {
return context_module::instance($this->feedbackstructure->get_cm()->id);
}
/**
* Allows to set the display column value for all columns without "col_xxxxx" method.
* @param string $column column name
* @param stdClass $row current record result of SQL query
*/
public function other_cols($column, $row) {
if (preg_match('/^val(\d+)$/', $column, $matches)) {
$items = $this->feedbackstructure->get_items();
$itemobj = feedback_get_item_class($items[$matches[1]]->typ);
$printval = $itemobj->get_printval($items[$matches[1]], (object) ['value' => $row->$column]);
if ($this->is_downloading()) {
$printval = s($printval);
}
return trim($printval);
}
return parent::other_cols($column, $row);
}
/**
* Prepares column userpic for display
* @param stdClass $row
* @return string
*/
public function col_userpic($row) {
global $OUTPUT;
$user = user_picture::unalias($row, [], $this->useridfield);
return $OUTPUT->user_picture($user, array('courseid' => $this->feedbackstructure->get_cm()->course));
}
/**
* Prepares column deleteentry for display
* @param stdClass $row
* @return string
*/
public function col_deleteentry($row) {
global $OUTPUT;
$deleteentryurl = new moodle_url($this->baseurl, ['delete' => $row->id, 'sesskey' => sesskey()]);
$deleteaction = new confirm_action(get_string('confirmdeleteentry', 'feedback'));
return $OUTPUT->action_icon($deleteentryurl,
new pix_icon('t/delete', get_string('delete_entry', 'feedback')), $deleteaction);
}
/**
* Returns a link for viewing a single response
* @param stdClass $row
* @return \moodle_url
*/
protected function get_link_single_entry($row) {
return new moodle_url($this->baseurl, ['userid' => $row->{$this->useridfield}, 'showcompleted' => $row->id]);
}
/**
* Prepares column completed_timemodified for display
* @param stdClass $student
* @return string
*/
public function col_completed_timemodified($student) {
if ($this->is_downloading()) {
return userdate($student->completed_timemodified);
} else {
return html_writer::link($this->get_link_single_entry($student),
userdate($student->completed_timemodified));
}
}
/**
* Prepares column courseid for display
* @param array $row
* @return string
*/
public function col_courseid($row) {
$courses = $this->feedbackstructure->get_completed_courses();
$name = '';
if (isset($courses[$row->courseid])) {
$name = $courses[$row->courseid];
if (!$this->is_downloading()) {
$name = html_writer::link(course_get_url($row->courseid), $name);
}
}
return $name;
}
/**
* Prepares column groups for display
* @param array $row
* @return string
*/
public function col_groups($row) {
$groups = '';
if ($usergrps = groups_get_all_groups($this->feedbackstructure->get_cm()->course, $row->userid, 0, 'name')) {
foreach ($usergrps as $group) {
$groups .= format_string($group->name). ' ';
}
}
return trim($groups);
}
/**
* Adds common values to the table that do not change the number or order of entries and
* are only needed when outputting or downloading data.
*/
protected function add_all_values_to_output() {
global $DB;
$tablecolumns = array_keys($this->columns);
$tableheaders = $this->headers;
$items = $this->feedbackstructure->get_items(true);
if (!$this->is_downloading() && !$this->buildforexternal) {
// In preview mode do not show all columns or the page becomes unreadable.
// The information message will be displayed to the teacher that the rest of the data can be viewed when downloading.
$items = array_slice($items, 0, self::PREVIEWCOLUMNSLIMIT, true);
}
$columnscount = 0;
$this->hasmorecolumns = max(0, count($items) - self::TABLEJOINLIMIT);
$headernamepostfix = !$this->is_downloading();
// Add feedback response values.
foreach ($items as $nr => $item) {
if ($columnscount++ < self::TABLEJOINLIMIT) {
// Mysql has a limit on the number of tables in the join, so we only add limited number of columns here,
// the rest will be added in {@link self::build_table()} and {@link self::build_table_chunk()} functions.
$this->sql->fields .= ", " . $DB->sql_cast_to_char("v{$nr}.value") . " AS val{$nr}";
$this->sql->from .= " LEFT OUTER JOIN {feedback_value} v{$nr} " .
"ON v{$nr}.completed = c.id AND v{$nr}.item = :itemid{$nr}";
$this->sql->params["itemid{$nr}"] = $item->id;
}
$tablecolumns[] = "val{$nr}";
$itemobj = feedback_get_item_class($item->typ);
$columnheader = $itemobj->get_display_name($item, $headernamepostfix);
if (!$this->is_downloading()) {
$columnheader = shorten_text($columnheader);
}
if (strval($item->label) !== '') {
$columnheader = get_string('nameandlabelformat', 'mod_feedback',
(object)['label' => format_string($item->label), 'name' => $columnheader]);
}
$tableheaders[] = $columnheader;
}
// Add 'Delete entry' column.
if (!$this->is_downloading() && has_capability('mod/feedback:deletesubmissions', $this->get_context())) {
$tablecolumns[] = 'deleteentry';
$tableheaders[] = '';
}
$this->define_columns($tablecolumns);
$this->define_headers($tableheaders);
}
/**
* Query the db. Store results in the table object for use by build_table.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar. Bar
* will only be used if there is a fullname column defined for the table.
*/
public function query_db($pagesize, $useinitialsbar=true) {
global $DB;
$this->totalrows = $grandtotal = $this->get_total_responses_count();
if (!$this->is_downloading()) {
$this->initialbars($useinitialsbar);
list($wsql, $wparams) = $this->get_sql_where();
if ($wsql) {
$this->countsql .= ' AND '.$wsql;
$this->countparams = array_merge($this->countparams, $wparams);
$this->sql->where .= ' AND '.$wsql;
$this->sql->params = array_merge($this->sql->params, $wparams);
$this->totalrows = $DB->count_records_sql($this->countsql, $this->countparams);
}
if ($this->totalrows > $pagesize) {
$this->pagesize($pagesize, $this->totalrows);
}
}
if ($sort = $this->get_sql_sort()) {
$sort = "ORDER BY $sort";
}
$sql = "SELECT
{$this->sql->fields}
FROM {$this->sql->from}
WHERE {$this->sql->where}
{$sort}";
if (!$this->is_downloading()) {
$this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
} else {
$this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params);
}
}
/**
* Returns total number of reponses (without any filters applied)
* @return int
*/
public function get_total_responses_count() {
global $DB;
if ($this->grandtotal === null) {
$this->grandtotal = $DB->count_records_sql($this->countsql, $this->countparams);
}
return $this->grandtotal;
}
/**
* Defines columns
* @param array $columns an array of identifying names for columns. If
* columns are sorted then column names must correspond to a field in sql.
*/
public function define_columns($columns) {
parent::define_columns($columns);
foreach ($this->columns as $column => $column) {
// Automatically assign classes to columns.
$this->column_class[$column] = ' ' . $column;
}
}
/**
* Convenience method to call a number of methods for you to display the
* table.
* @param int $pagesize
* @param bool $useinitialsbar
* @param string $downloadhelpbutton
*/
public function out($pagesize, $useinitialsbar, $downloadhelpbutton='') {
$this->add_all_values_to_output();
parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
}
/**
* Displays the table
*/
public function display() {
global $OUTPUT;
groups_print_activity_menu($this->feedbackstructure->get_cm(), $this->baseurl->out());
$grandtotal = $this->get_total_responses_count();
if (!$grandtotal) {
echo $OUTPUT->notification(get_string('nothingtodisplay'), 'info', false);
return;
}
if (count($this->feedbackstructure->get_items(true)) > self::PREVIEWCOLUMNSLIMIT) {
echo $OUTPUT->notification(get_string('questionslimited', 'feedback', self::PREVIEWCOLUMNSLIMIT), 'info');
}
$this->out($this->showall ? $grandtotal : FEEDBACK_DEFAULT_PAGE_COUNT,
$grandtotal > FEEDBACK_DEFAULT_PAGE_COUNT);
// Toggle 'Show all' link.
if ($this->totalrows > FEEDBACK_DEFAULT_PAGE_COUNT) {
if (!$this->use_pages) {
echo html_writer::div(html_writer::link(new moodle_url($this->baseurl, [$this->showallparamname => 0]),
get_string('showperpage', '', FEEDBACK_DEFAULT_PAGE_COUNT)), 'showall');
} else {
echo html_writer::div(html_writer::link(new moodle_url($this->baseurl, [$this->showallparamname => 1]),
get_string('showall', '', $this->totalrows)), 'showall');
}
}
}
/**
* Returns links to previous/next responses in the list
* @param stdClass $record
* @return array array of three elements [$prevresponseurl, $returnurl, $nextresponseurl]
*/
public function get_reponse_navigation_links($record) {
$this->setup();
$grandtotal = $this->get_total_responses_count();
$this->query_db($grandtotal);
$lastrow = $thisrow = $nextrow = null;
$counter = 0;
$page = 0;
while ($this->rawdata->valid()) {
$row = $this->rawdata->current();
if ($row->id == $record->id) {
$page = $this->showall ? 0 : floor($counter / FEEDBACK_DEFAULT_PAGE_COUNT);
$thisrow = $row;
$this->rawdata->next();
$nextrow = $this->rawdata->valid() ? $this->rawdata->current() : null;
break;
}
$lastrow = $row;
$this->rawdata->next();
$counter++;
}
$this->rawdata->close();
if (!$thisrow) {
$lastrow = null;
}
return [
$lastrow ? $this->get_link_single_entry($lastrow) : null,
new moodle_url($this->baseurl, [$this->request[TABLE_VAR_PAGE] => $page]),
$nextrow ? $this->get_link_single_entry($nextrow) : null,
];
}
/**
* Download the data.
*/
public function download() {
\core\session\manager::write_close();
$this->out($this->get_total_responses_count(), false);
exit;
}
/**
* Take the data returned from the db_query and go through all the rows
* processing each col using either col_{columnname} method or other_cols
* method or if other_cols returns NULL then put the data straight into the
* table.
*
* This overwrites the parent method because full SQL query may fail on Mysql
* because of the limit in the number of tables in the join. Therefore we only
* join 59 tables in the main query and add the rest here.
*
* @return void
*/
public function build_table() {
if ($this->rawdata instanceof \Traversable && !$this->rawdata->valid()) {
return;
}
if (!$this->rawdata) {
return;
}
$columnsgroups = [];
if ($this->hasmorecolumns) {
$items = $this->feedbackstructure->get_items(true);
$notretrieveditems = array_slice($items, self::TABLEJOINLIMIT, $this->hasmorecolumns, true);
$columnsgroups = array_chunk($notretrieveditems, self::TABLEJOINLIMIT, true);
}
$chunk = [];
foreach ($this->rawdata as $row) {
if ($this->hasmorecolumns) {
$chunk[$row->id] = $row;
if (count($chunk) >= self::ROWCHUNKSIZE) {
$this->build_table_chunk($chunk, $columnsgroups);
$chunk = [];
}
} else {
if ($this->buildforexternal) {
$this->add_data_for_external($row);
} else {
$this->add_data_keyed($this->format_row($row), $this->get_row_class($row));
}
}
}
$this->build_table_chunk($chunk, $columnsgroups);
}
/**
* Retrieve additional columns. Database engine may have a limit on number of joins.
*
* @param array $rows Array of rows with already retrieved data, new values will be added to this array
* @param array $columnsgroups array of arrays of columns. Each element has up to self::TABLEJOINLIMIT items. This
* is easy to calculate but because we can call this method many times we calculate it once and pass by
* reference for performance reasons
*/
protected function build_table_chunk(&$rows, &$columnsgroups) {
global $DB;
if (!$rows) {
return;
}
foreach ($columnsgroups as $columnsgroup) {
$fields = 'c.id';
$from = '{feedback_completed} c';
$params = [];
foreach ($columnsgroup as $nr => $item) {
$fields .= ", " . $DB->sql_cast_to_char("v{$nr}.value") . " AS val{$nr}";
$from .= " LEFT OUTER JOIN {feedback_value} v{$nr} " .
"ON v{$nr}.completed = c.id AND v{$nr}.item = :itemid{$nr}";
$params["itemid{$nr}"] = $item->id;
}
list($idsql, $idparams) = $DB->get_in_or_equal(array_keys($rows), SQL_PARAMS_NAMED);
$sql = "SELECT $fields FROM $from WHERE c.id ".$idsql;
$results = $DB->get_records_sql($sql, $params + $idparams);
foreach ($results as $result) {
foreach ($result as $key => $value) {
$rows[$result->id]->{$key} = $value;
}
}
}
foreach ($rows as $row) {
if ($this->buildforexternal) {
$this->add_data_for_external($row);
} else {
$this->add_data_keyed($this->format_row($row), $this->get_row_class($row));
}
}
}
/**
* Returns html code for displaying "Download" button if applicable.
*/
public function download_buttons() {
global $OUTPUT;
if ($this->is_downloadable() && !$this->is_downloading()) {
return $OUTPUT->download_dataformat_selector(get_string('downloadas', 'table'),
$this->baseurl->out_omit_querystring(), $this->downloadparamname, $this->baseurl->params());
} else {
return '';
}
}
/**
* Return user responses data ready for the external function.
*
* @param stdClass $row the table row containing the responses
* @return array returns the responses ready to be used by an external function
* @since Moodle 3.3
*/
protected function get_responses_for_external($row) {
$responses = [];
foreach ($row as $el => $val) {
// Get id from column name.
if (preg_match('/^val(\d+)$/', $el, $matches)) {
$id = $matches[1];
$responses[] = [
'id' => $id,
'name' => $this->headers[$this->columns[$el]],
'printval' => $this->other_cols($el, $row),
'rawval' => $val,
];
}
}
return $responses;
}
/**
* Add data for the external structure that will be returned.
*
* @param stdClass $row a database query record row
* @since Moodle 3.3
*/
protected function add_data_for_external($row) {
$this->dataforexternal[] = [
'id' => $row->id,
'courseid' => $row->courseid,
'userid' => $row->userid,
'fullname' => fullname($row),
'timemodified' => $row->completed_timemodified,
'responses' => $this->get_responses_for_external($row),
];
}
/**
* Exports the table as an external structure handling pagination.
*
* @param int $page page number (for pagination)
* @param int $perpage elements per page
* @since Moodle 3.3
* @return array returns the table ready to be used by an external function
*/
public function export_external_structure($page = 0, $perpage = 0) {
$this->buildforexternal = true;
$this->add_all_values_to_output();
// Set-up.
$this->setup();
// Override values, if needed.
if ($perpage > 0) {
$this->pageable = true;
$this->currpage = $page;
$this->pagesize = $perpage;
} else {
$this->pagesize = $this->get_total_responses_count();
}
$this->query_db($this->pagesize, false);
$this->build_table();
$this->close_recordset();
return $this->dataforexternal;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Search area for mod_feedback activities.
*
* @package mod_feedback
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\search;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for mod_feedback activities.
*
* @package mod_feedback
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
}
+367
View File
@@ -0,0 +1,367 @@
<?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/>.
/**
* Contains class mod_feedback_structure
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Stores and manipulates the structure of the feedback or template (items, pages, etc.)
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_structure {
/** @var stdClass record from 'feedback' table.
* Reliably has fields: id, course, timeopen, timeclose, anonymous, completionsubmit.
* For full object or to access any other field use $this->get_feedback()
*/
protected $feedback;
/** @var cm_info */
protected $cm;
/** @var int course where the feedback is filled. For feedbacks that are NOT on the front page this is 0 */
protected $courseid = 0;
/** @var int */
protected $templateid;
/** @var array */
protected $allitems;
/** @var array */
protected $allcourses;
/** @var int */
protected $userid;
/**
* Constructor
*
* @param stdClass $feedback feedback object, in case of the template
* this is the current feedback the template is accessed from
* @param stdClass|cm_info $cm course module object corresponding to the $feedback
* (at least one of $feedback or $cm is required)
* @param int $courseid current course (for site feedbacks only)
* @param int $templateid template id if this class represents the template structure
* @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
*/
public function __construct($feedback, $cm, $courseid = 0, $templateid = null, $userid = 0) {
global $USER;
if ((empty($feedback->id) || empty($feedback->course)) && (empty($cm->instance) || empty($cm->course))) {
throw new coding_exception('Either $feedback or $cm must be passed to constructor');
}
$this->feedback = $feedback ?: (object)['id' => $cm->instance, 'course' => $cm->course];
$this->cm = ($cm && $cm instanceof cm_info) ? $cm :
get_fast_modinfo($this->feedback->course)->instances['feedback'][$this->feedback->id];
$this->templateid = $templateid;
$this->courseid = ($this->feedback->course == SITEID) ? $courseid : 0;
if (empty($userid)) {
$this->userid = $USER->id;
} else {
$this->userid = $userid;
}
if (!$feedback) {
// If feedback object was not specified, populate object with fields required for the most of methods.
// These fields were added to course module cache in feedback_get_coursemodule_info().
// Full instance record can be retrieved by calling mod_feedback_structure::get_feedback().
$customdata = ($this->cm->customdata ?: []) + ['timeopen' => 0, 'timeclose' => 0, 'anonymous' => 0];
$this->feedback->timeopen = $customdata['timeopen'];
$this->feedback->timeclose = $customdata['timeclose'];
$this->feedback->anonymous = $customdata['anonymous'];
$this->feedback->completionsubmit = empty($this->cm->customdata['customcompletionrules']['completionsubmit']) ? 0 : 1;
}
}
/**
* Current feedback
* @return stdClass
*/
public function get_feedback() {
global $DB;
if (!isset($this->feedback->publish_stats) || !isset($this->feedback->name)) {
// Make sure the full object is retrieved.
$this->feedback = $DB->get_record('feedback', ['id' => $this->feedback->id], '*', MUST_EXIST);
}
return $this->feedback;
}
/**
* Current course module
* @return stdClass
*/
public function get_cm() {
return $this->cm;
}
/**
* Id of the current course (for site feedbacks only)
* @return stdClass
*/
public function get_courseid() {
return $this->courseid;
}
/**
* Template id
* @return int
*/
public function get_templateid() {
return $this->templateid;
}
/**
* Is this feedback open (check timeopen and timeclose)
* @return bool
*/
public function is_open() {
$checktime = time();
return (!$this->feedback->timeopen || $this->feedback->timeopen <= $checktime) &&
(!$this->feedback->timeclose || $this->feedback->timeclose >= $checktime);
}
/**
* Get all items in this feedback or this template
* @param bool $hasvalueonly only count items with a value.
* @return array of objects from feedback_item with an additional attribute 'itemnr'
*/
public function get_items($hasvalueonly = false) {
global $DB;
if ($this->allitems === null) {
if ($this->templateid) {
$this->allitems = $DB->get_records('feedback_item', ['template' => $this->templateid], 'position');
} else {
$this->allitems = $DB->get_records('feedback_item', ['feedback' => $this->feedback->id], 'position');
}
$idx = 1;
foreach ($this->allitems as $id => $item) {
$this->allitems[$id]->itemnr = $item->hasvalue ? ($idx++) : null;
}
}
if ($hasvalueonly && $this->allitems) {
return array_filter($this->allitems, function($item) {
return $item->hasvalue;
});
}
return $this->allitems;
}
/**
* Is the items list empty?
* @return bool
*/
public function is_empty() {
$items = $this->get_items();
$displayeditems = array_filter($items, function($item) {
return $item->typ !== 'pagebreak';
});
return !$displayeditems;
}
/**
* Is this feedback anonymous?
* @return bool
*/
public function is_anonymous() {
return $this->feedback->anonymous == FEEDBACK_ANONYMOUS_YES;
}
/**
* Returns the formatted text of the page after submit or null if it is not set
*
* @return string|null
*/
public function page_after_submit() {
global $CFG;
require_once($CFG->libdir . '/filelib.php');
$pageaftersubmit = $this->get_feedback()->page_after_submit;
if (empty($pageaftersubmit)) {
return null;
}
$pageaftersubmitformat = $this->get_feedback()->page_after_submitformat;
$context = context_module::instance($this->get_cm()->id);
$output = file_rewrite_pluginfile_urls($pageaftersubmit,
'pluginfile.php', $context->id, 'mod_feedback', 'page_after_submit', 0);
return format_text($output, $pageaftersubmitformat, array('overflowdiv' => true));
}
/**
* Checks if current user is able to view feedback on this course.
*
* @return bool
*/
public function can_view_analysis() {
global $USER;
$context = context_module::instance($this->cm->id);
if (has_capability('mod/feedback:viewreports', $context, $this->userid)) {
return true;
}
if (intval($this->get_feedback()->publish_stats) != 1 ||
!has_capability('mod/feedback:viewanalysepage', $context, $this->userid)) {
return false;
}
if ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid)) {
// There is no tracking for the guests, assume that they can view analysis if condition above is satisfied.
return $this->feedback->course == SITEID;
}
return $this->is_already_submitted(true);
}
/**
* check for multiple_submit = false.
* if the feedback is global so the courseid must be given
*
* @param bool $anycourseid if true checks if this feedback was submitted in any course, otherwise checks $this->courseid .
* Applicable to frontpage feedbacks only
* @return bool true if the feedback already is submitted otherwise false
*/
public function is_already_submitted($anycourseid = false) {
global $DB, $USER;
if ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid)) {
return false;
}
$params = array('userid' => $this->userid, 'feedback' => $this->feedback->id);
if (!$anycourseid && $this->courseid) {
$params['courseid'] = $this->courseid;
}
return $DB->record_exists('feedback_completed', $params);
}
/**
* Check whether the feedback is mapped to the given courseid.
*/
public function check_course_is_mapped() {
global $DB;
if ($this->feedback->course != SITEID) {
return true;
}
if ($DB->get_records('feedback_sitecourse_map', array('feedbackid' => $this->feedback->id))) {
$params = array('feedbackid' => $this->feedback->id, 'courseid' => $this->courseid);
if (!$DB->get_record('feedback_sitecourse_map', $params)) {
return false;
}
}
// No mapping means any course is mapped.
return true;
}
/**
* If there are any new responses to the anonymous feedback, re-shuffle all
* responses and assign response number to each of them.
*/
public function shuffle_anonym_responses() {
global $DB;
$params = array('feedback' => $this->feedback->id,
'random_response' => 0,
'anonymous_response' => FEEDBACK_ANONYMOUS_YES);
if ($DB->count_records('feedback_completed', $params, 'random_response')) {
// Get all of the anonymous records, go through them and assign a response id.
unset($params['random_response']);
$feedbackcompleteds = $DB->get_records('feedback_completed', $params, 'id');
shuffle($feedbackcompleteds);
$num = 1;
foreach ($feedbackcompleteds as $compl) {
$compl->random_response = $num++;
$DB->update_record('feedback_completed', $compl);
}
}
}
/**
* Counts records from {feedback_completed} table for a given feedback
*
* If $groupid or $this->courseid is set, the records are filtered by the group/course
*
* @param int $groupid
* @return mixed array of found completeds otherwise false
*/
public function count_completed_responses($groupid = 0) {
global $DB;
if (intval($groupid) > 0) {
$query = "SELECT COUNT(DISTINCT fbc.id)
FROM {feedback_completed} fbc, {groups_members} gm
WHERE fbc.feedback = :feedback
AND gm.groupid = :groupid
AND fbc.userid = gm.userid";
} else if ($this->courseid) {
$query = "SELECT COUNT(fbc.id)
FROM {feedback_completed} fbc
WHERE fbc.feedback = :feedback
AND fbc.courseid = :courseid";
} else {
$query = "SELECT COUNT(fbc.id) FROM {feedback_completed} fbc WHERE fbc.feedback = :feedback";
}
$params = ['feedback' => $this->feedback->id, 'groupid' => $groupid, 'courseid' => $this->courseid];
return $DB->get_field_sql($query, $params);
}
/**
* For the frontpage feedback returns the list of courses with at least one completed feedback
*
* @return array id=>name pairs of courses
*/
public function get_completed_courses() {
global $DB;
if ($this->get_feedback()->course != SITEID) {
return [];
}
if ($this->allcourses !== null) {
return $this->allcourses;
}
$courseselect = "SELECT fbc.courseid
FROM {feedback_completed} fbc
WHERE fbc.feedback = :feedbackid";
$ctxselect = context_helper::get_preload_record_columns_sql('ctx');
$sql = 'SELECT c.id, c.shortname, c.fullname, c.idnumber, c.visible, '. $ctxselect. '
FROM {course} c
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextcourse
WHERE c.id IN ('. $courseselect.') ORDER BY c.sortorder';
$list = $DB->get_records_sql($sql, ['contextcourse' => CONTEXT_COURSE, 'feedbackid' => $this->get_feedback()->id]);
$this->allcourses = array();
foreach ($list as $course) {
context_helper::preload_from_record($course);
if (!$course->visible &&
!has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id), $this->userid)) {
// Do not return courses that current user can not see.
continue;
}
$label = get_course_display_name_for_list($course);
$this->allcourses[$course->id] = $label;
}
return $this->allcourses;
}
}
+108
View File
@@ -0,0 +1,108 @@
<?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/>.
/**
* Contains class mod_feedback_templates_table
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/tablelib.php');
/**
* Class mod_feedback_templates_table
*
* @package mod_feedback
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_templates_table extends flexible_table {
/** @var string|null Indicate whether we are managing template or not. */
private $mode;
/**
* Constructor
* @param int $uniqueid all tables have to have a unique id, this is used
* as a key when storing table properties like sort order in the session.
* @param moodle_url $baseurl
* @param string $mode Indicate whether we are managing templates
*/
public function __construct($uniqueid, $baseurl, ?string $mode = null) {
parent::__construct($uniqueid);
$this->mode = $mode;
$tablecolumns = array('template');
if ($this->mode) {
$tablecolumns[] = 'actions';
}
$tableheaders = array(get_string('template', 'feedback'), '');
$this->set_attribute('class', 'templateslist');
$this->define_columns($tablecolumns);
$this->define_headers($tableheaders);
$this->define_baseurl($baseurl);
$this->column_class('template', 'template');
$this->column_class('actions', 'text-right');
$this->sortable(false);
}
/**
* Displays the table with the given set of templates
* @param array $templates
*/
public function display($templates) {
global $OUTPUT;
if (empty($templates)) {
echo $OUTPUT->box(get_string('no_templates_available_yet', 'feedback'),
'generalbox boxaligncenter');
return;
}
$this->setup();
$strdeletefeedback = get_string('delete_template', 'feedback');
foreach ($templates as $template) {
$data = [];
$url = new moodle_url($this->baseurl, array('templateid' => $template->id, 'sesskey' => sesskey()));
$data[] = $OUTPUT->action_link($url, format_string($template->name));
// Only show the actions if we are managing templates.
if ($this->mode && has_capability('mod/feedback:deletetemplate', $this->get_context())) {
$deleteurl = new moodle_url('/mod/feedback/manage_templates.php',
$url->params() + ['deletetemplate' => $template->id]);
$deleteaction = new confirm_action(get_string('confirmdeletetemplate', 'feedback'));
$deleteicon = $OUTPUT->action_icon($deleteurl, new pix_icon('t/delete', $strdeletefeedback), $deleteaction);
if ($template->ispublic) {
$systemcontext = context_system::instance();
if (!(has_capability('mod/feedback:createpublictemplate', $systemcontext) &&
has_capability('mod/feedback:deletetemplate', $systemcontext))) {
$deleteicon = false;
}
}
$data[] = $deleteicon;
}
$this->add_data($data);
}
$this->finish_output();
}
}