diff --git a/classes/helper.php b/classes/helper.php
index e042c72..222302d 100644
--- a/classes/helper.php
+++ b/classes/helper.php
@@ -171,4 +171,30 @@ public static function merge_duplicate_records(array $records): \stdClass {
return $baserecord;
}
+
+ /**
+ * Estimate row count for local_csp table
+ *
+ * @return int
+ */
+ public static function get_row_estimate(): int {
+ global $CFG, $DB;
+
+ if ($DB->get_dbfamily() === 'mysql') {
+ // Use explain to keep accurate count after truncate.
+ $explainsql = 'EXPLAIN SELECT COUNT(1) FROM {local_csp}';
+ $record = $DB->get_record_sql($explainsql);
+ return $record->rows ?? 0;
+ } else if ($DB->get_dbfamily() === 'postgres') {
+ $sql = "SELECT relname AS table_name, reltuples::BIGINT AS row_count
+ FROM pg_class
+ WHERE relname = :tablename";
+ $params = ['tablename' => $CFG->prefix . 'local_csp'];
+ $record = $DB->get_record_sql($sql, $params);
+ return $record->row_count ?? 0;
+ } else {
+ // Estimates not currently supported for other databases, use a full count.
+ return $DB->count_records('local_csp');
+ }
+ }
}
diff --git a/csp_report.php b/csp_report.php
index 8e16335..f6a20db 100644
--- a/csp_report.php
+++ b/csp_report.php
@@ -29,6 +29,7 @@
$viewviolation = optional_param('violation', false, PARAM_TEXT);
$removeviolation = optional_param('removeviolation', false, PARAM_TEXT);
$removerecordwithid = optional_param('removerecordwithid', false, PARAM_TEXT);
+$limitedreport = optional_param('limited', null, PARAM_INT);
$download = optional_param('download', '', PARAM_ALPHA);
admin_externalpage_setup('local_csp_report', '', null, '', array('pagelayout' => 'report'));
@@ -113,6 +114,12 @@
$action,
));
+// The local_csp table can grow very large if a policy is not set up correctly.
+// This report can be slow even with indexes, so this can be used to load a limited version.
+if (!isset($limitedreport)) {
+ $limitedreport = \local_csp\helper::get_row_estimate() > 5000000;
+}
+
// If user has clicked on a violation to view all violation entries.
if ($viewviolation) {
$fields = 'id, sha1hash, blockeduri, blockeddomain, violateddirective, failcounter, timeupdated, documenturi, violationhash';
@@ -138,6 +145,38 @@
$action,
));
+ // Disable sorting on large tables as sorting by failcounter requires the full table.
+ if ($limitedreport) {
+ $table->sortable(false);
+ }
+
+} else if ($limitedreport) {
+ // If a table is too large only load the fields that can be loaded using hashes and no aggregation.
+ $table->define_columns([
+ 'failcounter',
+ 'violateddirective',
+ 'blockeddomain',
+ 'timecreated',
+ 'action',
+ ]);
+ $table->define_headers([
+ $failcounter,
+ $violateddirective,
+ $blockeddomain,
+ $timeupdated,
+ $action,
+ ]);
+
+ // Summing failcounter may be too slow on huge tables, so use violationcount as a rough indicator instead.
+ $fields = 'id, blockeddomain, violateddirective, violationhash, violationcount as failcounter, timecreated';
+ $from = "(SELECT MAX(id) AS maxid, count(1) AS violationcount
+ FROM {local_csp}
+ WHERE violationhash IS NOT NULL
+ GROUP BY violationhash) AS A
+ JOIN {local_csp} ON id = maxid";
+ $where = '1 = 1';
+ $params = [];
+
} else {
$fields = 'id, blockeddomain, violateddirective, violationhash, A.failcounter, A.timecreated';
// Select the first blockedURI of a type, and collapse the rest while summing failcounter.
@@ -152,10 +191,22 @@
$params = [];
}
+if (!$viewviolation) {
+ // Simplify count SQL to also speed up processing.
+ $table->set_count_sql("SELECT COUNT(DISTINCT violationhash)
+ FROM {local_csp}
+ WHERE violationhash IS NOT NULL");
+}
+
if (!$table->is_downloading()) {
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
+ if ($limitedreport) {
+ $fullreport = new moodle_url($PAGE->url, ['limited' => 0]);
+ echo $OUTPUT->notification(get_string('limitedreport', 'local_csp', $fullreport->out()), 'warning');
+ }
+
$action = new \confirm_action(get_string('areyousuretodeleteallrecords', 'local_csp'));
$urlresetallcspstatistics = new moodle_url($PAGE->url, array(
'resetallcspstatistics' => 1,
diff --git a/lang/en/local_csp.php b/lang/en/local_csp.php
index ef2834e..a2f9444 100644
--- a/lang/en/local_csp.php
+++ b/lang/en/local_csp.php
@@ -57,6 +57,7 @@
$string['failcounter'] = '#';
$string['highestviolaters'] = 'Top Violation Sources';
$string['invalidblockeduri'] = 'Invalid Blocked URI: {$a}';
+$string['limitedreport'] = 'The report columns and sorting options have been limited due to a large amount of data. This is usually an indication that the policy is not set up correctly, consider updating the policy and resetting the statistics. Load full report.';
$string['loaddata'] = 'Load data';
$string['loadexternaljavascript'] = 'Load external javascript from {$a}';
$string['loadingmixedcontentdescription'] = 'When accessing moodle website via HTTPS browser prohibits displaying of the below resources because they origin from HTTP.
You should be able to see it in your browser\'s Javascript console.';