From fdaa738bb5118fdbaf37996cf3359567196a5564 Mon Sep 17 00:00:00 2001 From: Laurent David <lmedavid@gmail.com> Date: Fri, 4 Aug 2023 12:31:09 +0200 Subject: [PATCH 1/2] MDL-78907 mod_bigbluebuttonbn: Refactor instance class * Make the constructor private as it should only be instancied through static methods * Do not duplicate the code between get_from_instanceid, get_from_cmid and get_all_instances_in_course --- mod/bigbluebuttonbn/classes/instance.php | 183 ++++++++++++-------- mod/bigbluebuttonbn/tests/instance_test.php | 21 ++- mod/bigbluebuttonbn/upgrade.txt | 3 + 3 files changed, 133 insertions(+), 74 deletions(-) diff --git a/mod/bigbluebuttonbn/classes/instance.php b/mod/bigbluebuttonbn/classes/instance.php index 4e108b8f46d..cbae0f8ab6f 100644 --- a/mod/bigbluebuttonbn/classes/instance.php +++ b/mod/bigbluebuttonbn/classes/instance.php @@ -20,6 +20,7 @@ use cm_info; use context; use context_course; use context_module; +use core\dml\table; use mod_bigbluebuttonbn\local\config; use mod_bigbluebuttonbn\local\helpers\files; use mod_bigbluebuttonbn\local\helpers\roles; @@ -64,14 +65,16 @@ class instance { protected $groupid; /** - * instance constructor. + * Instance constructor. + * + * Never called directly. Use self::get_from_instanceid or self::get_from_cmid. * * @param cm_info $cm * @param stdClass $course * @param stdClass $instancedata * @param int|null $groupid */ - public function __construct(cm_info $cm, stdClass $course, stdClass $instancedata, ?int $groupid = null) { + private function __construct(cm_info $cm, stdClass $course, stdClass $instancedata, ?int $groupid = null) { $this->cm = $cm; $this->course = $course; $this->instancedata = $instancedata; @@ -101,42 +104,7 @@ class instance { * @return null|self */ public static function get_from_instanceid(int $instanceid): ?self { - global $DB; - - $coursetable = new \core\dml\table('course', 'c', 'c'); - $courseselect = $coursetable->get_field_select(); - $coursefrom = $coursetable->get_from_sql(); - - $cmtable = new \core\dml\table('course_modules', 'cm', 'cm'); - $cmfrom = $cmtable->get_from_sql(); - - $bbbtable = new \core\dml\table('bigbluebuttonbn', 'bbb', 'b'); - $bbbselect = $bbbtable->get_field_select(); - $bbbfrom = $bbbtable->get_from_sql(); - - $sql = <<<EOF - SELECT {$courseselect}, {$bbbselect} - FROM {$cmfrom} -INNER JOIN {$coursefrom} ON c.id = cm.course -INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname -INNER JOIN {$bbbfrom} ON cm.instance = bbb.id - WHERE bbb.id = :instanceid -EOF; - - $result = $DB->get_record_sql($sql, [ - 'modname' => 'bigbluebuttonbn', - 'instanceid' => $instanceid, - ]); - - if (empty($result)) { - return null; - } - - $course = $coursetable->extract_from_result($result); - $instancedata = $bbbtable->extract_from_result($result); - $cm = get_fast_modinfo($course)->instances['bigbluebuttonbn'][$instancedata->id]; - - return new self($cm, $course, $instancedata); + return self::get_instance_info_retriever($instanceid, self::IDTYPE_INSTANCEID); } /** @@ -146,32 +114,72 @@ EOF; * @return null|self */ public static function get_from_cmid(int $cmid): ?self { + return self::get_instance_info_retriever($cmid, self::IDTYPE_CMID); + } + + /** + * Get the instance information from a cmid. + */ + const IDTYPE_CMID = 0; + /** + * Get the instance information from an id. + */ + const IDTYPE_INSTANCEID = 1; + + /** + * Helper to get the instance information from an id. + * + * Used by self::get_from_id and self::get_cmid. + * + * @param string $id The id to look for. + * @param int $idtype self::IDTYPE_CMID or self::IDTYPE_INSTANCEID + * @return null|self + * @throws \moodle_exception + */ + private static function get_instance_info_retriever(string $id, int $idtype = self::IDTYPE_INSTANCEID): ?self { global $DB; - $coursetable = new \core\dml\table('course', 'c', 'c'); - $courseselect = $coursetable->get_field_select(); - $coursefrom = $coursetable->get_from_sql(); + if (!in_array($idtype, [self::IDTYPE_CMID, self::IDTYPE_INSTANCEID])) { + throw new \moodle_exception('Invalid idtype'); + } - $cmtable = new \core\dml\table('course_modules', 'cm', 'cm'); - $cmfrom = $cmtable->get_from_sql(); + [ + 'coursetable' => $coursetable, + 'courseselect' => $courseselect, + 'coursefrom' => $coursefrom, + 'cmfrom' => $cmfrom, + 'cmselect' => $cmselect, + 'bbbtable' => $bbbtable, + 'bbbselect' => $bbbselect, + 'bbbfrom' => $bbbfrom, + ] = self::get_tables_info(); - $bbbtable = new \core\dml\table('bigbluebuttonbn', 'bbb', 'b'); - $bbbselect = $bbbtable->get_field_select(); - $bbbfrom = $bbbtable->get_from_sql(); - - $sql = <<<EOF - SELECT {$courseselect}, {$bbbselect} - FROM {$cmfrom} -INNER JOIN {$coursefrom} ON c.id = cm.course -INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname -INNER JOIN {$bbbfrom} ON cm.instance = bbb.id - WHERE cm.id = :cmid -EOF; - - $result = $DB->get_record_sql($sql, [ + $select = implode(', ', [$courseselect, $bbbselect, $cmselect]); + $params = [ 'modname' => 'bigbluebuttonbn', - 'cmid' => $cmid, - ]); + 'bbbid' => $id, + ]; + $where = 'bbb.id = :bbbid'; + $from = <<<EOF + {$bbbfrom} + INNER JOIN {$cmfrom} ON cm.instance = bbb.id + INNER JOIN {$coursefrom} ON c.id = cm.course + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname +EOF; + if ($idtype == self::IDTYPE_CMID) { + $params['cmid'] = $id; + $where = 'cm.id = :cmid'; + $from = <<<EOF + {$cmfrom} + INNER JOIN {$coursefrom} ON c.id = cm.course + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname + INNER JOIN {$bbbfrom} ON cm.instance = bbb.id +EOF; + } + + $sql = "SELECT {$select} FROM {$from} WHERE {$where}"; + + $result = $DB->get_record_sql($sql, $params); if (empty($result)) { return null; @@ -179,8 +187,13 @@ EOF; $course = $coursetable->extract_from_result($result); $instancedata = $bbbtable->extract_from_result($result); - $cm = get_fast_modinfo($course)->get_cm($cmid); - + if ($idtype == self::IDTYPE_INSTANCEID) { + $cmid = $result->cmid; + } else { + $cmid = $id; + } + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($cmid); return new self($cm, $course, $instancedata); } @@ -233,20 +246,19 @@ EOF; */ public static function get_all_instances_in_course(int $courseid): array { global $DB; + [ + 'coursetable' => $coursetable, + 'courseselect' => $courseselect, + 'coursefrom' => $coursefrom, + 'cmfrom' => $cmfrom, + 'bbbtable' => $bbbtable, + 'bbbselect' => $bbbselect, + 'bbbfrom' => $bbbfrom + ] = self::get_tables_info(); - $coursetable = new \core\dml\table('course', 'c', 'c'); - $courseselect = $coursetable->get_field_select(); - $coursefrom = $coursetable->get_from_sql(); - - $cmtable = new \core\dml\table('course_modules', 'cm', 'cm'); - $cmfrom = $cmtable->get_from_sql(); - - $bbbtable = new \core\dml\table('bigbluebuttonbn', 'bbb', 'b'); - $bbbselect = $bbbtable->get_field_select(); - $bbbfrom = $bbbtable->get_from_sql(); - + $selects = implode(', ', [$courseselect, $bbbselect]); $sql = <<<EOF - SELECT cm.id as cmid, {$courseselect}, {$bbbselect} + SELECT cm.id as cmid, {$selects} FROM {$cmfrom} INNER JOIN {$coursefrom} ON c.id = cm.course INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname @@ -269,7 +281,32 @@ EOF; return $instances; } + // Add an helper method that can be used in both self::get_all_instances_in_course and self::get_all_instances_in_course. + // This method will be used to get the additional tables returned from the subplugin. + /** + * Get the additional tables returned from the subplugin. + * + * @return array + */ + private static function get_tables_info(): array { + $coursetable = new table('course', 'c', 'c'); + $courseselect = $coursetable->get_field_select(); + $coursefrom = $coursetable->get_from_sql(); + $cmtable = new table('course_modules', 'cm', 'cm'); + $cmselect = $cmtable->get_field_select(); + $cmfrom = $cmtable->get_from_sql(); + + $bbbtable = new table('bigbluebuttonbn', 'bbb', 'b'); + $bbbselect = $bbbtable->get_field_select(); + $bbbfrom = $bbbtable->get_from_sql(); + + return compact( + 'coursetable', 'courseselect', 'coursefrom', + 'cmtable', 'cmselect', 'cmfrom', + 'bbbtable', 'bbbselect', 'bbbfrom' + ); + } /** * Set the current group id of the activity. * diff --git a/mod/bigbluebuttonbn/tests/instance_test.php b/mod/bigbluebuttonbn/tests/instance_test.php index 8fe81cb2277..1d9338276c1 100644 --- a/mod/bigbluebuttonbn/tests/instance_test.php +++ b/mod/bigbluebuttonbn/tests/instance_test.php @@ -588,6 +588,26 @@ class instance_test extends advanced_testcase { $this->assertFalse($instance->is_guest_allowed()); } + /** + * Test private method get_instance_info_retriever + * + * @covers ::get_instance_info_retriever + */ + public function test_get_instance_info_retriever() { + $this->resetAfterTest(); + [ + 'record' => $record, + 'cm' => $cm, + ] = $this->get_test_instance(); + $instance = instance::get_from_instanceid($record->id); + $instancereflection = new \ReflectionClass($instance); + $getinstanceinforetriever = $instancereflection->getMethod('get_instance_info_retriever'); + $getinstanceinforetriever->setAccessible(true); + $this->assertInstanceOf('\mod_bigbluebuttonbn\instance', + $getinstanceinforetriever->invoke($instance, $record->id, instance::IDTYPE_INSTANCEID)); + $this->assertEquals($cm->id, $instance->get_cm_id()); + } + /** * Test guest access password * @@ -601,5 +621,4 @@ class instance_test extends advanced_testcase { $instance = instance::get_from_instanceid($record->id); $this->assertNotEmpty($instance->get_guest_access_password()); } - } diff --git a/mod/bigbluebuttonbn/upgrade.txt b/mod/bigbluebuttonbn/upgrade.txt index 5871a50f02f..4a8da0ee7e7 100644 --- a/mod/bigbluebuttonbn/upgrade.txt +++ b/mod/bigbluebuttonbn/upgrade.txt @@ -1,4 +1,7 @@ This files describes API changes in the bigbluebuttonbn code. +=== 4.3 === +* Make instance class constructor private and use the factory method to create instances. + === 4.2 === * Simplify bigbluebutton_proxy get_join_url so to pass only parameters that cannot be deduced from the instance. * Add a new parameter for mod_bigbluebuttonbn\recording::sync_pending_recordings_from_server so sync only 'dismissed' recordings From 23caba90bdc7e801b9277d8aaf850ff3304c4a90 Mon Sep 17 00:00:00 2001 From: Laurent David <lmedavid@gmail.com> Date: Sat, 5 Aug 2023 11:02:09 +0200 Subject: [PATCH 2/2] MDL-78907 mod_bigbluebuttonbn: Get cm when necessary * The constructor of mod_bigbluebuttonbn\instance was provided with a cm_info. This forced us to retrieve it whenever we created an instance. The issue is that sometimes we need all the "other" data but not necessarily the cm_info. So we now lazy load it. --- mod/bigbluebuttonbn/classes/instance.php | 35 ++++++++++++++---------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/mod/bigbluebuttonbn/classes/instance.php b/mod/bigbluebuttonbn/classes/instance.php index cbae0f8ab6f..bb348d53be8 100644 --- a/mod/bigbluebuttonbn/classes/instance.php +++ b/mod/bigbluebuttonbn/classes/instance.php @@ -64,18 +64,22 @@ class instance { /** @var int The current groupid if set */ protected $groupid; + /** @var int The course module id. */ + protected $cmid; + /** * Instance constructor. * * Never called directly. Use self::get_from_instanceid or self::get_from_cmid. * - * @param cm_info $cm + * @param int $cmid * @param stdClass $course * @param stdClass $instancedata * @param int|null $groupid */ - private function __construct(cm_info $cm, stdClass $course, stdClass $instancedata, ?int $groupid = null) { - $this->cm = $cm; + private function __construct(int $cmid, stdClass $course, stdClass $instancedata, ?int $groupid = null) { + $this->cmid = $cmid; + $this->cm = null; // This is not retrieved later, whenever we call ::get_cm() it will be retrieved. $this->course = $course; $this->instancedata = $instancedata; $this->groupid = $groupid; @@ -90,7 +94,7 @@ class instance { */ public static function get_group_instance_from_instance(self $originalinstance, int $groupid): ?self { return new self( - $originalinstance->get_cm(), + $originalinstance->get_cm_id(), $originalinstance->get_course(), $originalinstance->get_instance_data(), $groupid @@ -192,9 +196,7 @@ EOF; } else { $cmid = $id; } - $modinfo = get_fast_modinfo($course); - $cm = $modinfo->get_cm($cmid); - return new self($cm, $course, $instancedata); + return new self($cmid, $course, $instancedata); } /** @@ -275,8 +277,7 @@ EOF; foreach ($results as $result) { $course = $coursetable->extract_from_result($result); $instancedata = $bbbtable->extract_from_result($result); - $cm = get_fast_modinfo($course)->get_cm($result->cmid); - $instances[$cm->id] = new self($cm, $course, $instancedata); + $instances[$result->cmid] = new self($result->cmid, $course, $instancedata); } return $instances; @@ -378,6 +379,12 @@ EOF; * @return cm_info */ public function get_cm(): cm_info { + if ($this->cm === null) { + // We do a sort of late binding here as if we call get_cm on a disabled module or in a call stack where + // get_cm was already called, we will get an exception or infinite loop. + $modinfo = get_fast_modinfo($this->course); + $this->cm = $modinfo->get_cm($this->cmid); + } return $this->cm; } @@ -387,7 +394,7 @@ EOF; * @return int */ public function get_cm_id(): int { - return $this->get_cm()->id; + return $this->cmid; } /** @@ -1058,7 +1065,7 @@ EOF; */ public function get_view_url(): moodle_url { return new moodle_url('/mod/bigbluebuttonbn/view.php', [ - 'id' => $this->cm->id, + 'id' => $this->get_cm()->id, ]); } @@ -1070,8 +1077,8 @@ EOF; public function get_logout_url(): moodle_url { return new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [ 'action' => 'logout', - 'id' => $this->cm->id, - 'courseid' => $this->cm->course // Used to find the course if ever the activity is deleted + 'id' => $this->get_cm()->id, + 'courseid' => $this->get_cm()->course // Used to find the course if ever the activity is deleted // while the meeting is running. ]); } @@ -1108,7 +1115,7 @@ EOF; public function get_join_url(): moodle_url { return new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [ 'action' => 'join', - 'id' => $this->cm->id, + 'id' => $this->get_cm()->id, 'bn' => $this->instancedata->id, ]); }