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