mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 00:16:46 +02:00
MDL-48595 logstore: Replacing references to deprecated interfaces
This commit is contained in:
parent
1cfce08e63
commit
59aebbed70
19 changed files with 44 additions and 44 deletions
|
@ -48,24 +48,24 @@ class tool_log_manager_testcase extends advanced_testcase {
|
||||||
$this->assertCount(2, $stores);
|
$this->assertCount(2, $stores);
|
||||||
foreach ($stores as $key => $store) {
|
foreach ($stores as $key => $store) {
|
||||||
$this->assertInternalType('string', $key);
|
$this->assertInternalType('string', $key);
|
||||||
$this->assertInstanceOf('core\log\sql_select_reader', $store);
|
$this->assertInstanceOf('core\log\sql_reader', $store);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stores = $manager->get_readers('core\log\sql_internal_reader');
|
$stores = $manager->get_readers('core\log\sql_internal_table_reader');
|
||||||
$this->assertInternalType('array', $stores);
|
$this->assertInternalType('array', $stores);
|
||||||
$this->assertCount(1, $stores);
|
$this->assertCount(1, $stores);
|
||||||
foreach ($stores as $key => $store) {
|
foreach ($stores as $key => $store) {
|
||||||
$this->assertInternalType('string', $key);
|
$this->assertInternalType('string', $key);
|
||||||
$this->assertSame('logstore_standard', $key);
|
$this->assertSame('logstore_standard', $key);
|
||||||
$this->assertInstanceOf('core\log\sql_internal_reader', $store);
|
$this->assertInstanceOf('core\log\sql_internal_table_reader', $store);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stores = $manager->get_readers('core\log\sql_select_reader');
|
$stores = $manager->get_readers('core\log\sql_reader');
|
||||||
$this->assertInternalType('array', $stores);
|
$this->assertInternalType('array', $stores);
|
||||||
$this->assertCount(2, $stores);
|
$this->assertCount(2, $stores);
|
||||||
foreach ($stores as $key => $store) {
|
foreach ($stores as $key => $store) {
|
||||||
$this->assertInternalType('string', $key);
|
$this->assertInternalType('string', $key);
|
||||||
$this->assertInstanceOf('core\log\sql_select_reader', $store);
|
$this->assertInstanceOf('core\log\sql_reader', $store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -656,7 +656,7 @@ abstract class backup_cron_automated_helper {
|
||||||
*/
|
*/
|
||||||
protected static function is_course_modified($courseid, $since) {
|
protected static function is_course_modified($courseid, $since) {
|
||||||
$logmang = get_log_manager();
|
$logmang = get_log_manager();
|
||||||
$readers = $logmang->get_readers('core\log\sql_select_reader');
|
$readers = $logmang->get_readers('core\log\sql_reader');
|
||||||
$where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
|
$where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
|
||||||
$params = array('courseid' => $courseid, 'since' => $since);
|
$params = array('courseid' => $courseid, 'since' => $since);
|
||||||
foreach ($readers as $reader) {
|
foreach ($readers as $reader) {
|
||||||
|
|
|
@ -2437,7 +2437,7 @@ function can_delete_course($courseid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$logmanger = get_log_manager();
|
$logmanger = get_log_manager();
|
||||||
$readers = $logmanger->get_readers('\core\log\sql_select_reader');
|
$readers = $logmanger->get_readers('\core\log\sql_reader');
|
||||||
$reader = reset($readers);
|
$reader = reset($readers);
|
||||||
|
|
||||||
if (empty($reader)) {
|
if (empty($reader)) {
|
||||||
|
|
|
@ -72,7 +72,7 @@ class send_failed_login_notifications_task extends scheduled_task {
|
||||||
// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
|
// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
|
||||||
// and insert them into the cache_flags temp table.
|
// and insert them into the cache_flags temp table.
|
||||||
$logmang = get_log_manager();
|
$logmang = get_log_manager();
|
||||||
$readers = $logmang->get_readers('\core\log\sql_internal_reader');
|
$readers = $logmang->get_readers('\core\log\sql_internal_table_reader');
|
||||||
$reader = reset($readers);
|
$reader = reset($readers);
|
||||||
$readername = key($readers);
|
$readername = key($readers);
|
||||||
if (empty($reader) || empty($readername)) {
|
if (empty($reader) || empty($readername)) {
|
||||||
|
|
|
@ -949,7 +949,7 @@ function stats_get_start_from($str) {
|
||||||
$stores = $manager->get_readers();
|
$stores = $manager->get_readers();
|
||||||
$firstlog = false;
|
$firstlog = false;
|
||||||
foreach ($stores as $store) {
|
foreach ($stores as $store) {
|
||||||
if ($store instanceof \core\log\sql_internal_reader) {
|
if ($store instanceof \core\log\sql_internal_table_reader) {
|
||||||
$logtable = $store->get_internal_log_table_name();
|
$logtable = $store->get_internal_log_table_name();
|
||||||
if (!$logtable) {
|
if (!$logtable) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1767,7 +1767,7 @@ function stats_temp_table_fill($timestart, $timeend) {
|
||||||
$manager = get_log_manager();
|
$manager = get_log_manager();
|
||||||
$stores = $manager->get_readers();
|
$stores = $manager->get_readers();
|
||||||
foreach ($stores as $store) {
|
foreach ($stores as $store) {
|
||||||
if ($store instanceof \core\log\sql_internal_reader) {
|
if ($store instanceof \core\log\sql_internal_table_reader) {
|
||||||
$logtable = $store->get_internal_log_table_name();
|
$logtable = $store->get_internal_log_table_name();
|
||||||
if (!$logtable) {
|
if (!$logtable) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -160,17 +160,17 @@ class report_log_renderable implements renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of enabled sql_select_reader objects/name
|
* Get a list of enabled sql_reader objects/name
|
||||||
*
|
*
|
||||||
* @param bool $nameonly if true only reader names will be returned.
|
* @param bool $nameonly if true only reader names will be returned.
|
||||||
* @return array core\log\sql_select_reader object or name.
|
* @return array core\log\sql_reader object or name.
|
||||||
*/
|
*/
|
||||||
public function get_readers($nameonly = false) {
|
public function get_readers($nameonly = false) {
|
||||||
if (!isset($this->logmanager)) {
|
if (!isset($this->logmanager)) {
|
||||||
$this->logmanager = get_log_manager();
|
$this->logmanager = get_log_manager();
|
||||||
}
|
}
|
||||||
|
|
||||||
$readers = $this->logmanager->get_readers('core\log\sql_select_reader');
|
$readers = $this->logmanager->get_readers('core\log\sql_reader');
|
||||||
if ($nameonly) {
|
if ($nameonly) {
|
||||||
foreach ($readers as $pluginname => $reader) {
|
foreach ($readers as $pluginname => $reader) {
|
||||||
$readers[$pluginname] = $reader->get_name();
|
$readers[$pluginname] = $reader->get_name();
|
||||||
|
|
|
@ -48,7 +48,7 @@ function report_log_extend_navigation_course($navigation, $course, $context) {
|
||||||
* @return bool returns true if the store is supported by the report, false otherwise.
|
* @return bool returns true if the store is supported by the report, false otherwise.
|
||||||
*/
|
*/
|
||||||
function report_log_supports_logstore($instance) {
|
function report_log_supports_logstore($instance) {
|
||||||
if ($instance instanceof \core\log\sql_select_reader) {
|
if ($instance instanceof \core\log\sql_reader) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -52,8 +52,8 @@ function report_log_print_graph($course, $userid, $type, $date=0, $logreader='')
|
||||||
} else {
|
} else {
|
||||||
$reader = $readers[$logreader];
|
$reader = $readers[$logreader];
|
||||||
}
|
}
|
||||||
// If reader is not a sql_internal_reader and not legacy store then don't show graph.
|
// If reader is not a sql_internal_table_reader and not legacy store then don't show graph.
|
||||||
if (!($reader instanceof \core\log\sql_internal_reader) && !($reader instanceof logstore_legacy\log\store)) {
|
if (!($reader instanceof \core\log\sql_internal_table_reader) && !($reader instanceof logstore_legacy\log\store)) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +82,8 @@ function report_log_usercourse($userid, $courseid, $coursestart, $logreader = ''
|
||||||
$reader = $readers[$logreader];
|
$reader = $readers[$logreader];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If reader is not a sql_internal_reader and not legacy store then return.
|
// If reader is not a sql_internal_table_reader and not legacy store then return.
|
||||||
if (!($reader instanceof \core\log\sql_internal_reader) && !($reader instanceof logstore_legacy\log\store)) {
|
if (!($reader instanceof \core\log\sql_internal_table_reader) && !($reader instanceof logstore_legacy\log\store)) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +134,8 @@ function report_log_userday($userid, $courseid, $daystart, $logreader = '') {
|
||||||
$reader = $readers[$logreader];
|
$reader = $readers[$logreader];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If reader is not a sql_internal_reader and not legacy store then return.
|
// If reader is not a sql_internal_table_reader and not legacy store then return.
|
||||||
if (!($reader instanceof \core\log\sql_internal_reader) && !($reader instanceof logstore_legacy\log\store)) {
|
if (!($reader instanceof \core\log\sql_internal_table_reader) && !($reader instanceof logstore_legacy\log\store)) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,18 +124,18 @@ class report_loglive_renderable implements renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of enabled sql_select_reader objects/name
|
* Get a list of enabled sql_reader objects/name
|
||||||
*
|
*
|
||||||
* @param bool $nameonly if true only reader names will be returned.
|
* @param bool $nameonly if true only reader names will be returned.
|
||||||
*
|
*
|
||||||
* @return array core\log\sql_select_reader object or name.
|
* @return array core\log\sql_reader object or name.
|
||||||
*/
|
*/
|
||||||
public function get_readers($nameonly = false) {
|
public function get_readers($nameonly = false) {
|
||||||
if (!isset($this->logmanager)) {
|
if (!isset($this->logmanager)) {
|
||||||
$this->logmanager = get_log_manager();
|
$this->logmanager = get_log_manager();
|
||||||
}
|
}
|
||||||
|
|
||||||
$readers = $this->logmanager->get_readers('core\log\sql_select_reader');
|
$readers = $this->logmanager->get_readers('core\log\sql_reader');
|
||||||
if ($nameonly) {
|
if ($nameonly) {
|
||||||
foreach ($readers as $pluginname => $reader) {
|
foreach ($readers as $pluginname => $reader) {
|
||||||
$readers[$pluginname] = $reader->get_name();
|
$readers[$pluginname] = $reader->get_name();
|
||||||
|
|
|
@ -56,7 +56,7 @@ class report_loglive_table_log extends table_sql {
|
||||||
* - int userid: user id
|
* - int userid: user id
|
||||||
* - int|string modid: Module id or "site_errors" to view site errors
|
* - int|string modid: Module id or "site_errors" to view site errors
|
||||||
* - int groupid: Group id
|
* - int groupid: Group id
|
||||||
* - \core\log\sql_select_reader logreader: reader from which data will be fetched.
|
* - \core\log\sql_reader logreader: reader from which data will be fetched.
|
||||||
* - int edulevel: educational level.
|
* - int edulevel: educational level.
|
||||||
* - string action: view action
|
* - string action: view action
|
||||||
* - int date: Date from which logs to be viewed.
|
* - int date: Date from which logs to be viewed.
|
||||||
|
|
|
@ -51,7 +51,7 @@ function report_loglive_extend_navigation_course($navigation, $course, $context)
|
||||||
* @return bool returns true if the store is supported by the report, false otherwise.
|
* @return bool returns true if the store is supported by the report, false otherwise.
|
||||||
*/
|
*/
|
||||||
function report_loglive_supports_logstore($instance) {
|
function report_loglive_supports_logstore($instance) {
|
||||||
if ($instance instanceof \core\log\sql_select_reader) {
|
if ($instance instanceof \core\log\sql_reader) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -134,7 +134,7 @@ if ($uselegacyreader) {
|
||||||
$views = $DB->get_records_sql($sql, $params);
|
$views = $DB->get_records_sql($sql, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get record from sql_internal_reader and merge with records obtained from legacy log (if needed).
|
// Get record from sql_internal_table_reader and merge with records obtained from legacy log (if needed).
|
||||||
if ($useinternalreader) {
|
if ($useinternalreader) {
|
||||||
// Check if we need to show the last access.
|
// Check if we need to show the last access.
|
||||||
$sqllasttime = '';
|
$sqllasttime = '';
|
||||||
|
|
|
@ -114,7 +114,7 @@ function report_outline_page_type_list($pagetype, $parentcontext, $currentcontex
|
||||||
* @return bool returns true if the store is supported by the report, false otherwise.
|
* @return bool returns true if the store is supported by the report, false otherwise.
|
||||||
*/
|
*/
|
||||||
function report_outline_supports_logstore($instance) {
|
function report_outline_supports_logstore($instance) {
|
||||||
if ($instance instanceof \core\log\sql_internal_reader || $instance instanceof \logstore_legacy\log\store) {
|
if ($instance instanceof \core\log\sql_internal_table_reader || $instance instanceof \logstore_legacy\log\store) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -87,8 +87,8 @@ function report_outline_get_common_log_variables() {
|
||||||
$uselegacyreader = true;
|
$uselegacyreader = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If sql_internal_reader is preferred reader.
|
// If sql_internal_table_reader is preferred reader.
|
||||||
if ($reader instanceof \core\log\sql_internal_reader) {
|
if ($reader instanceof \core\log\sql_internal_table_reader) {
|
||||||
$useinternalreader = true;
|
$useinternalreader = true;
|
||||||
$logtable = $reader->get_internal_log_table_name();
|
$logtable = $reader->get_internal_log_table_name();
|
||||||
$minloginternalreader = $DB->get_field_sql('SELECT min(timecreated) FROM {' . $logtable . '}');
|
$minloginternalreader = $DB->get_field_sql('SELECT min(timecreated) FROM {' . $logtable . '}');
|
||||||
|
@ -144,7 +144,7 @@ function report_outline_user_outline($userid, $cmid, $module, $instanceid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get record from sql_internal_reader and combine with the number of views from the legacy log table (if needed).
|
// Get record from sql_internal_table_reader and combine with the number of views from the legacy log table (if needed).
|
||||||
if ($useinternalreader) {
|
if ($useinternalreader) {
|
||||||
$params = array('userid' => $userid, 'contextlevel' => CONTEXT_MODULE, 'contextinstanceid' => $cmid, 'crud' => 'r',
|
$params = array('userid' => $userid, 'contextlevel' => CONTEXT_MODULE, 'contextinstanceid' => $cmid, 'crud' => 'r',
|
||||||
'edulevel1' => core\event\base::LEVEL_PARTICIPATING, 'edulevel2' => core\event\base::LEVEL_TEACHING,
|
'edulevel1' => core\event\base::LEVEL_PARTICIPATING, 'edulevel2' => core\event\base::LEVEL_TEACHING,
|
||||||
|
@ -223,7 +223,7 @@ function report_outline_user_complete($userid, $cmid, $module, $instanceid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get record from sql_internal_reader and combine with the number of views from the legacy log table (if needed).
|
// Get record from sql_internal_table_reader and combine with the number of views from the legacy log table (if needed).
|
||||||
if ($useinternalreader) {
|
if ($useinternalreader) {
|
||||||
$params = array('userid' => $userid, 'contextlevel' => CONTEXT_MODULE, 'contextinstanceid' => $cmid, 'crud' => 'r',
|
$params = array('userid' => $userid, 'contextlevel' => CONTEXT_MODULE, 'contextinstanceid' => $cmid, 'crud' => 'r',
|
||||||
'edulevel1' => core\event\base::LEVEL_PARTICIPATING, 'edulevel2' => core\event\base::LEVEL_TEACHING,
|
'edulevel1' => core\event\base::LEVEL_PARTICIPATING, 'edulevel2' => core\event\base::LEVEL_TEACHING,
|
||||||
|
|
|
@ -79,7 +79,7 @@ $PAGE->set_title($course->shortname .': '. $strparticipation);
|
||||||
$PAGE->set_heading($course->fullname);
|
$PAGE->set_heading($course->fullname);
|
||||||
echo $OUTPUT->header();
|
echo $OUTPUT->header();
|
||||||
|
|
||||||
$uselegacyreader = false; // Use legacy reader with sql_internal_reader to aggregate records.
|
$uselegacyreader = false; // Use legacy reader with sql_internal_table_reader to aggregate records.
|
||||||
$onlyuselegacyreader = false; // Use only legacy log table to aggregate records.
|
$onlyuselegacyreader = false; // Use only legacy log table to aggregate records.
|
||||||
|
|
||||||
$logtable = report_participation_get_log_table_name(); // Log table to use for fetaching records.
|
$logtable = report_participation_get_log_table_name(); // Log table to use for fetaching records.
|
||||||
|
@ -100,7 +100,7 @@ if (!$onlyuselegacyreader && empty($logtable)) {
|
||||||
|
|
||||||
$modinfo = get_fast_modinfo($course);
|
$modinfo = get_fast_modinfo($course);
|
||||||
|
|
||||||
$minloginternalreader = 0; // Time of first record in sql_internal_reader.
|
$minloginternalreader = 0; // Time of first record in sql_internal_table_reader.
|
||||||
|
|
||||||
if ($onlyuselegacyreader) {
|
if ($onlyuselegacyreader) {
|
||||||
// If no sql_inrenal_reader enabled then get min. time from log table.
|
// If no sql_inrenal_reader enabled then get min. time from log table.
|
||||||
|
@ -119,7 +119,7 @@ if ($onlyuselegacyreader) {
|
||||||
$minlog = $minloginternalreader;
|
$minlog = $minloginternalreader;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If timefrom is greater then first record in sql_internal_reader then get record from sql_internal_reader only.
|
// If timefrom is greater then first record in sql_internal_table_reader then get record from sql_internal_table_reader only.
|
||||||
if (!empty($timefrom) && ($minloginternalreader < $timefrom)) {
|
if (!empty($timefrom) && ($minloginternalreader < $timefrom)) {
|
||||||
$uselegacyreader = false;
|
$uselegacyreader = false;
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ if (!empty($instanceid) && !empty($roleid)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get record from sql_internal_reader and merge with records got from legacy log (if needed).
|
// Get record from sql_internal_table_reader and merge with records got from legacy log (if needed).
|
||||||
if (!$onlyuselegacyreader) {
|
if (!$onlyuselegacyreader) {
|
||||||
$sql = "SELECT ra.userid, $usernamefields, u.idnumber, COUNT(l.actioncount) AS count
|
$sql = "SELECT ra.userid, $usernamefields, u.idnumber, COUNT(l.actioncount) AS count
|
||||||
FROM (SELECT DISTINCT userid FROM {role_assignments} WHERE contextid $relatedctxsql AND roleid = :roleid ) ra
|
FROM (SELECT DISTINCT userid FROM {role_assignments} WHERE contextid $relatedctxsql AND roleid = :roleid ) ra
|
||||||
|
|
|
@ -65,7 +65,7 @@ function report_participation_page_type_list($pagetype, $parentcontext, $current
|
||||||
* @return bool returns true if the store is supported by the report, false otherwise.
|
* @return bool returns true if the store is supported by the report, false otherwise.
|
||||||
*/
|
*/
|
||||||
function report_participation_supports_logstore($instance) {
|
function report_participation_supports_logstore($instance) {
|
||||||
if ($instance instanceof \core\log\sql_internal_reader || $instance instanceof \logstore_legacy\log\store) {
|
if ($instance instanceof \core\log\sql_internal_table_reader || $instance instanceof \logstore_legacy\log\store) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -30,7 +30,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||||
* @return string table name
|
* @return string table name
|
||||||
*/
|
*/
|
||||||
function report_participation_get_log_table_name() {
|
function report_participation_get_log_table_name() {
|
||||||
// Get prefered sql_internal_reader reader (if enabled).
|
// Get prefered sql_internal_table_reader reader (if enabled).
|
||||||
$logmanager = get_log_manager();
|
$logmanager = get_log_manager();
|
||||||
$readers = $logmanager->get_readers();
|
$readers = $logmanager->get_readers();
|
||||||
$logtable = '';
|
$logtable = '';
|
||||||
|
@ -43,8 +43,8 @@ function report_participation_get_log_table_name() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If sql_internal_reader is preferred reader.
|
// If sql_internal_table_reader is preferred reader.
|
||||||
if ($reader instanceof \core\log\sql_internal_reader) {
|
if ($reader instanceof \core\log\sql_internal_table_reader) {
|
||||||
$logtable = $reader->get_internal_log_table_name();
|
$logtable = $reader->get_internal_log_table_name();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ function report_stats_page_type_list($pagetype, $parentcontext, $currentcontext)
|
||||||
* @return bool returns true if the store is supported by the report, false otherwise.
|
* @return bool returns true if the store is supported by the report, false otherwise.
|
||||||
*/
|
*/
|
||||||
function report_stats_supports_logstore($instance) {
|
function report_stats_supports_logstore($instance) {
|
||||||
if ($instance instanceof \core\log\sql_internal_reader || $instance instanceof \logstore_legacy\log\store) {
|
if ($instance instanceof \core\log\sql_internal_table_reader || $instance instanceof \logstore_legacy\log\store) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
$version = 2015030500.00; // YYYYMMDD = weekly release date of this DEV branch.
|
$version = 2015030500.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||||
// RR = release increments - 00 in DEV branches.
|
// RR = release increments - 00 in DEV branches.
|
||||||
// .XX = incremental changes.
|
// .XX = incremental changes.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue