Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Contains class content_item_repository, for fetching content_items. 19 * 20 * @package core 21 * @subpackage course 22 * @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace core_course\local\repository; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 use core_course\local\entity\content_item; 30 use core_course\local\entity\lang_string_title; 31 use core_course\local\entity\string_title; 32 33 /** 34 * The class content_item_repository, for reading content_items. 35 * 36 * @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class content_item_readonly_repository implements content_item_readonly_repository_interface { 40 /** 41 * Get the help string for content items representing core modules. 42 * 43 * @param string $modname the module name. 44 * @return string the help string, including help link. 45 */ 46 private function get_core_module_help_string(string $modname): string { 47 global $OUTPUT; 48 49 $help = ''; 50 $sm = get_string_manager(); 51 if ($sm->string_exists('modulename_help', $modname)) { 52 $help = get_string('modulename_help', $modname); 53 if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs. 54 $link = get_string('modulename_link', $modname); 55 $linktext = get_string('morehelp'); 56 $arialabel = get_string('morehelpaboutmodule', '', get_string('modulename', $modname)); 57 $doclink = $OUTPUT->doc_link($link, $linktext, true, ['aria-label' => $arialabel]); 58 $help .= \html_writer::tag('div', $doclink, ['class' => 'helpdoclink']); 59 } 60 } 61 return $help; 62 } 63 64 /** 65 * Create a content_item object based on legacy data returned from the get_shortcuts hook implementations. 66 * 67 * @param \stdClass $item the stdClass of legacy data. 68 * @return content_item a content item object. 69 */ 70 private function content_item_from_legacy_data(\stdClass $item): content_item { 71 global $OUTPUT; 72 73 // Make sure the legacy data results in a content_item with id = 0. 74 // Even with an id, we can't uniquely identify the item, because we can't guarantee what component it came from. 75 // An id of -1, signifies this. 76 $item->id = -1; 77 78 // If the module provides the helplink property, append it to the help text to match the look and feel 79 // of the default course modules. 80 if (isset($item->help) && isset($item->helplink)) { 81 $linktext = get_string('morehelp'); 82 $item->help .= \html_writer::tag('div', 83 $OUTPUT->doc_link($item->helplink, $linktext, true), ['class' => 'helpdoclink']); 84 } 85 86 if (is_string($item->title)) { 87 $item->title = new string_title($item->title); 88 } else if ($item->title instanceof \lang_string) { 89 $item->title = new lang_string_title($item->title->get_identifier(), $item->title->get_component()); 90 } 91 92 // Legacy items had names which are in one of 2 forms: 93 // modname, i.e. 'assign' or 94 // modname:link, i.e. lti:http://etc... 95 // We need to grab the module name out to create the componentname. 96 $modname = (strpos($item->name, ':') !== false) ? explode(':', $item->name)[0] : $item->name; 97 98 return new content_item($item->id, $item->name, $item->title, $item->link, $item->icon, $item->help ?? '', 99 $item->archetype, 'mod_' . $modname); 100 } 101 102 /** 103 * Create a stdClass type object based on a content_item instance. 104 * 105 * @param content_item $contentitem 106 * @return \stdClass the legacy data. 107 */ 108 private function content_item_to_legacy_data(content_item $contentitem): \stdClass { 109 $item = new \stdClass(); 110 $item->id = $contentitem->get_id(); 111 $item->name = $contentitem->get_name(); 112 $item->title = $contentitem->get_title(); 113 $item->link = $contentitem->get_link(); 114 $item->icon = $contentitem->get_icon(); 115 $item->help = $contentitem->get_help(); 116 $item->archetype = $contentitem->get_archetype(); 117 $item->componentname = $contentitem->get_component_name(); 118 return $item; 119 } 120 121 /** 122 * Helper to get the contentitems from all subplugin hooks for a given module plugin. 123 * 124 * @param string $parentpluginname the name of the module plugin to check subplugins for. 125 * @param content_item $modulecontentitem the content item of the module plugin, to pass to the hooks. 126 * @param \stdClass $user the user object to pass to subplugins. 127 * @return array the array of content items. 128 */ 129 private function get_subplugin_course_content_items(string $parentpluginname, content_item $modulecontentitem, 130 \stdClass $user): array { 131 132 $contentitems = []; 133 $pluginmanager = \core_plugin_manager::instance(); 134 foreach ($pluginmanager->get_subplugins_of_plugin($parentpluginname) as $subpluginname => $subplugin) { 135 // Call the hook, but with a copy of the module content item data. 136 $spcontentitems = component_callback($subpluginname, 'get_course_content_items', [$modulecontentitem, $user], null); 137 if (!is_null($spcontentitems)) { 138 foreach ($spcontentitems as $spcontentitem) { 139 $contentitems[] = $spcontentitem; 140 } 141 } 142 } 143 return $contentitems; 144 } 145 146 /** 147 * Get all the content items for a subplugin. 148 * 149 * @param string $parentpluginname 150 * @param content_item $modulecontentitem 151 * @return array 152 */ 153 private function get_subplugin_all_content_items(string $parentpluginname, content_item $modulecontentitem): array { 154 $contentitems = []; 155 $pluginmanager = \core_plugin_manager::instance(); 156 foreach ($pluginmanager->get_subplugins_of_plugin($parentpluginname) as $subpluginname => $subplugin) { 157 // Call the hook, but with a copy of the module content item data. 158 $spcontentitems = component_callback($subpluginname, 'get_all_content_items', [$modulecontentitem], null); 159 if (!is_null($spcontentitems)) { 160 foreach ($spcontentitems as $spcontentitem) { 161 $contentitems[] = $spcontentitem; 162 } 163 } 164 } 165 return $contentitems; 166 } 167 168 /** 169 * Helper to make sure any legacy items have certain properties, which, if missing are inherited from the parent module item. 170 * 171 * @param \stdClass $legacyitem the legacy information, a stdClass coming from get_shortcuts() hook. 172 * @param content_item $modulecontentitem The module's content item information, to inherit if needed. 173 * @return \stdClass the updated legacy item stdClass 174 */ 175 private function legacy_item_inherit_missing(\stdClass $legacyitem, content_item $modulecontentitem): \stdClass { 176 // Fall back to the plugin parent value if the subtype didn't provide anything. 177 $legacyitem->archetype = $legacyitem->archetype ?? $modulecontentitem->get_archetype(); 178 $legacyitem->icon = $legacyitem->icon ?? $modulecontentitem->get_icon(); 179 return $legacyitem; 180 } 181 182 /** 183 * Find all the available content items, not restricted to course or user. 184 * 185 * @return array the array of content items. 186 */ 187 public function find_all(): array { 188 global $OUTPUT, $DB, $CFG; 189 190 // Get all modules so we know which plugins are enabled and able to add content. 191 // Only module plugins may add content items. 192 $modules = $DB->get_records('modules', ['visible' => 1]); 193 $return = []; 194 195 // Now, generate the content_items. 196 foreach ($modules as $modid => $mod) { 197 // Exclude modules if the code doesn't exist. 198 if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) { 199 continue; 200 } 201 // Create the content item for the module itself. 202 // If the module chooses to implement the hook, this may be thrown away. 203 $help = $this->get_core_module_help_string($mod->name); 204 $archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); 205 206 $contentitem = new content_item( 207 $mod->id, 208 $mod->name, 209 new lang_string_title("modulename", $mod->name), 210 new \moodle_url(''), // No course scope, so just an empty link. 211 $OUTPUT->pix_icon('icon', '', $mod->name, ['class' => 'icon']), 212 $help, 213 $archetype, 214 'mod_' . $mod->name 215 ); 216 217 $modcontentitemreference = clone($contentitem); 218 219 if (component_callback_exists('mod_' . $mod->name, 'get_all_content_items')) { 220 // Call the module hooks for this module. 221 $plugincontentitems = component_callback('mod_' . $mod->name, 'get_all_content_items', 222 [$modcontentitemreference], []); 223 if (!empty($plugincontentitems)) { 224 array_push($return, ...$plugincontentitems); 225 } 226 227 // Now, get those for subplugins of the module. 228 $subplugincontentitems = $this->get_subplugin_all_content_items('mod_' . $mod->name, $modcontentitemreference); 229 if (!empty($subplugincontentitems)) { 230 array_push($return, ...$subplugincontentitems); 231 } 232 } else { 233 // Neither callback was found, so just use the default module content item. 234 $return[] = $contentitem; 235 } 236 } 237 return $return; 238 } 239 240 /** 241 * Get the list of potential content items for the given course. 242 * 243 * @param \stdClass $course the course 244 * @param \stdClass $user the user, to pass to plugins implementing callbacks. 245 * @return array the array of content_item objects 246 */ 247 public function find_all_for_course(\stdClass $course, \stdClass $user): array { 248 global $OUTPUT, $DB, $CFG; 249 250 // Get all modules so we know which plugins are enabled and able to add content. 251 // Only module plugins may add content items. 252 $modules = $DB->get_records('modules', ['visible' => 1]); 253 $return = []; 254 255 // A moodle_url is expected and required by modules in their implementation of the hook 'get_shortcuts'. 256 $urlbase = new \moodle_url('/course/mod.php', ['id' => $course->id]); 257 258 // Now, generate the content_items. 259 foreach ($modules as $modid => $mod) { 260 // Exclude modules if the code doesn't exist. 261 if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) { 262 continue; 263 } 264 // Create the content item for the module itself. 265 // If the module chooses to implement the hook, this may be thrown away. 266 $help = $this->get_core_module_help_string($mod->name); 267 $archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); 268 269 $contentitem = new content_item( 270 $mod->id, 271 $mod->name, 272 new lang_string_title("modulename", $mod->name), 273 new \moodle_url($urlbase, ['add' => $mod->name]), 274 $OUTPUT->pix_icon('icon', '', $mod->name, ['class' => 'icon']), 275 $help, 276 $archetype, 277 'mod_' . $mod->name 278 ); 279 280 // Legacy vs new hooks. 281 // If the new hook is found for a module plugin, use that path (calling mod plugins and their subplugins directly) 282 // If not, check the legacy hook. This won't provide us with enough information to identify items uniquely within their 283 // component (lti + lti source being an example), but we can still list these items. 284 $modcontentitemreference = clone($contentitem); 285 286 if (component_callback_exists('mod_' . $mod->name, 'get_course_content_items')) { 287 // Call the module hooks for this module. 288 $plugincontentitems = component_callback('mod_' . $mod->name, 'get_course_content_items', 289 [$modcontentitemreference, $user, $course], []); 290 if (!empty($plugincontentitems)) { 291 array_push($return, ...$plugincontentitems); 292 } 293 294 // Now, get those for subplugins of the module. 295 $subpluginitems = $this->get_subplugin_course_content_items('mod_' . $mod->name, $modcontentitemreference, $user); 296 if (!empty($subpluginitems)) { 297 array_push($return, ...$subpluginitems); 298 } 299 300 } else if (component_callback_exists('mod_' . $mod->name, 'get_shortcuts')) { 301 // TODO: MDL-68011 this block needs to be removed in 4.1. 302 debugging('The callback get_shortcuts has been deprecated. Please use get_course_content_items and 303 get_all_content_items instead. Some features of the activity chooser, such as favourites and recommendations 304 are not supported when providing content items via the deprecated callback.'); 305 306 // If get_shortcuts() callback is defined, the default module action is not added. 307 // It is a responsibility of the callback to add it to the return value unless it is not needed. 308 // The legacy hook, get_shortcuts, expects a stdClass representation of the core module content_item entry. 309 $modcontentitemreference = $this->content_item_to_legacy_data($contentitem); 310 311 $legacyitems = component_callback($mod->name, 'get_shortcuts', [$modcontentitemreference], null); 312 if (!is_null($legacyitems)) { 313 foreach ($legacyitems as $legacyitem) { 314 315 $legacyitem = $this->legacy_item_inherit_missing($legacyitem, $contentitem); 316 317 // All items must have different links, use them as a key in the return array. 318 // If plugin returned the only one item with the same link as default item - keep $modname, 319 // otherwise append the link url to the module name. 320 $legacyitem->name = (count($legacyitems) == 1 && 321 $legacyitem->link->out() === $contentitem->get_link()->out()) ? $mod->name : $mod->name . ':' . 322 $legacyitem->link; 323 324 $plugincontentitem = $this->content_item_from_legacy_data($legacyitem); 325 326 $return[] = $plugincontentitem; 327 } 328 } 329 } else { 330 // Neither callback was found, so just use the default module content item. 331 $return[] = $contentitem; 332 } 333 } 334 335 return $return; 336 } 337 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body