/** * Parse all callbacks and builds the tree. * * @param integer $user ID of the user for which the profile is displayed. * @param bool $iscurrentuser true if the profile being viewed is of current user, else false. * @param \stdClass $course Course object * * @return tree Fully build tree to be rendered on my profile page. */ public static function build_tree($user, $iscurrentuser, $course = null) { global $CFG; $tree = new tree(); //print_r($user); // Add core nodes. require_once $CFG->libdir . "/myprofilelib.php"; core_myprofile_navigation($tree, $user, $iscurrentuser, $course); // Core components. $components = \core_component::get_core_subsystems(); foreach ($components as $component => $directory) { if (empty($directory)) { continue; } $file = $directory . "/lib.php"; if (is_readable($file)) { require_once $file; $function = "core_" . $component . "_myprofile_navigation"; //print_r($function); if (function_exists($function)) { //echo "Current function: ".$function."<br>"; $function($tree, $user, $iscurrentuser, $course); } } } // Plugins. $pluginswithfunction = get_plugins_with_function('myprofile_navigation', 'lib.php'); foreach ($pluginswithfunction as $plugins) { foreach ($plugins as $function) { $function($tree, $user, $iscurrentuser, $course); } } $tree->sort_categories(); return $tree; }
/** * This function loads all of the front page settings into the settings navigation. * This function is called when the user is on the front page, or $COURSE==$SITE * @param bool $forceopen (optional) * @return navigation_node */ protected function load_front_page_settings($forceopen = false) { global $SITE, $CFG; $course = clone $SITE; $coursecontext = context_course::instance($course->id); // Course context $frontpage = $this->add(get_string('frontpagesettings'), null, self::TYPE_SETTING, null, 'frontpage'); if ($forceopen) { $frontpage->force_open(); } $frontpage->id = 'frontpagesettings'; if ($this->page->user_allowed_editing()) { // Add the turn on/off settings $url = new moodle_url('/course/view.php', array('id' => $course->id, 'sesskey' => sesskey())); if ($this->page->user_is_editing()) { $url->param('edit', 'off'); $editstring = get_string('turneditingoff'); } else { $url->param('edit', 'on'); $editstring = get_string('turneditingon'); } $frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', '')); } if (has_capability('moodle/course:update', $coursecontext)) { // Add the course settings link $url = new moodle_url('/admin/settings.php', array('section' => 'frontpagesettings')); $frontpage->add(get_string('editsettings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', '')); } // add enrol nodes enrol_add_course_navigation($frontpage, $course); // Manage filters if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext)) > 0) { $url = new moodle_url('/filter/manage.php', array('contextid' => $coursecontext->id)); $frontpage->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', '')); } // View course reports. if (has_capability('moodle/site:viewreports', $coursecontext)) { // Basic capability for listing of reports. $frontpagenav = $frontpage->add(get_string('reports'), null, self::TYPE_CONTAINER, null, 'frontpagereports', new pix_icon('i/stats', '')); $coursereports = core_component::get_plugin_list('coursereport'); foreach ($coursereports as $report => $dir) { $libfile = $CFG->dirroot . '/course/report/' . $report . '/lib.php'; if (file_exists($libfile)) { require_once $libfile; $reportfunction = $report . '_report_extend_navigation'; if (function_exists($report . '_report_extend_navigation')) { $reportfunction($frontpagenav, $course, $coursecontext); } } } $reports = get_plugin_list_with_function('report', 'extend_navigation_course', 'lib.php'); foreach ($reports as $reportfunction) { $reportfunction($frontpagenav, $course, $coursecontext); } } // Backup this course if (has_capability('moodle/backup:backupcourse', $coursecontext)) { $url = new moodle_url('/backup/backup.php', array('id' => $course->id)); $frontpage->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', '')); } // Restore to this course if (has_capability('moodle/restore:restorecourse', $coursecontext)) { $url = new moodle_url('/backup/restorefile.php', array('contextid' => $coursecontext->id)); $frontpage->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', '')); } // Questions require_once $CFG->libdir . '/questionlib.php'; question_extend_settings_navigation($frontpage, $coursecontext)->trim_if_empty(); // Manage files if ($course->legacyfiles == 2 and has_capability('moodle/course:managefiles', $this->context)) { //hiden in new installs $url = new moodle_url('/files/index.php', array('contextid' => $coursecontext->id)); $frontpage->add(get_string('sitelegacyfiles'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/folder', '')); } // Let plugins hook into frontpage navigation. $pluginsfunction = get_plugins_with_function('extend_navigation_frontpage', 'lib.php'); foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { $pluginfunction($frontpage, $course, $coursecontext); } } return $frontpage; }
/** * Get a list of all the plugins of a given type that define a certain API function * in a certain file. The plugin component names and function names are returned. * * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'. * @param string $function the part of the name of the function after the * frankenstyle prefix. e.g 'hook' if you are looking for functions with * names like report_courselist_hook. * @param string $file the name of file within the plugin that defines the * function. Defaults to lib.php. * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum') * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook'). */ function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') { global $CFG; // We don't include here as all plugin types files would be included. $plugins = get_plugins_with_function($function, $file, false); if (empty($plugins[$plugintype])) { return array(); } $allplugins = core_component::get_plugin_list($plugintype); // Reformat the array and include the files. $pluginfunctions = array(); foreach ($plugins[$plugintype] as $pluginname => $functionname) { // Check that it has not been removed and the file is still available. if (!empty($allplugins[$pluginname])) { $filepath = $allplugins[$pluginname] . DIRECTORY_SEPARATOR . $file; if (file_exists($filepath)) { include_once $filepath; $pluginfunctions[$plugintype . '_' . $pluginname] = $functionname; } } } return $pluginfunctions; }
/** * Allow plugins to provide some content to be rendered in the navbar. * The plugin must define a PLUGIN_render_navbar_output function that returns * the HTML they wish to add to the navbar. * * @return string HTML for the navbar */ public function navbar_plugin_output() { $output = ''; if ($pluginsfunction = get_plugins_with_function('render_navbar_output')) { foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { $output .= $pluginfunction($this); } } } return $output; }
/** * Delete a block, and associated data. * * @param object $instance a row from the block_instances table * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility. * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient. */ function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) { global $DB; // Allow plugins to use this block before we completely delete it. if ($pluginsfunction = get_plugins_with_function('pre_block_delete')) { foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { $pluginfunction($instance); } } } if ($block = block_instance($instance->blockname, $instance)) { $block->instance_delete(); } context_helper::delete_instance(CONTEXT_BLOCK, $instance->id); if (!$skipblockstables) { $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id)); $DB->delete_records('block_instances', array('id' => $instance->id)); $DB->delete_records_list('user_preferences', 'name', array('block' . $instance->id . 'hidden', 'docked_block_instance_' . $instance->id)); } }
/** * This function will handle the whole deletion process of a module. This includes calling * the modules delete_instance function, deleting files, events, grades, conditional data, * the data in the course_module and course_sections table and adding a module deletion * event to the DB. * * @param int $cmid the course module id * @since Moodle 2.5 */ function course_delete_module($cmid) { global $CFG, $DB; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->libdir . '/questionlib.php'; require_once $CFG->dirroot . '/blog/lib.php'; require_once $CFG->dirroot . '/calendar/lib.php'; // Get the course module. if (!($cm = $DB->get_record('course_modules', array('id' => $cmid)))) { return true; } // Get the module context. $modcontext = context_module::instance($cm->id); // Get the course module name. $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST); // Get the file location of the delete_instance function for this module. $modlib = "{$CFG->dirroot}/mod/{$modulename}/lib.php"; // Include the file required to call the delete_instance function for this module. if (file_exists($modlib)) { require_once $modlib; } else { throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null, "Cannot delete this module as the file mod/{$modulename}/lib.php is missing."); } $deleteinstancefunction = $modulename . '_delete_instance'; // Ensure the delete_instance function exists for this module. if (!function_exists($deleteinstancefunction)) { throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null, "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/{$modulename}/lib.php."); } // Allow plugins to use this course module before we completely delete it. if ($pluginsfunction = get_plugins_with_function('pre_course_module_delete')) { foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { $pluginfunction($cm); } } } // Delete activity context questions and question categories. question_delete_activity($cm); // Call the delete_instance function, if it returns false throw an exception. if (!$deleteinstancefunction($cm->instance)) { throw new moodle_exception('cannotdeletemoduleinstance', '', '', null, "Cannot delete the module {$modulename} (instance)."); } // Remove all module files in case modules forget to do that. $fs = get_file_storage(); $fs->delete_area_files($modcontext->id); // Delete events from calendar. if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) { foreach ($events as $event) { $calendarevent = calendar_event::load($event->id); $calendarevent->delete(); } } // Delete grade items, outcome items and grades attached to modules. if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename, 'iteminstance' => $cm->instance, 'courseid' => $cm->course))) { foreach ($grade_items as $grade_item) { $grade_item->delete('moddelete'); } } // Delete completion and availability data; it is better to do this even if the // features are not turned on, in case they were turned on previously (these will be // very quick on an empty table). $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id)); $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id, 'course' => $cm->course, 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY)); // Delete all tag instances associated with the instance of this module. core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id); core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id); // Notify the competency subsystem. \core_competency\api::hook_course_module_deleted($cm); // Delete the context. context_helper::delete_instance(CONTEXT_MODULE, $cm->id); // Delete the module from the course_modules table. $DB->delete_records('course_modules', array('id' => $cm->id)); // Delete module from that section. if (!delete_mod_from_section($cm->id, $cm->section)) { throw new moodle_exception('cannotdeletemodulefromsection', '', '', null, "Cannot delete the module {$modulename} (instance) from section."); } // Trigger event for course module delete action. $event = \core\event\course_module_deleted::create(array('courseid' => $cm->course, 'context' => $modcontext, 'objectid' => $cm->id, 'other' => array('modulename' => $modulename, 'instanceid' => $cm->instance))); $event->add_record_snapshot('course_modules', $cm); $event->trigger(); rebuild_course_cache($cm->course, true); }
public function test_async_section_deletion_hook_not_implemented() { // If no plugins advocate async removal, then normal synchronous removal will take place. // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns // 'true' from the 'course_module_adhoc_deletion_recommended' hook. // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it. global $DB, $USER; $this->resetAfterTest(true); $this->setAdminUser(); set_config('coursebinenable', false, 'tool_recyclebin'); // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test. // If at least one plugin still returns true, then skip this test. if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) { foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { if ($pluginfunction()) { $this->markTestSkipped(); } } } } // Create course, module and context. $generator = $this->getDataGenerator(); $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]); $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); // Delete empty section. No difference from normal, synchronous behaviour. $this->assertTrue(course_delete_section($course, 4, false, true)); $this->assertEquals(3, course_get_format($course)->get_course()->numsections); // Delete section in the middle (2). $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison. $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change. $sink = $this->redirectEvents(); // To capture the event. $this->assertTrue(course_delete_section($course, 2, true, true)); // Now, confirm that: // a) The section's modules have deleted and; // b) the section has been deleted and; // c) course_section_deleted event has been fired and; // d) course_module_deleted events have both been fired. // Confirm modules have been deleted. list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid]); $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids); $this->assertEmpty($cmcount); // Confirm the section has been deleted. $this->assertEquals(2, course_get_format($course)->get_course()->numsections); // Confirm the course_section_deleted event has been generated. $events = $sink->get_events(); $event = array_pop($events); $sink->close(); $this->assertInstanceOf('\\core\\event\\course_section_deleted', $event); $this->assertEquals($section->id, $event->objectid); $this->assertEquals($USER->id, $event->userid); $this->assertEquals('course_sections', $event->objecttable); $this->assertEquals(null, $event->get_url()); $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id)); // Confirm that the course_module_deleted events have both been generated. $count = 0; while (!empty($events)) { $event = array_pop($events); if ($event instanceof \core\event\course_module_deleted && in_array($event->objectid, [$assign0->cmid, $assign1->cmid])) { $count++; } } $this->assertEquals(2, $count); }
/** * Recursively delete category including all subcategories and courses * * Function {@link coursecat::can_delete_full()} MUST be called prior * to calling this function because there is no capability check * inside this function * * @param boolean $showfeedback display some notices * @return array return deleted courses * @throws moodle_exception */ public function delete_full($showfeedback = true) { global $CFG, $DB; require_once($CFG->libdir.'/gradelib.php'); require_once($CFG->libdir.'/questionlib.php'); require_once($CFG->dirroot.'/cohort/lib.php'); // Make sure we won't timeout when deleting a lot of courses. $settimeout = core_php_time_limit::raise(); // Allow plugins to use this category before we completely delete it. if ($pluginsfunction = get_plugins_with_function('pre_course_category_delete')) { $category = $this->get_db_record(); foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { $pluginfunction($category); } } } $deletedcourses = array(); // Get children. Note, we don't want to use cache here because it would be rebuilt too often. $children = $DB->get_records('course_categories', array('parent' => $this->id), 'sortorder ASC'); foreach ($children as $record) { $coursecat = new coursecat($record); $deletedcourses += $coursecat->delete_full($showfeedback); } if ($courses = $DB->get_records('course', array('category' => $this->id), 'sortorder ASC')) { foreach ($courses as $course) { if (!delete_course($course, false)) { throw new moodle_exception('cannotdeletecategorycourse', '', '', $course->shortname); } $deletedcourses[] = $course; } } // Move or delete cohorts in this context. cohort_delete_category($this); // Now delete anything that may depend on course category context. grade_course_category_delete($this->id, 0, $showfeedback); if (!question_delete_course_category($this, 0, $showfeedback)) { throw new moodle_exception('cannotdeletecategoryquestions', '', '', $this->get_formatted_name()); } // Finally delete the category and it's context. $DB->delete_records('course_categories', array('id' => $this->id)); $coursecatcontext = context_coursecat::instance($this->id); $coursecatcontext->delete(); cache_helper::purge_by_event('changesincoursecat'); // Trigger a course category deleted event. /* @var \core\event\course_category_deleted $event */ $event = \core\event\course_category_deleted::create(array( 'objectid' => $this->id, 'context' => $coursecatcontext, 'other' => array('name' => $this->name) )); $event->set_coursecat($this); $event->trigger(); // If we deleted $CFG->defaultrequestcategory, make it point somewhere else. if ($this->id == $CFG->defaultrequestcategory) { set_config('defaultrequestcategory', $DB->get_field('course_categories', 'MIN(id)', array('parent' => 0))); } return $deletedcourses; }
/** * Plugins can extend the coursemodule settings form. */ protected function plugin_extend_coursemodule_standard_elements() { $callbacks = get_plugins_with_function('coursemodule_standard_elements', 'lib.php'); foreach ($callbacks as $type => $plugins) { foreach ($plugins as $plugin => $pluginfunction) { // We have exposed all the important properties with public getters - and the callback can manipulate the mform // directly. $pluginfunction($this, $this->_form); } } }
/** * Hook for plugins to take action when a module is created or updated. * * @param stdClass $moduleinfo the module info * @param stdClass $course the course of the module * * @return stdClass moduleinfo updated by plugins. */ function plugin_extend_coursemodule_edit_post_actions($moduleinfo, $course) { $callbacks = get_plugins_with_function('coursemodule_edit_post_actions', 'lib.php'); foreach ($callbacks as $type => $plugins) { foreach ($plugins as $plugin => $pluginfunction) { $moduleinfo = $pluginfunction($moduleinfo, $course); } } return $moduleinfo; }
/** * Returns if scale used anywhere - activities, grade items, outcomes, etc. * * @return bool */ public function is_used() { global $DB; global $CFG; // count grade items excluding the $params = array($this->id); $sql = "SELECT COUNT(id) FROM {grade_items} WHERE scaleid = ? AND outcomeid IS NULL"; if ($DB->count_records_sql($sql, $params)) { return true; } // count outcomes $sql = "SELECT COUNT(id) FROM {grade_outcomes} WHERE scaleid = ?"; if ($DB->count_records_sql($sql, $params)) { return true; } // Ask the competency subsystem. if (\core_competency\api::is_scale_used_anywhere($this->id)) { return true; } // Ask all plugins if the scale is used anywhere. $pluginsfunction = get_plugins_with_function('scale_used_anywhere'); foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { if ($pluginfunction($this->id)) { return true; } } } return false; }
/** * This method will delete a course section and may delete all modules inside it. * * No permissions are checked here, use {@link course_can_delete_section()} to * check if section can actually be deleted. * * @param int|stdClass $course * @param int|stdClass|section_info $section * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it. * @param bool $async whether or not to try to delete the section using an adhoc task. Async also depends on a plugin hook. * @return bool whether section was deleted */ function course_delete_section($course, $section, $forcedeleteifnotempty = true, $async = false) { global $DB; // Prepare variables. $courseid = is_object($course) ? $course->id : (int) $course; $sectionnum = is_object($section) ? $section->section : (int) $section; $section = $DB->get_record('course_sections', array('course' => $courseid, 'section' => $sectionnum)); if (!$section) { // No section exists, can't proceed. return false; } // Check the 'course_module_background_deletion_recommended' hook first. // Only use asynchronous deletion if at least one plugin returns true and if async deletion has been requested. // Both are checked because plugins should not be allowed to dictate the deletion behaviour, only support/decline it. // It's up to plugins to handle things like whether or not they are enabled. if ($async && ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended'))) { foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { if ($pluginfunction()) { return course_delete_section_async($section, $forcedeleteifnotempty); } } } } $format = course_get_format($course); $sectionname = $format->get_section_name($section); // Delete section. $result = $format->delete_section($section, $forcedeleteifnotempty); // Trigger an event for course section deletion. if ($result) { $context = context_course::instance($courseid); $event = \core\event\course_section_deleted::create(array('objectid' => $section->id, 'courseid' => $courseid, 'context' => $context, 'other' => array('sectionnum' => $section->section, 'sectionname' => $sectionname))); $event->add_record_snapshot('course_sections', $section); $event->trigger(); } return $result; }