diff --git a/report/configlog/classes/form/search.php b/report/configlog/classes/form/search.php deleted file mode 100644 index b881b292f1d..00000000000 --- a/report/configlog/classes/form/search.php +++ /dev/null @@ -1,72 +0,0 @@ -. - -/** - * Report search form class. - * - * @package report_configlog - * @copyright 2019 Paul Holden (paulh@moodle.com) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace report_configlog\form; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * Report search form class. - * - * @package report_configlog - * @copyright 2019 Paul Holden (paulh@moodle.com) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class search extends \moodleform { - - /** - * Form definition - * - * @return void - */ - public function definition() { - $mform = $this->_form; - - // By default just show the 'setting' field. - $mform->addElement('header', 'heading', get_string('search')); - $mform->addElement('text', 'setting', get_string('setting', 'report_configlog')); - $mform->setType('setting', PARAM_TEXT); - - // Rest of the search fields. - $mform->addElement('text', 'value', get_string('value', 'report_configlog')); - $mform->setType('value', PARAM_TEXT); - $mform->addHelpButton('value', 'value', 'report_configlog'); - $mform->setAdvanced('value', true); - - $mform->addElement('text', 'user', get_string('user', 'report_configlog')); - $mform->setType('user', PARAM_TEXT); - $mform->addHelpButton('user', 'user', 'report_configlog'); - $mform->setAdvanced('user', true); - - $mform->addElement('date_selector', 'datefrom', get_string('datefrom', 'report_configlog'), ['optional' => true]); - $mform->setAdvanced('datefrom', true); - - $mform->addElement('date_selector', 'dateto', get_string('dateto', 'report_configlog'), ['optional' => true]); - $mform->setAdvanced('dateto', true); - - $this->add_action_buttons(false, get_string('search')); - } -} \ No newline at end of file diff --git a/report/configlog/classes/local/entities/config_change.php b/report/configlog/classes/local/entities/config_change.php new file mode 100644 index 00000000000..62ebeb62774 --- /dev/null +++ b/report/configlog/classes/local/entities/config_change.php @@ -0,0 +1,207 @@ +. + +namespace report_configlog\local\entities; + +use lang_string; +use core_reportbuilder\local\entities\base; +use core_reportbuilder\local\helpers\format; +use core_reportbuilder\local\report\column; +use core_reportbuilder\local\report\filter; +use core_reportbuilder\local\filters\date; +use core_reportbuilder\local\filters\text; + +/** + * Config change entity class implementation + * + * Defines all the columns and filters that can be added to reports that use this entity. + * + * @package report_configlog + * @copyright 2020 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class config_change extends base { + + /** + * Database tables that this entity uses and their default aliases + * + * @return array + */ + protected function get_default_table_aliases(): array { + return ['config_log' => 'cl']; + } + + /** + * The default title for this entity + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('entityconfigchange', 'report_configlog'); + } + + /** + * Initialize the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this->add_filter($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $tablealias = $this->get_table_alias('config_log'); + + // Time modified column. + $columns[] = (new column( + 'timemodified', + new lang_string('timemodified', 'report_configlog'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TIMESTAMP) + ->add_fields("{$tablealias}.timemodified") + ->set_is_sortable(true) + ->add_callback([format::class, 'userdate']); + + // Plugin column. + $columns[] = (new column( + 'plugin', + new lang_string('plugin', 'report_configlog'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$tablealias}.plugin") + ->set_is_sortable(true) + ->add_callback(static function(?string $plugin): string { + return $plugin ?? 'core'; + }); + + // Setting column. + $columns[] = (new column( + 'setting', + new lang_string('setting', 'report_configlog'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$tablealias}.name") + ->set_is_sortable(true); + + // New value column. + $columns[] = (new column( + 'newvalue', + new lang_string('valuenew', 'report_configlog'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$tablealias}.value") + ->set_is_sortable(true) + ->add_callback(static function(?string $value): string { + return format_text($value, FORMAT_PLAIN); + }); + + // Old value column. + $columns[] = (new column( + 'oldvalue', + new lang_string('valueold', 'report_configlog'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$tablealias}.oldvalue") + ->set_is_sortable(true) + ->add_callback(static function(?string $oldvalue): string { + return format_text($oldvalue, FORMAT_PLAIN); + }); + + return $columns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + global $DB; + + $tablealias = $this->get_table_alias('config_log'); + + // Time modified filter. + $filters[] = (new filter( + date::class, + 'timemodified', + new lang_string('timemodified', 'report_configlog'), + $this->get_entity_name(), + "{$tablealias}.timemodified" + )) + ->add_joins($this->get_joins()) + ->set_limited_operators([ + date::DATE_ANY, + date::DATE_RANGE, + ]); + + // Setting filter. + $filters[] = (new filter( + text::class, + 'setting', + new lang_string('setting', 'report_configlog'), + $this->get_entity_name(), + "{$tablealias}.name" + )) + ->add_joins($this->get_joins()); + + // New value filter. + $filters[] = (new filter( + text::class, + 'value', + new lang_string('valuenew', 'report_configlog'), + $this->get_entity_name(), + "{$tablealias}.value" + )) + ->add_joins($this->get_joins()); + + // Old value filter. + $filters[] = (new filter( + text::class, + 'oldvalue', + new lang_string('valueold', 'report_configlog'), + $this->get_entity_name(), + "{$tablealias}.oldvalue" + )) + ->add_joins($this->get_joins()); + + return $filters; + } +} diff --git a/report/configlog/classes/local/systemreports/config_changes.php b/report/configlog/classes/local/systemreports/config_changes.php new file mode 100644 index 00000000000..90973fb1eb2 --- /dev/null +++ b/report/configlog/classes/local/systemreports/config_changes.php @@ -0,0 +1,115 @@ +. + +namespace report_configlog\local\systemreports; + +use context_system; +use report_configlog\local\entities\config_change as config_change_entity; +use core_reportbuilder\system_report; +use core_reportbuilder\local\entities\user as user_entity; +use stdClass; + +/** + * Config changes system report class implementation + * + * @package report_configlog + * @copyright 2020 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class config_changes extends system_report { + + /** + * Initialise report, we need to set the main table, load our entities and set columns/filters + */ + protected function initialise(): void { + // Our main entity, it contains all of the column definitions that we need. + $entitymain = new config_change_entity(); + $entitymainalias = $entitymain->get_table_alias('config_log'); + + $this->set_main_table('config_log', $entitymainalias); + $this->add_entity($entitymain); + + // We can join the "user" entity to our "main" entity using standard SQL JOIN. + $entityuser = new user_entity(); + $entityuseralias = $entityuser->get_table_alias('user'); + $this->add_entity($entityuser + ->add_join("LEFT JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = {$entitymainalias}.userid") + ); + + // Now we can call our helper methods to add the content we want to include in the report. + $this->add_columns(); + $this->add_filters(); + + // Set if report can be downloaded. + $this->set_downloadable(true, get_string('pluginname', 'report_configlog')); + } + + /** + * Validates access to view this report + * + * @return bool + */ + protected function can_view(): bool { + return has_capability('moodle/site:config', context_system::instance()); + } + + /** + * 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 + */ + protected function add_columns(): void { + $columns = [ + 'config_change:timemodified', + 'user:fullnamewithlink', + 'config_change:plugin', + 'config_change:setting', + 'config_change:newvalue', + 'config_change:oldvalue', + ]; + + $this->add_columns_from_entities($columns); + + // Default sorting. + $this->set_initial_sort_column('config_change:timemodified', SORT_DESC); + + // Custom callback to show 'CLI or install' in fullname column when there is no user. + if ($column = $this->get_column('user:fullnamewithlink')) { + $column->add_callback(static function(string $fullname, stdClass $row): string { + return $fullname ?: get_string('usernone', 'report_configlog'); + }); + } + } + + /** + * 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 = [ + 'config_change:setting', + 'config_change:value', + 'config_change:oldvalue', + 'user:fullname', + 'config_change:timemodified', + ]; + + $this->add_filters_from_entities($filters); + } +} diff --git a/report/configlog/classes/output/renderer.php b/report/configlog/classes/output/renderer.php deleted file mode 100644 index 8d612bb5602..00000000000 --- a/report/configlog/classes/output/renderer.php +++ /dev/null @@ -1,57 +0,0 @@ -. - -/** - * Plugin renderer class. - * - * @package report_configlog - * @copyright 2019 Paul Holden (pholden@greenhead.ac.uk) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace report_configlog\output; - -defined('MOODLE_INTERNAL') || die(); - -/** - * Plugin renderer class. - * - * @package report_configlog - * @copyright 2019 Paul Holden (pholden@greenhead.ac.uk) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class renderer extends \plugin_renderer_base { - - /** @var int Page size for displaying report table. */ - const REPORT_TABLE_PAGESIZE = 30; - - /** - * Return output to be rendered to page - * - * @param report_table $table - * @return string HTML rendered table - */ - protected function render_report_table(report_table $table) { - ob_start(); - - $table->out(self::REPORT_TABLE_PAGESIZE, false); - $output = ob_get_contents(); - - ob_end_clean(); - - return $output; - } -} diff --git a/report/configlog/classes/output/report_table.php b/report/configlog/classes/output/report_table.php deleted file mode 100644 index d334fc5c11d..00000000000 --- a/report/configlog/classes/output/report_table.php +++ /dev/null @@ -1,194 +0,0 @@ -. - -/** - * Report table class. - * - * @package report_configlog - * @copyright 2019 Paul Holden (pholden@greenhead.ac.uk) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace report_configlog\output; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/searchlib.php'); -require_once($CFG->libdir . '/tablelib.php'); - -/** - * Report table class. - * - * @package report_configlog - * @copyright 2019 Paul Holden (pholden@greenhead.ac.uk) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class report_table extends \table_sql implements \renderable { - - /** @var string $search */ - protected $search; - - /** - * Constructor - * - * @param string $search - */ - public function __construct(string $search) { - parent::__construct('report-configlog-report-table'); - - $this->search = trim($search); - - // Define columns. - $columns = [ - 'timemodified' => get_string('timemodified', 'report_configlog'), - 'fullname' => get_string('name'), - 'plugin' => get_string('plugin', 'report_configlog'), - 'name' => get_string('setting', 'report_configlog'), - 'value' => get_string('valuenew', 'report_configlog'), - 'oldvalue' => get_string('valueold', 'report_configlog'), - ]; - $this->define_columns(array_keys($columns)); - $this->define_headers(array_values($columns)); - - // Table configuration. - $this->set_attribute('id', $this->uniqueid); - $this->set_attribute('cellspacing', '0'); - - $this->sortable(true, 'timemodified', SORT_DESC); - - $this->initialbars(false); - $this->collapsible(false); - - $this->useridfield = 'userid'; - - // Initialize table SQL properties. - $this->init_sql(); - } - - /** - * Initializes table SQL properties - * - * @return void - */ - protected function init_sql() { - global $DB; - - $userfieldsapi = \core_user\fields::for_name(); - $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; - $fields = 'cl.id, cl.timemodified, cl.plugin, cl.name, cl.value, cl.oldvalue, cl.userid, ' . $userfields; - - $from = '{config_log} cl - LEFT JOIN {user} u ON u.id = cl.userid'; - - // Report search. - $where = '1=1'; - $params = []; - - if (!empty($this->search)) { - // Clean quotes, allow search by 'setting:' prefix. - $searchstring = str_replace(["\\\"", 'setting:'], ["\"", 'subject:'], $this->search); - - $parser = new \search_parser(); - $lexer = new \search_lexer($parser); - - if ($lexer->parse($searchstring)) { - $parsearray = $parser->get_parsed_array(); - - // Data fields should contain both value/oldvalue. - $datafields = $DB->sql_concat_join("':'", ['cl.value', 'cl.oldvalue']); - - list($where, $params) = search_generate_SQL($parsearray, $datafields, 'cl.name', 'cl.userid', 'u.id', - 'u.firstname', 'u.lastname', 'cl.timemodified', 'cl.id'); - } - } - - $this->set_sql($fields, $from, $where, $params); - $this->set_count_sql('SELECT COUNT(1) FROM ' . $from . ' WHERE ' . $where, $params); - } - - /** - * Cross DB text-compatible sorting for value/oldvalue fields - * - * @return string - */ - public function get_sql_sort() { - global $DB; - - $sort = preg_replace_callback('/\b(value|oldvalue)\b/', function(array $matches) use ($DB) { - return $DB->sql_order_by_text($matches[1], 255); - }, parent::get_sql_sort()); - - return $sort; - } - - /** - * Format report timemodified field - * - * @param stdClass $row - * @return string - */ - public function col_timemodified(\stdClass $row) { - return userdate($row->timemodified); - } - - /** - * Format fullname field - * - * @param stdClass $row - * @return string - */ - public function col_fullname($row) { - - $userid = $row->{$this->useridfield}; - if (empty($userid)) { - // If the user id is empty it must have been set via the - // admin/cli/cfg.php script or during the initial install. - return get_string('usernone', 'report_configlog'); - } else { - return parent::col_fullname($row); - } - } - - /** - * Format report plugin field - * - * @param stdClass $row - * @return string - */ - public function col_plugin(\stdClass $row) { - return $row->plugin ?? 'core'; - } - - /** - * Format report value field - * - * @param stdClass $row - * @return string - */ - public function col_value(\stdClass $row) { - return $this->format_text($row->value, FORMAT_PLAIN); - } - - /** - * Format report old value field - * - * @param stdClass $row - * @return string - */ - public function col_oldvalue(\stdClass $row) { - return $this->format_text($row->oldvalue, FORMAT_PLAIN);; - } -} diff --git a/report/configlog/index.php b/report/configlog/index.php index 91d4ba8f4bf..b360a2226cf 100644 --- a/report/configlog/index.php +++ b/report/configlog/index.php @@ -23,6 +23,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core_reportbuilder\system_report_factory; +use core_reportbuilder\local\filters\text; +use report_configlog\local\systemreports\config_changes; + require(__DIR__.'/../../config.php'); require_once($CFG->libdir.'/adminlib.php'); @@ -34,50 +38,15 @@ admin_externalpage_setup('reportconfiglog', '', ['search' => $search], '', ['pag echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('configlog', 'report_configlog')); -/** @var cache_session $cache */ -$cache = cache::make_from_params(cache_store::MODE_SESSION, 'report_customlog', 'search'); - +// Create out report instance, setting initial filtering if required. +$report = system_report_factory::create(config_changes::class, context_system::instance()); if (!empty($search)) { - $searchdata = (object) ['setting' => $search]; -} else { - $searchdata = $cache->get('data'); + $report->set_filter_values([ + 'config_change:setting_operator' => text::IS_EQUAL_TO, + 'config_change:setting_value' => $search, + ]); } -$mform = new \report_configlog\form\search(); -$mform->set_data($searchdata); +echo $report->output(); -$searchclauses = []; - -// Check if we have a form submission, or a cached submission. -$data = ($mform->is_submitted() ? $mform->get_data() : fullclone($searchdata)); -if ($data instanceof stdClass) { - if (!empty($data->value)) { - $searchclauses[] = $data->value; - } - if (!empty($data->setting)) { - $searchclauses[] = "setting:{$data->setting}"; - } - if (!empty($data->user)) { - $searchclauses[] = "user:{$data->user}"; - } - if (!empty($data->datefrom)) { - $searchclauses[] = "datefrom:{$data->datefrom}"; - } - if (!empty($data->dateto)) { - $dateto = $data->dateto + DAYSECS - 1; - $searchclauses[] = "dateto:{$dateto}"; - } - - // Cache form submission so that it is preserved while paging through the report. - unset($data->submitbutton); - $cache->set('data', $data); -} - -$mform->display(); - -$table = new \report_configlog\output\report_table(implode(' ', $searchclauses)); -$table->define_baseurl($PAGE->url); - -echo $PAGE->get_renderer('report_configlog')->render($table); - -echo $OUTPUT->footer(); \ No newline at end of file +echo $OUTPUT->footer(); diff --git a/report/configlog/lang/en/report_configlog.php b/report/configlog/lang/en/report_configlog.php index 643c7b6d103..4c0b94add46 100644 --- a/report/configlog/lang/en/report_configlog.php +++ b/report/configlog/lang/en/report_configlog.php @@ -26,6 +26,7 @@ $string['configlog'] = 'Config changes'; $string['datefrom'] = 'Date from'; $string['dateto'] = 'Date to'; +$string['entityconfigchange'] = 'Config change'; $string['plugin'] = 'Plugin'; $string['pluginname'] = 'Config changes'; $string['setting'] = 'Setting'; diff --git a/report/configlog/tests/behat/view_report.feature b/report/configlog/tests/behat/view_report.feature index 17673019e3c..0df50d4af7c 100644 --- a/report/configlog/tests/behat/view_report.feature +++ b/report/configlog/tests/behat/view_report.feature @@ -1,4 +1,4 @@ -@report @report_configlog +@report @report_configlog @core_reportbuilder Feature: In a report, admin can see configuration changes In order see configuration changes As an admin @@ -16,7 +16,7 @@ Feature: In a report, admin can see configuration changes @javascript Scenario: Display configuration changes report When I navigate to "Reports > Config changes" in site administration - Then the following should exist in the "report-configlog-report-table" table: + Then the following should exist in the "reportbuilder-table" table: | User | Plugin | Setting | New value | Original value | | Admin User | quiz | initialnumfeedbacks | 5 | 2 | | Admin User | folder | maxsizetodownload | 2048 | 0 | @@ -25,16 +25,19 @@ Feature: In a report, admin can see configuration changes @javascript Scenario Outline: Search configuration changes report When I navigate to "Reports > Config changes" in site administration - And I click on "Show more..." "link" - And I set the field "" to "" - And I click on "Search" "button" in the "#fitem_id_submitbutton" "css_element" - Then the following should exist in the "report-configlog-report-table" table: + And I click on "Filters" "button" + And I set the following fields in the "" "core_reportbuilder > Filter" to these values: + | operator | Contains | + | value | | + And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" + And I should see "Filters applied" + Then the following should exist in the "reportbuilder-table" table: | Plugin | Setting | New value | | | | | - And I should not see "" in the "report-configlog-report-table" "table" + And I should not see "" in the "reportbuilder-table" "table" Examples: - | field | search | plugin | setting | value | excluded | - | Setting | initialnumfeedbacks | quiz | initialnumfeedbacks | 5 | maxsizetodownload | - | Setting | maxsizetodownload | folder | maxsizetodownload | 2048 | initialnumfeedbacks | - | Value | Perth | core | defaultcity | Perth | maxsizetodownload | - | User | Admin | core | defaultcity | Perth | zzzzzzzzz | + | field | search | plugin | setting | value | excluded | + | Setting | initialnumfeedbacks | quiz | initialnumfeedbacks | 5 | maxsizetodownload | + | Setting | maxsizetodownload | folder | maxsizetodownload | 2048 | initialnumfeedbacks | + | New value | Perth | core | defaultcity | Perth | maxsizetodownload | + | Full name | Admin User | core | defaultcity | Perth | zzzzzzzzz |