diff --git a/mod/feedback/classes/reportbuilder/local/entities/question.php b/mod/feedback/classes/reportbuilder/local/entities/question.php new file mode 100644 index 0000000000000..a993e18599b6e --- /dev/null +++ b/mod/feedback/classes/reportbuilder/local/entities/question.php @@ -0,0 +1,133 @@ +. + +declare(strict_types=1); + +namespace mod_feedback\reportbuilder\local\entities; + +use lang_string; +use stdClass; +use core_reportbuilder\local\entities\base; +use core_reportbuilder\local\filters\text; +use core_reportbuilder\local\report\{column, filter}; + +/** + * Question entity + * + * @package mod_feedback + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question extends base { + + /** + * Database tables that this entity uses and their default aliases + * + * @return array + */ + protected function get_default_tables(): array { + return [ + 'feedback_item', + ]; + } + + /** + * The default title for this entity in the list of columns/conditions/filters in the report builder + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('question', 'mod_feedback'); + } + + /** + * Initialise the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + // All the filters defined by the entity can also be used as conditions. + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this + ->add_filter($filter) + ->add_condition($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $questionalias = $this->get_table_alias('feedback_item'); + + // Name. + $columnns[] = (new column( + 'name', + new lang_string('name'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$questionalias}.label") + ->add_field("{$questionalias}.name") + ->set_is_sortable(true) + ->add_callback(static function(stdClass $row): string { + if (strval($row->label) !== '') { + return get_string( + 'nameandlabelformat', + 'mod_feedback', + (object)['label' => format_string($row->label), 'name' => $row->name] + ); + } + else { + return format_string($row->name); + } + }); + return $columnns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + global $DB; + + $questionalias = $this->get_table_alias('feedback_item'); + + // Name filter. + $filters[] = (new filter( + text::class, + 'name', + new lang_string('name'), + $this->get_entity_name(), + "{$questionalias}.name" + )) + ->add_joins($this->get_joins()); + + return $filters; + } +} diff --git a/mod/feedback/classes/reportbuilder/local/entities/question_value.php b/mod/feedback/classes/reportbuilder/local/entities/question_value.php new file mode 100644 index 0000000000000..b2bc402f6e999 --- /dev/null +++ b/mod/feedback/classes/reportbuilder/local/entities/question_value.php @@ -0,0 +1,120 @@ +. + +declare(strict_types=1); + +namespace mod_feedback\reportbuilder\local\entities; + +use lang_string; +use core_reportbuilder\local\entities\base; +use core_reportbuilder\local\filters\text; +use core_reportbuilder\local\report\{column, filter}; + +/** + * Question value entity + * + * @package mod_feedback + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_value extends base { + + /** + * Database tables that this entity uses and their default aliases + * + * @return array + */ + protected function get_default_tables(): array { + return [ + 'feedback_value', + ]; + } + + /** + * The default title for this entity in the list of columns/conditions/filters in the report builder + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('questionvalue', 'mod_feedback'); + } + + /** + * Initialise the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + // All the filters defined by the entity can also be used as conditions. + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this + ->add_filter($filter) + ->add_condition($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $questionvaluealias = $this->get_table_alias('feedback_value'); + + // Value. + $columnns[] = (new column( + 'value', + new lang_string('value', 'mod_feedback'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$questionvaluealias}.value") + ->set_is_sortable(true); + + return $columnns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + global $DB; + + $questionvaluealias = $this->get_table_alias('feedback_value'); + + // Value filter. + $filters[] = (new filter( + text::class, + 'value', + new lang_string('value', 'mod_feedback'), + $this->get_entity_name(), + "{$questionvaluealias}.value" + )) + ->add_joins($this->get_joins()); + + return $filters; + } +} diff --git a/mod/feedback/classes/reportbuilder/local/entities/response.php b/mod/feedback/classes/reportbuilder/local/entities/response.php new file mode 100644 index 0000000000000..16447f26d795f --- /dev/null +++ b/mod/feedback/classes/reportbuilder/local/entities/response.php @@ -0,0 +1,123 @@ +. + +declare(strict_types=1); + +namespace mod_feedback\reportbuilder\local\entities; + +use lang_string; +use core_reportbuilder\local\entities\base; +use core_reportbuilder\local\filters\date; +use core_reportbuilder\local\helpers\format; +use core_reportbuilder\local\report\{column, filter}; + +/** + * Response entity + * + * @package mod_feedback + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class response extends base { + + /** + * Database tables that this entity uses and their default aliases + * + * @return array + */ + protected function get_default_tables(): array { + return [ + 'feedback_completed', + ]; + } + + /** + * The default title for this entity in the list of columns/conditions/filters in the report builder + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('responses', 'mod_feedback'); + } + + /** + * Initialise the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + // All the filters defined by the entity can also be used as conditions. + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this + ->add_filter($filter) + ->add_condition($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $responsealias = $this->get_table_alias('feedback_completed'); + + // Date column. + $columnns[] = (new column( + 'timemodified', + new lang_string('date'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TIMESTAMP) + ->add_fields("{$responsealias}.timemodified, {$responsealias}.anonymous_response") + ->set_is_sortable(true) + ->add_callback([format::class, 'userdate']) + ->add_callback(fn($value, $row) => $row->anonymous_response == FEEDBACK_ANONYMOUS_YES ? '' : $value); + + return $columnns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + global $DB; + + $responsealias = $this->get_table_alias('feedback_completed'); + + // Date filter. + $filters[] = (new filter( + date::class, + 'timemodified', + new lang_string('date'), + $this->get_entity_name(), + "{$responsealias}.timemodified" + )) + ->add_joins($this->get_joins()); + + return $filters; + } +} diff --git a/mod/feedback/classes/reportbuilder/local/systemreports/responses.php b/mod/feedback/classes/reportbuilder/local/systemreports/responses.php new file mode 100644 index 0000000000000..e71def535bd23 --- /dev/null +++ b/mod/feedback/classes/reportbuilder/local/systemreports/responses.php @@ -0,0 +1,195 @@ +. + +declare(strict_types=1); + +namespace mod_feedback\reportbuilder\local\systemreports; + +// use core\context\{course, system}; +use core_group\reportbuilder\local\entities\group; +use mod_feedback\reportbuilder\local\entities\response; +use mod_feedback\reportbuilder\local\entities\question; +use mod_feedback\reportbuilder\local\entities\question_value; +use core_reportbuilder\local\entities\{course, user}; +use core_reportbuilder\local\helpers\database; +use core_reportbuilder\local\report\{action, column}; +use core_reportbuilder\system_report; +use html_writer; +use lang_string; +use moodle_url; +use pix_icon; +use stdClass; + +defined('MOODLE_INTERNAL') || die; + +global $CFG; + +/** + * Feedback responses system report class implementation + * + * @package mod_feedback + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class responses extends system_report { + + /** @var array $items Array of items to be displayed in the report */ + private array $items; + + /** + * Initialise report, we need to set the main table, load our entities and set columns/filters + */ + protected function initialise(): void { + global $USER; + + [$course, $cm] = get_course_and_cm_from_cmid($this->get_context()->instanceid, 'feedback'); + $this->items = (new \mod_feedback_structure(null, $cm, $course->id))->get_items(true); + + $responseentity = new response(); + $responsealias = $responseentity->get_table_alias('feedback_completed'); + + $this->set_main_table('feedback_completed', $responsealias); + $this->add_entity($responseentity); + + // Join user entity. + $userentity = new user(); + $useralias = $userentity->get_table_alias('user'); + $this->add_entity($userentity->add_join( + "LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$responsealias}.userid" + )); + + // Any columns required by actions should be defined here to ensure they're always available. + $this->add_base_fields("{$responsealias}.id"); + + $this->add_columns(); + $this->add_filters(); + $this->add_actions(); + + // TODO: Set the initial sort column. + $this->set_initial_sort_column('user:fullnamewithpicturelink', SORT_ASC); + } + + /** + * Validates access to view this report + * + * @return bool + */ + protected function can_view(): bool { + // TODO: Implement can_view() method. + return true; + } + + /** + * Adds the columns we want to display in the report + * + * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their + * unique identifier + * + * @param string $entityuseralias + * @param string $entityservicealias + */ + public function add_columns(): void { + global $DB; + + $entityresponsealias = $this->get_entity('response')->get_table_alias('feedback_completed'); + + $this->add_columns_from_entities([ + 'user:fullnamewithpicturelink', + 'response:timemodified', + ]); + + // TODO: Add the rest of the columns, including the ones from the question entity. + $questionentity = new question(); + $this->add_entity($questionentity); + foreach ($this->items as $key => $item) { + $alias = database::generate_alias(); + $this->add_column((new column( + "item{$key}", + $this->get_item_name($item), + 'question', + )) + ->add_join("LEFT JOIN {feedback_value} {$alias} ON {$alias}.completed = {$entityresponsealias}.id + AND {$alias}.item = {$item->id}") + ->add_field($DB->sql_cast_to_char("{$alias}.value"), 'value') + ->set_is_sortable(true) + ); + } + } + + /** + * Adds the filters we want to display in the report + * + * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their + * unique identifier + */ + protected function add_filters(): void { + $filters = [ + 'user:fullname', + ]; + + $this->add_filters_from_entities($filters); + + $this->get_filter('user:fullname') + ->set_header(new lang_string('user')); + } + + /** + * Add the system report actions. An extra column will be appended to each row, containing all actions added here + * + * Note the use of ":id" placeholder which will be substituted according to actual values in the row + */ + protected function add_actions(): void { + + // Action to preview response. + $this->add_action((new action( + new moodle_url('#'), + new pix_icon('t/preview', '', 'core'), + ['data-action' => 'response-view', 'data-response-id' => ':id'], + false, + new lang_string('preview', 'core') + ))); + // TODO: Add JS and WS to perform the preview action. + + // Action to delete response. + $this->add_action((new action( + new moodle_url('#'), + new pix_icon('t/delete', '', 'core'), + ['class' => 'text-danger', 'data-action' => 'response-delete', 'data-response-id' => ':id'], + false, + new lang_string('delete', 'core') + ))); + // TODO: Add JS and WS to perform the delete action. + } + + /** + * Returns the name of the item + * + * @param stdClass $item + * @return lang_string + */ + private function get_item_name(stdClass $item): lang_string { + if (strval($item->label) !== '') { + return new lang_string( + 'nameandlabelformat', + 'mod_feedback', + (object)['label' => $item->label, 'name' => $item->name] + ); + } + else { + return new lang_string('nameformat', 'mod_feedback', $item->name); + } + } +} diff --git a/mod/feedback/lang/en/feedback.php b/mod/feedback/lang/en/feedback.php index e67380f80ea31..3e9c443e69883 100644 --- a/mod/feedback/lang/en/feedback.php +++ b/mod/feedback/lang/en/feedback.php @@ -202,6 +202,7 @@ $string['name'] = 'Name'; $string['name_required'] = 'Name required'; $string['nameandlabelformat'] = '({$a->label}) {$a->name}'; +$string['nameformat'] = '{$a->name}'; $string['next_page'] = 'Next page'; $string['no_handler'] = 'No action handler exists for'; $string['no_itemlabel'] = 'No label'; @@ -246,6 +247,7 @@ $string['questionandsubmission'] = 'Question and submission settings'; $string['questions'] = 'Questions'; $string['questionslimited'] = 'Showing only {$a} first questions, view individual answers or download table data to view all.'; +$string['questionvalue'] = 'Question value'; $string['radio'] = 'Multiple choice - single answer'; $string['radio_values'] = 'Responses'; $string['ready_feedbacks'] = 'Ready feedbacks'; @@ -253,6 +255,7 @@ $string['resetting_data'] = 'Responses'; $string['resetting_delete'] = 'Delete responses'; $string['resetting_feedbacks'] = 'Resetting feedbacks'; +$string['response'] = 'Response'; $string['response_nr'] = 'Response number'; $string['responses'] = 'Responses'; $string['responsetime'] = 'Responses time'; @@ -299,5 +302,6 @@ $string['use_one_line_for_each_value'] = 'Use one line for each answer!'; $string['use_this_template'] = 'Use this template'; $string['using_templates'] = 'Use a template'; +$string['value'] = 'Value'; $string['vertical'] = 'Vertical'; $string['whatfor'] = 'What do you want to do?'; diff --git a/mod/feedback/show_entries.php b/mod/feedback/show_entries.php index 07db00d05236b..756858ace9d0b 100644 --- a/mod/feedback/show_entries.php +++ b/mod/feedback/show_entries.php @@ -22,6 +22,9 @@ * @package mod_feedback */ +use core_reportbuilder\system_report_factory; +use mod_feedback\reportbuilder\local\systemreports\responses; + require_once("../../config.php"); require_once("lib.php"); @@ -44,33 +47,15 @@ $actionbar = new \mod_feedback\output\responses_action_bar($cm->id, $baseurl); -if ($deleteid) { - // This is a request to delete a reponse. - require_capability('mod/feedback:deletesubmissions', $context); - require_sesskey(); - $feedbackstructure = new mod_feedback_completion($feedback, $cm, 0, true, $deleteid); - feedback_delete_completed($feedbackstructure->get_completed(), $feedback, $cm); - redirect($baseurl); -} else { - // Viewing list of reponses. - $feedbackstructure = new mod_feedback_structure($feedback, $cm, $courseid); -} - -$responsestable = new mod_feedback_responses_table($feedbackstructure); -$anonresponsestable = new mod_feedback_responses_anon_table($feedbackstructure); - -if ($responsestable->is_downloading()) { - $responsestable->download(); -} -if ($anonresponsestable->is_downloading()) { - $anonresponsestable->download(); -} +$feedbackstructure = new mod_feedback_structure($feedback, $cm, $courseid); +// TODO: No idea what this is for. // Process course select form. $courseselectform = new mod_feedback_course_select_form($baseurl, $feedbackstructure, $feedback->course == SITEID); if ($data = $courseselectform->get_data()) { redirect(new moodle_url($baseurl, ['courseid' => $data->courseid])); } + // Print the page header. navigation_node::override_active_url($baseurl); $PAGE->set_heading($course->fullname); @@ -86,23 +71,12 @@ echo $renderer->main_action_bar($actionbar); echo $OUTPUT->heading(get_string('show_entries', 'mod_feedback'), 3); +// TODO: No idea what this is for. // Print the list of responses. $courseselectform->display(); -// Show non-anonymous responses (always retrieve them even if current feedback is anonymous). -$totalrows = $responsestable->get_total_responses_count(); -if (!$feedbackstructure->is_anonymous() || $totalrows) { - echo $OUTPUT->heading(get_string('non_anonymous_entries', 'feedback', $totalrows), 4); - $responsestable->display(); -} - -// Show anonymous responses (always retrieve them even if current feedback is not anonymous). -$feedbackstructure->shuffle_anonym_responses(); -$totalrows = $anonresponsestable->get_total_responses_count(); -if ($feedbackstructure->is_anonymous() || $totalrows) { - echo $OUTPUT->heading(get_string('anonymous_entries', 'feedback', $totalrows), 4); - $anonresponsestable->display(); -} +$report = system_report_factory::create(responses::class, $context); +echo $report->output(); // Finish the page. echo $OUTPUT->footer();