From e87214bda75fd92072eabd0de2d06ac23844c039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Fri, 4 Oct 2013 22:40:44 +0200 Subject: [PATCH] MDL-42078 multiple uninstall improvements and cleanup Includes: * update checker refactored to \core\update\ namespace * plugininfo classes refactored to \core\plugininfo\ namespace * plugin_manager renamed to core_plugin_manager * redirect back to original page after plugin uninstall * fixed assign subplugin uninstall * move assign subplugins under the assignment in admin tree * fixed plugininfo for all question related plugin types * auth uninstall support * added missing block dependencies * added theme uninstall * subplugin types are following the plugin on plugin overview page * several performance improvements in plugin manager * new warnigns when plugininfo are outdated or missing * multiple fixes and other improvements --- admin/auth.php | 5 +- admin/blocks.php | 23 +- admin/cli/install.php | 3 +- admin/cli/install_database.php | 3 +- admin/cli/upgrade.php | 3 +- admin/courseformats.php | 7 +- admin/editors.php | 3 +- admin/enrol.php | 7 +- admin/filters.php | 9 +- admin/index.php | 32 +- admin/localplugins.php | 3 +- admin/message.php | 5 +- admin/modules.php | 21 +- admin/plagiarism.php | 3 +- admin/plugins.php | 28 +- admin/portfolio.php | 5 +- admin/qbehaviours.php | 78 +- admin/qtypes.php | 79 +- admin/renderer.php | 93 +- admin/reports.php | 5 +- admin/repository.php | 9 +- admin/repositoryinstance.php | 7 +- admin/settings/plugins.php | 150 +- admin/tool/behat/cli/util.php | 1 - admin/tool/installaddon/classes/installer.php | 3 +- admin/tool/phpunit/cli/util.php | 1 - admin/tools.php | 4 +- auth/cas/version.php | 2 + blocks/feedback/version.php | 2 + blocks/glossary_random/version.php | 2 + blocks/quiz_results/version.php | 2 + blocks/search_forums/version.php | 2 + filter/manage.php | 1 - lang/en/question.php | 8 - lib/adminlib.php | 107 +- lib/classes/component.php | 136 +- lib/classes/plugin_manager.php | 1262 +++++ lib/classes/plugininfo/auth.php | 133 + lib/classes/plugininfo/base.php | 593 +++ lib/classes/plugininfo/block.php | 169 + lib/classes/plugininfo/cachelock.php | 36 + lib/classes/plugininfo/cachestore.php | 36 + lib/classes/plugininfo/calendartype.php | 36 + lib/classes/plugininfo/coursereport.php | 36 + lib/classes/plugininfo/editor.php | 100 + lib/classes/plugininfo/enrol.php | 161 + lib/classes/plugininfo/filter.php | 115 + lib/classes/plugininfo/format.php | 173 + lib/classes/plugininfo/general.php | 36 + lib/classes/plugininfo/gradeexport.php | 36 + lib/classes/plugininfo/gradeimport.php | 36 + lib/classes/plugininfo/gradereport.php | 36 + lib/classes/plugininfo/gradingform.php | 36 + lib/classes/plugininfo/local.php | 70 + lib/classes/plugininfo/message.php | 104 + lib/classes/plugininfo/mnetservice.php | 42 + lib/classes/plugininfo/mod.php | 200 + lib/classes/plugininfo/plagiarism.php | 67 + lib/classes/plugininfo/portfolio.php | 57 + lib/classes/plugininfo/profilefield.php | 47 + lib/classes/plugininfo/qbehaviour.php | 108 + lib/classes/plugininfo/qformat.php | 35 + lib/classes/plugininfo/qtype.php | 126 + lib/classes/plugininfo/report.php | 46 + lib/classes/plugininfo/repository.php | 70 + lib/classes/plugininfo/theme.php | 80 + lib/classes/plugininfo/tool.php | 70 + lib/classes/plugininfo/webservice.php | 84 + lib/classes/update/checker.php | 809 ++++ lib/classes/update/checker_exception.php | 40 + lib/classes/update/deployer.php | 586 +++ lib/classes/update/info.php | 65 + lib/cronlib.php | 3 +- lib/db/caches.php | 2 +- lib/editor/atto/classes/plugininfo/atto.php | 35 + lib/editor/tinymce/adminlib.php | 82 +- .../tinymce/classes/plugininfo/tinymce.php | 98 + lib/editor/tinymce/settings.php | 4 +- lib/editor/tinymce/subplugins.php | 3 +- lib/moodlelib.php | 4 +- lib/phpunit/classes/util.php | 12 +- lib/pluginlib.php | 4233 +---------------- lib/tests/component_test.php | 36 + lib/tests/plugin_manager_test.php | 72 +- ...ecker_test.php => update_checker_test.php} | 9 +- ...oyer_test.php => update_deployer_test.php} | 17 +- lib/upgrade.txt | 6 +- message/lib.php | 6 +- message/renderer.php | 4 +- mod/assign/adminlib.php | 169 +- .../classes/plugininfo/assignfeedback.php | 128 + .../classes/plugininfo/assignsubmission.php | 128 + mod/assign/lang/en/assign.php | 3 - mod/assign/settings.php | 33 +- .../classes/plugininfo/assignment.php | 35 + mod/book/classes/plugininfo/booktool.php | 35 + mod/data/classes/plugininfo/datafield.php | 36 + mod/data/classes/plugininfo/datapreset.php | 35 + .../plugininfo/quiz.php} | 14 +- mod/quiz/classes/plugininfo/quizaccess.php | 35 + mod/scorm/classes/plugininfo/scormreport.php | 35 + .../classes/plugininfo/workshopallocation.php | 38 + .../classes/plugininfo/workshopeval.php | 38 + .../classes/plugininfo/workshopform.php | 38 + user/portfolio.php | 5 +- 105 files changed, 7033 insertions(+), 4986 deletions(-) create mode 100644 lib/classes/plugin_manager.php create mode 100644 lib/classes/plugininfo/auth.php create mode 100644 lib/classes/plugininfo/base.php create mode 100644 lib/classes/plugininfo/block.php create mode 100644 lib/classes/plugininfo/cachelock.php create mode 100644 lib/classes/plugininfo/cachestore.php create mode 100644 lib/classes/plugininfo/calendartype.php create mode 100644 lib/classes/plugininfo/coursereport.php create mode 100644 lib/classes/plugininfo/editor.php create mode 100644 lib/classes/plugininfo/enrol.php create mode 100644 lib/classes/plugininfo/filter.php create mode 100644 lib/classes/plugininfo/format.php create mode 100644 lib/classes/plugininfo/general.php create mode 100644 lib/classes/plugininfo/gradeexport.php create mode 100644 lib/classes/plugininfo/gradeimport.php create mode 100644 lib/classes/plugininfo/gradereport.php create mode 100644 lib/classes/plugininfo/gradingform.php create mode 100644 lib/classes/plugininfo/local.php create mode 100644 lib/classes/plugininfo/message.php create mode 100644 lib/classes/plugininfo/mnetservice.php create mode 100644 lib/classes/plugininfo/mod.php create mode 100644 lib/classes/plugininfo/plagiarism.php create mode 100644 lib/classes/plugininfo/portfolio.php create mode 100644 lib/classes/plugininfo/profilefield.php create mode 100644 lib/classes/plugininfo/qbehaviour.php create mode 100644 lib/classes/plugininfo/qformat.php create mode 100644 lib/classes/plugininfo/qtype.php create mode 100644 lib/classes/plugininfo/report.php create mode 100644 lib/classes/plugininfo/repository.php create mode 100644 lib/classes/plugininfo/theme.php create mode 100644 lib/classes/plugininfo/tool.php create mode 100644 lib/classes/plugininfo/webservice.php create mode 100644 lib/classes/update/checker.php create mode 100644 lib/classes/update/checker_exception.php create mode 100644 lib/classes/update/deployer.php create mode 100644 lib/classes/update/info.php create mode 100644 lib/editor/atto/classes/plugininfo/atto.php create mode 100644 lib/editor/tinymce/classes/plugininfo/tinymce.php rename lib/tests/{available_update_checker_test.php => update_checker_test.php} (97%) rename lib/tests/{available_update_deployer_test.php => update_deployer_test.php} (91%) create mode 100644 mod/assign/classes/plugininfo/assignfeedback.php create mode 100644 mod/assign/classes/plugininfo/assignsubmission.php create mode 100644 mod/assignment/classes/plugininfo/assignment.php create mode 100644 mod/book/classes/plugininfo/booktool.php create mode 100644 mod/data/classes/plugininfo/datafield.php create mode 100644 mod/data/classes/plugininfo/datapreset.php rename mod/quiz/{adminlib.php => classes/plugininfo/quiz.php} (85%) create mode 100644 mod/quiz/classes/plugininfo/quizaccess.php create mode 100644 mod/scorm/classes/plugininfo/scormreport.php create mode 100644 mod/workshop/classes/plugininfo/workshopallocation.php create mode 100644 mod/workshop/classes/plugininfo/workshopeval.php create mode 100644 mod/workshop/classes/plugininfo/workshopform.php diff --git a/admin/auth.php b/admin/auth.php index 43109017fb8..c83e8482bee 100644 --- a/admin/auth.php +++ b/admin/auth.php @@ -10,7 +10,6 @@ require_once('../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->libdir.'/pluginlib.php'); require_login(); require_capability('moodle/site:config', context_system::instance()); @@ -53,7 +52,7 @@ switch ($action) { set_config('registerauth', ''); } \core\session\manager::gc(); // Remove stale sessions. - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); break; case 'enable': @@ -64,7 +63,7 @@ switch ($action) { set_config('auth', implode(',', $authsenabled)); } \core\session\manager::gc(); // Remove stale sessions. - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); break; case 'down': diff --git a/admin/blocks.php b/admin/blocks.php index 1e37dd26d73..a37af21f1aa 100644 --- a/admin/blocks.php +++ b/admin/blocks.php @@ -5,7 +5,6 @@ require_once('../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); - require_once($CFG->libdir.'/pluginlib.php'); admin_externalpage_setup('manageblocks'); @@ -37,7 +36,7 @@ print_error('blockdoesnotexist', 'error'); } $DB->set_field('block', 'visible', '0', array('id'=>$block->id)); // Hide block - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); admin_get_root(true, false); // settings not required - only pages } @@ -46,7 +45,7 @@ print_error('blockdoesnotexist', 'error'); } $DB->set_field('block', 'visible', '1', array('id'=>$block->id)); // Show block - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); admin_get_root(true, false); // settings not required - only pages } @@ -97,8 +96,8 @@ $table = new flexible_table('admin-blocks-compatible'); - $table->define_columns(array('name', 'instances', 'version', 'hideshow', 'undeletable', 'uninstall', 'settings')); - $table->define_headers(array($strname, $strcourses, $strversion, $strhide.'/'.$strshow, $strprotecthdr, $struninstall, $strsettings)); + $table->define_columns(array('name', 'instances', 'version', 'hideshow', 'undeletable', 'settings', 'uninstall')); + $table->define_headers(array($strname, $strcourses, $strversion, $strhide.'/'.$strshow, $strprotecthdr, $strsettings, $struninstall)); $table->define_baseurl($CFG->wwwroot.'/'.$CFG->admin.'/blocks.php'); $table->set_attribute('class', 'admintable blockstable generaltable'); $table->set_attribute('id', 'compatibleblockstable'); @@ -141,7 +140,7 @@ } } - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('block_'.$blockname)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('block_'.$blockname, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } else { $uninstall = ''; @@ -184,7 +183,7 @@ } else { $visible = ''. ''.$strshow.''; - $class = ' class="dimmed_text"'; // Leading space required! + $class = 'dimmed_text'; } if ($dbversion == $plugin->version) { @@ -205,15 +204,15 @@ } $row = array( - ''.$strblockname.'', + $strblockname, $blocklist, - ''.$version.'', + $version, $visible, $undeletable, + $settings, $uninstall, - $settings ); - $table->add_data($row); + $table->add_data($row, $class); } $table->print_html(); @@ -232,7 +231,7 @@ $table->setup(); foreach ($incompatible as $block) { - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('block_'.$block->name)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('block_'.$block->name, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } else { $uninstall = ''; diff --git a/admin/cli/install.php b/admin/cli/install.php index 0a5ce9be3f5..a6c3873499a 100644 --- a/admin/cli/install.php +++ b/admin/cli/install.php @@ -753,9 +753,8 @@ if (!$envstatus) { } // Test plugin dependencies. -require_once($CFG->libdir . '/pluginlib.php'); $failed = array(); -if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) { +if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) { cli_problem(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed))))); cli_error(get_string('pluginschecktodo', 'admin')); } diff --git a/admin/cli/install_database.php b/admin/cli/install_database.php index 7a4f288ba5a..3ab627920d1 100644 --- a/admin/cli/install_database.php +++ b/admin/cli/install_database.php @@ -167,9 +167,8 @@ if (!$envstatus) { } // Test plugin dependencies. -require_once($CFG->libdir . '/pluginlib.php'); $failed = array(); -if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) { +if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) { cli_problem(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed))))); cli_error(get_string('pluginschecktodo', 'admin')); } diff --git a/admin/cli/upgrade.php b/admin/cli/upgrade.php index 28d7c8d2342..be2ba8bde25 100644 --- a/admin/cli/upgrade.php +++ b/admin/cli/upgrade.php @@ -43,7 +43,6 @@ require_once($CFG->libdir.'/adminlib.php'); // various admin-only function require_once($CFG->libdir.'/upgradelib.php'); // general upgrade/install related functions require_once($CFG->libdir.'/clilib.php'); // cli only functions require_once($CFG->libdir.'/environmentlib.php'); -require_once($CFG->libdir.'/pluginlib.php'); // now get cli options list($options, $unrecognized) = cli_get_params( @@ -117,7 +116,7 @@ if (!$envstatus) { // Test plugin dependencies. $failed = array(); -if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) { +if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) { cli_problem(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed))))); cli_error(get_string('pluginschecktodo', 'admin')); } diff --git a/admin/courseformats.php b/admin/courseformats.php index f86c4becc8c..80f76803bb1 100644 --- a/admin/courseformats.php +++ b/admin/courseformats.php @@ -24,7 +24,6 @@ require_once('../config.php'); require_once($CFG->libdir.'/adminlib.php'); -require_once($CFG->libdir.'/pluginlib.php'); $action = required_param('action', PARAM_ALPHANUMEXT); $formatname = required_param('format', PARAM_PLUGIN); @@ -39,7 +38,7 @@ require_sesskey(); $return = new moodle_url('/admin/settings.php', array('section' => 'manageformats')); -$formatplugins = plugin_manager::instance()->get_plugins_of_type('format'); +$formatplugins = core_plugin_manager::instance()->get_plugins_of_type('format'); $sortorder = array_flip(array_keys($formatplugins)); if (!isset($formatplugins[$formatname])) { @@ -53,13 +52,13 @@ switch ($action) { print_error('cannotdisableformat', 'error', $return); } set_config('disabled', 1, 'format_'. $formatname); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } break; case 'enable': if (!$formatplugins[$formatname]->is_enabled()) { unset_config('disabled', 'format_'. $formatname); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } break; case 'up': diff --git a/admin/editors.php b/admin/editors.php index 50ee7a51086..e1be31f3f67 100644 --- a/admin/editors.php +++ b/admin/editors.php @@ -7,7 +7,6 @@ require_once('../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->libdir.'/pluginlib.php'); $action = required_param('action', PARAM_ALPHANUMEXT); $editor = required_param('editor', PARAM_PLUGIN); @@ -94,7 +93,7 @@ if (empty($active_editors)) { } set_config('texteditors', implode(',', $active_editors)); -plugin_manager::reset_caches(); +core_plugin_manager::reset_caches(); if ($return) { redirect ($returnurl); diff --git a/admin/enrol.php b/admin/enrol.php index 14a7649f899..642d93beace 100644 --- a/admin/enrol.php +++ b/admin/enrol.php @@ -27,7 +27,6 @@ define('NO_OUTPUT_BUFFERING', true); require_once('../config.php'); require_once($CFG->libdir.'/adminlib.php'); -require_once($CFG->libdir.'/pluginlib.php'); $action = required_param('action', PARAM_ALPHANUMEXT); $enrol = required_param('enrol', PARAM_PLUGIN); @@ -51,7 +50,7 @@ switch ($action) { case 'disable': unset($enabled[$enrol]); set_config('enrol_plugins_enabled', implode(',', array_keys($enabled))); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $syscontext->mark_dirty(); // resets all enrol caches break; @@ -62,7 +61,7 @@ switch ($action) { $enabled = array_keys($enabled); $enabled[] = $enrol; set_config('enrol_plugins_enabled', implode(',', $enabled)); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $syscontext->mark_dirty(); // resets all enrol caches break; @@ -122,7 +121,7 @@ switch ($action) { echo $OUTPUT->notification(get_string('success'), 'notifysuccess'); - if (!$return = plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol)) { + if (!$return = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) { $return = new moodle_url('/admin/plugins.php'); } echo $OUTPUT->continue_button($return); diff --git a/admin/filters.php b/admin/filters.php index 05c463ab419..ae64944a43a 100644 --- a/admin/filters.php +++ b/admin/filters.php @@ -33,7 +33,6 @@ require_once(dirname(__FILE__) . '/../config.php'); require_once($CFG->libdir . '/adminlib.php'); - require_once($CFG->libdir . '/pluginlib.php'); $action = optional_param('action', '', PARAM_ALPHANUMEXT); $filterpath = optional_param('filterpath', '', PARAM_SAFEDIR); @@ -104,7 +103,7 @@ // Reset caches and return if ($action) { - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); reset_text_filters_cache(); redirect($returnurl); } @@ -137,7 +136,7 @@ $table = new html_table(); $table->head = array(get_string('filter'), get_string('isactive', 'filters'), - get_string('order'), get_string('applyto', 'filters'), get_string('settings'), get_string('delete')); + get_string('order'), get_string('applyto', 'filters'), get_string('settings'), get_string('uninstallplugin', 'core_admin')); $table->colclasses = array ('leftalign', 'leftalign', 'centeralign', 'leftalign', 'leftalign', 'leftalign'); $table->attributes['class'] = 'admintable generaltable'; $table->id = 'filterssetting'; @@ -180,7 +179,7 @@ function filters_action_url($filterpath, $action) { if ($action === 'delete') { - return new moodle_url('/admin/plugins.php', array('sesskey'=>sesskey(), 'uninstall'=>'filter_'.$filterpath)); + return core_plugin_manager::instance()->get_uninstall_url('filter_'.$filterpath, 'manage'); } return new moodle_url('/admin/filters.php', array('sesskey'=>sesskey(), 'filterpath'=>$filterpath, 'action'=>$action)); } @@ -233,7 +232,7 @@ function get_table_row($filterinfo, $isfirstrow, $islastactive, $applytostrings) } // Delete - $row[] = '' . get_string('delete') . ''; + $row[] = '' . get_string('uninstallplugin', 'core_admin') . ''; return $row; } diff --git a/admin/index.php b/admin/index.php index cf2e78ae416..ea9f37dd294 100644 --- a/admin/index.php +++ b/admin/index.php @@ -78,7 +78,6 @@ core_component::get_core_subsystems(); require_once($CFG->libdir.'/adminlib.php'); // various admin-only functions require_once($CFG->libdir.'/upgradelib.php'); // general upgrade/install related functions -require_once($CFG->libdir.'/pluginlib.php'); // available updates notifications $id = optional_param('id', '', PARAM_TEXT); $confirmupgrade = optional_param('confirmupgrade', 0, PARAM_BOOL); @@ -196,7 +195,7 @@ if (!core_tables_exist()) { // check plugin dependencies $failed = array(); - if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) { + if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) { $PAGE->navbar->add(get_string('pluginscheck', 'admin')); $PAGE->set_title($strinstallation); $PAGE->set_heading($strinstallation . ' - Moodle ' . $CFG->target_release); @@ -323,7 +322,7 @@ if (!$cache and $version > $CFG->version) { // upgrade // check plugin dependencies first $failed = array(); - if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) { + if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) { echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl); die(); } @@ -332,12 +331,12 @@ if (!$cache and $version > $CFG->version) { // upgrade if ($fetchupdates) { // no sesskey support guaranteed here if (empty($CFG->disableupdatenotifications)) { - available_update_checker::instance()->fetch(); + \core\update\checker::instance()->fetch(); } redirect($reloadurl); } - $deployer = available_update_deployer::instance(); + $deployer = \core\update\deployer::instance(); if ($deployer->enabled()) { $deployer->initialize($reloadurl, $reloadurl); @@ -348,7 +347,7 @@ if (!$cache and $version > $CFG->version) { // upgrade } } - echo $output->upgrade_plugin_check_page(plugin_manager::instance(), available_update_checker::instance(), + echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(), $version, $showallplugins, $reloadurl, new moodle_url('/admin/index.php', array('confirmupgrade'=>1, 'confirmrelease'=>1, 'confirmplugincheck'=>1))); die(); @@ -386,13 +385,13 @@ if (!$cache and moodle_needs_upgrading()) { if ($fetchupdates) { // no sesskey support guaranteed here - available_update_checker::instance()->fetch(); + \core\update\checker::instance()->fetch(); redirect($PAGE->url); } $output = $PAGE->get_renderer('core', 'admin'); - $deployer = available_update_deployer::instance(); + $deployer = \core\update\deployer::instance(); if ($deployer->enabled()) { $deployer->initialize($PAGE->url, $PAGE->url); @@ -405,14 +404,14 @@ if (!$cache and moodle_needs_upgrading()) { // check plugin dependencies first $failed = array(); - if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) { + if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) { echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url); die(); } unset($failed); // dependencies check passed, let's rock! - echo $output->upgrade_plugin_check_page(plugin_manager::instance(), available_update_checker::instance(), + echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(), $version, $showallplugins, new moodle_url($PAGE->url), new moodle_url('/admin/index.php', array('confirmplugincheck'=>1))); @@ -514,6 +513,15 @@ if (any_new_admin_settings($adminroot)){ redirect('upgradesettings.php'); } +// Return to original page that started the plugin uninstallation if necessary. +if (isset($SESSION->pluginuninstallreturn)) { + $return = $SESSION->pluginuninstallreturn; + unset($SESSION->pluginuninstallreturn); + if ($return) { + redirect($return); + } +} + // Everything should now be set up, and the user is an admin // Print default admin page with notifications. @@ -525,13 +533,13 @@ $dbproblems = $DB->diagnose(); $maintenancemode = !empty($CFG->maintenance_enabled); // Available updates for Moodle core -$updateschecker = available_update_checker::instance(); +$updateschecker = \core\update\checker::instance(); $availableupdates = array(); $availableupdates['core'] = $updateschecker->get_update_info('core', array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds)); // Available updates for contributed plugins -$pluginman = plugin_manager::instance(); +$pluginman = core_plugin_manager::instance(); foreach ($pluginman->get_plugins() as $plugintype => $plugintypeinstances) { foreach ($plugintypeinstances as $pluginname => $plugininfo) { if (!empty($plugininfo->availableupdates)) { diff --git a/admin/localplugins.php b/admin/localplugins.php index 8286e96fee5..fb5c4cf74c6 100644 --- a/admin/localplugins.php +++ b/admin/localplugins.php @@ -30,7 +30,6 @@ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->libdir.'/pluginlib.php'); admin_externalpage_setup('managelocalplugins'); @@ -60,7 +59,7 @@ core_collator::asort($plugins); foreach ($plugins as $plugin => $name) { $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('local_'.$plugin)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('local_'.$plugin, 'manage')) { $uninstall = html_writer::link($uninstallurl, get_string('uninstallplugin', 'core_admin')); } diff --git a/admin/message.php b/admin/message.php index f9fee27c2f6..270a230210e 100644 --- a/admin/message.php +++ b/admin/message.php @@ -24,7 +24,6 @@ require_once(dirname(__FILE__) . '/../config.php'); require_once($CFG->dirroot . '/message/lib.php'); require_once($CFG->libdir.'/adminlib.php'); -require_once($CFG->libdir.'/pluginlib.php'); // This is an admin page admin_externalpage_setup('managemessageoutputs'); @@ -43,7 +42,7 @@ if (!empty($disable) && confirm_sesskey()) { print_error('outputdoesnotexist', 'message'); } $DB->set_field('message_processors', 'enabled', '0', array('id'=>$processor->id)); // Disable output - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } if (!empty($enable) && confirm_sesskey()) { @@ -51,7 +50,7 @@ if (!empty($enable) && confirm_sesskey()) { print_error('outputdoesnotexist', 'message'); } $DB->set_field('message_processors', 'enabled', '1', array('id'=>$processor->id)); // Enable output - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } if ($disable || $enable) { diff --git a/admin/modules.php b/admin/modules.php index a964c73dfe4..4cca5690820 100644 --- a/admin/modules.php +++ b/admin/modules.php @@ -5,7 +5,6 @@ require_once('../course/lib.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); - require_once($CFG->libdir.'/pluginlib.php'); // defines define('MODULE_TABLE','module_administration_table'); @@ -48,7 +47,7 @@ FROM {course_modules} WHERE visibleold=1 AND module=?)", array($module->id)); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); admin_get_root(true, false); // settings not required - only pages } @@ -65,7 +64,7 @@ FROM {course_modules} WHERE visible=1 AND module=?)", array($module->id)); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); admin_get_root(true, false); // settings not required - only pages } @@ -82,10 +81,10 @@ // construct the flexible table ready to display $table = new flexible_table(MODULE_TABLE); $table->define_columns(array('name', 'instances', 'version', 'hideshow', 'uninstall', 'settings')); - $table->define_headers(array($stractivitymodule, $stractivities, $strversion, "$strhide/$strshow", $struninstall, $strsettings)); + $table->define_headers(array($stractivitymodule, $stractivities, $strversion, "$strhide/$strshow", $strsettings, $struninstall)); $table->define_baseurl($CFG->wwwroot.'/'.$CFG->admin.'/modules.php'); $table->set_attribute('id', 'modules'); - $table->set_attribute('class', 'generaltable'); + $table->set_attribute('class', 'admintable generaltable'); $table->setup(); foreach ($modules as $module) { @@ -101,7 +100,7 @@ } $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('mod_'.$module->name)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('mod_'.$module->name, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } @@ -136,7 +135,7 @@ } else { $visible = "name&sesskey=".sesskey()."\" title=\"$strshow\">". "pix_url('t/show') . "\" class=\"iconsmall\" alt=\"$strshow\" />"; - $class = ' class="dimmed_text"'; + $class = 'dimmed_text'; } if ($module->name == "forum") { $uninstall = ""; @@ -146,13 +145,13 @@ $version = get_config('mod_'.$module->name, 'version'); $table->add_data(array( - ''.$strmodulename.'', + $strmodulename, $countlink, - ''.$version.'', + $version, $visible, + $settings, $uninstall, - $settings - )); + ), $class); } $table->print_html(); diff --git a/admin/plagiarism.php b/admin/plagiarism.php index d5fc85ae125..d0048693c49 100644 --- a/admin/plagiarism.php +++ b/admin/plagiarism.php @@ -29,7 +29,6 @@ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->libdir.'/pluginlib.php'); admin_externalpage_setup('manageplagiarismplugins'); @@ -74,7 +73,7 @@ foreach ($plagiarismplugins as $plugin => $dir) { } // uninstall link. $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('plagiarism_'.$plugin)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('plagiarism_'.$plugin, 'manage')) { $uninstall = html_writer::link($uninstallurl, $txt->uninstall); } $table->data[] = array($displayname, $version, $uninstall, $settings); diff --git a/admin/plugins.php b/admin/plugins.php index a092e000189..c156f9d77cc 100644 --- a/admin/plugins.php +++ b/admin/plugins.php @@ -35,15 +35,15 @@ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); require_once($CFG->libdir . '/filelib.php'); $fetchremote = optional_param('fetchremote', false, PARAM_BOOL); $updatesonly = optional_param('updatesonly', false, PARAM_BOOL); $contribonly = optional_param('contribonly', false, PARAM_BOOL); -$uninstall = optional_param('uninstall', '', PARAM_COMPONENT); -$delete = optional_param('delete', '', PARAM_COMPONENT); -$confirmed = optional_param('confirm', false, PARAM_BOOL); +$uninstall = optional_param('uninstall', '', PARAM_COMPONENT); +$delete = optional_param('delete', '', PARAM_COMPONENT); +$confirmed = optional_param('confirm', false, PARAM_BOOL); +$return = optional_param('return', 'overview', PARAM_ALPHA); // NOTE: do not use admin_externalpage_setup() here because it loads // full admin tree which is not possible during uninstallation. @@ -52,7 +52,7 @@ require_login(); $syscontext = context_system::instance(); require_capability('moodle/site:config', $syscontext); -$pluginman = plugin_manager::instance(); +$pluginman = core_plugin_manager::instance(); if ($uninstall) { require_sesskey(); @@ -74,7 +74,7 @@ if ($uninstall) { // Make sure we know the plugin. if (is_null($pluginfo)) { throw new moodle_exception('err_uninstalling_unknown_plugin', 'core_plugin', '', array('plugin' => $uninstall), - 'plugin_manager::get_plugin_info() returned null for the plugin to be uninstalled'); + 'core_plugin_manager::get_plugin_info() returned null for the plugin to be uninstalled'); } $pluginname = $pluginman->plugin_name($pluginfo->component); @@ -84,15 +84,17 @@ if ($uninstall) { if (!$pluginman->can_uninstall_plugin($pluginfo->component)) { throw new moodle_exception('err_cannot_uninstall_plugin', 'core_plugin', '', array('plugin' => $pluginfo->component), - 'plugin_manager::can_uninstall_plugin() returned false'); + 'core_plugin_manager::can_uninstall_plugin() returned false'); } if (!$confirmed) { - $continueurl = new moodle_url($PAGE->url, array('uninstall' => $pluginfo->component, 'sesskey' => sesskey(), 'confirm' => 1)); - echo $output->plugin_uninstall_confirm_page($pluginman, $pluginfo, $continueurl); + $continueurl = new moodle_url($PAGE->url, array('uninstall' => $pluginfo->component, 'sesskey' => sesskey(), 'confirm' => 1, 'return'=>$return)); + $cancelurl = $pluginfo->get_return_url_after_uninstall($return); + echo $output->plugin_uninstall_confirm_page($pluginman, $pluginfo, $continueurl, $cancelurl); exit(); } else { + $SESSION->pluginuninstallreturn = $pluginfo->get_return_url_after_uninstall($return); $progress = new progress_trace_buffer(new text_progress_trace(), false); $pluginman->uninstall_plugin($pluginfo->component, $progress); $progress->finished(); @@ -133,7 +135,7 @@ if ($delete and $confirmed) { // Make sure we know the plugin. if (is_null($pluginfo)) { throw new moodle_exception('err_removing_unknown_plugin', 'core_plugin', '', array('plugin' => $delete), - 'plugin_manager::get_plugin_info() returned null for the plugin to be deleted'); + 'core_plugin_manager::get_plugin_info() returned null for the plugin to be deleted'); } $pluginname = $pluginman->plugin_name($pluginfo->component); @@ -144,7 +146,7 @@ if ($delete and $confirmed) { if (!is_null($pluginfo->versiondb)) { throw new moodle_exception('err_removing_installed_plugin', 'core_plugin', '', array('plugin' => $pluginfo->component, 'versiondb' => $pluginfo->versiondb), - 'plugin_manager::get_plugin_info() returned not-null versiondb for the plugin to be deleted'); + 'core_plugin_manager::get_plugin_info() returned not-null versiondb for the plugin to be deleted'); } // Make sure the folder is removable. @@ -176,7 +178,7 @@ admin_externalpage_setup('pluginsoverview'); /** @var core_admin_renderer $output */ $output = $PAGE->get_renderer('core', 'admin'); -$checker = available_update_checker::instance(); +$checker = \core\update\checker::instance(); // Filtering options. $options = array( @@ -190,7 +192,7 @@ if ($fetchremote) { redirect(new moodle_url($PAGE->url, $options)); } -$deployer = available_update_deployer::instance(); +$deployer = \core\update\deployer::instance(); if ($deployer->enabled()) { $myurl = new moodle_url($PAGE->url, array('updatesonly' => $updatesonly, 'contribonly' => $contribonly)); $deployer->initialize($myurl, new moodle_url('/admin')); diff --git a/admin/portfolio.php b/admin/portfolio.php index 3b460fdce2e..48237bbb33a 100644 --- a/admin/portfolio.php +++ b/admin/portfolio.php @@ -4,7 +4,6 @@ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->libdir . '/portfoliolib.php'); require_once($CFG->libdir . '/portfolio/forms.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); $portfolio = optional_param('pf', '', PARAM_ALPHANUMEXT); $action = optional_param('action', '', PARAM_ALPHA); @@ -89,7 +88,7 @@ if (($action == 'edit') || ($action == 'new')) { } else { portfolio_static_function($plugin, 'create_instance', $plugin, $fromform->name, $fromform); } - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $savedstr = get_string('instancesaved', 'portfolio'); redirect($baseurl, $savedstr, 1); exit; @@ -118,7 +117,7 @@ if (($action == 'edit') || ($action == 'new')) { $instance->set('visible', $visible); $instance->save(); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $return = true; } else if ($action == 'delete') { $instance = portfolio_instance($portfolio); diff --git a/admin/qbehaviours.php b/admin/qbehaviours.php index 8e98af4237e..7cd027e2ef6 100644 --- a/admin/qbehaviours.php +++ b/admin/qbehaviours.php @@ -28,7 +28,6 @@ require_once(dirname(__FILE__) . '/../config.php'); require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); require_once($CFG->libdir . '/tablelib.php'); // Check permissions. @@ -40,7 +39,7 @@ admin_externalpage_setup('manageqbehaviours'); $thispageurl = new moodle_url('/admin/qbehaviours.php'); $behaviours = core_component::get_plugin_list('qbehaviour'); -$pluginmanager = plugin_manager::instance(); +$pluginmanager = core_plugin_manager::instance(); // Get some data we will need - question counts and which types are needed. $counts = $DB->get_records_sql_menu(" @@ -92,7 +91,7 @@ if (($disable = optional_param('disable', '', PARAM_PLUGIN)) && confirm_sesskey( $disabledbehaviours[] = $disable; set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question'); } - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($thispageurl); } @@ -110,7 +109,7 @@ if (($enable = optional_param('enable', '', PARAM_PLUGIN)) && confirm_sesskey()) unset($disabledbehaviours[$key]); set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question'); } - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($thispageurl); } @@ -138,65 +137,6 @@ if (($down = optional_param('down', '', PARAM_PLUGIN)) && confirm_sesskey()) { redirect($thispageurl); } -// Delete. -if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey()) { - // Check it is OK to delete this question type. - if ($delete == 'missing') { - print_error('cannotdeletemissingbehaviour', 'question', $thispageurl); - } - - if (!isset($behaviours[$delete]) && !get_config('qbehaviour_' . $delete, 'version')) { - print_error('unknownbehaviour', 'question', $thispageurl, $delete); - } - - $behaviourname = $sortedbehaviours[$delete]; - if ($counts[$delete] > 0) { - print_error('cannotdeletebehaviourinuse', 'question', $thispageurl, $behaviourname); - } - if ($needed[$delete] > 0) { - print_error('cannotdeleteneededbehaviour', 'question', $thispageurl, $behaviourname); - } - - // If not yet confirmed, display a confirmation message. - if (!optional_param('confirm', '', PARAM_BOOL)) { - echo $OUTPUT->header(); - echo $OUTPUT->heading(get_string('deletebehaviourareyousure', 'question', $behaviourname)); - echo $OUTPUT->confirm( - get_string('deletebehaviourareyousuremessage', 'question', $behaviourname), - new moodle_url($thispageurl, array('delete' => $delete, 'confirm' => 1)), - $thispageurl); - echo $OUTPUT->footer(); - exit; - } - - // Do the deletion. - echo $OUTPUT->header(); - echo $OUTPUT->heading(get_string('deletingbehaviour', 'question', $behaviourname)); - - // Remove this behaviour from configurations where it might appear. - if (($key = array_search($delete, $disabledbehaviours)) !== false) { - unset($disabledbehaviours[$key]); - set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question'); - } - $behaviourorder = array_keys($sortedbehaviours); - if (($key = array_search($delete, $behaviourorder)) !== false) { - unset($behaviourorder[$key]); - set_config('behavioursortorder', implode(',', $behaviourorder), 'question'); - } - - // Then uninstall the plugin. - uninstall_plugin('qbehaviour', $delete); - - // Display a message. - $a = new stdClass(); - $a->behaviour = $behaviourname; - $a->directory = core_component::get_plugin_directory('qbehaviour', $delete); - echo $OUTPUT->box(get_string('qbehaviourdeletefiles', 'question', $a), 'generalbox', 'notice'); - echo $OUTPUT->continue_button($thispageurl); - echo $OUTPUT->footer(); - exit; -} - // End of process actions ================================================== // Print the page heading. @@ -207,12 +147,12 @@ echo $OUTPUT->heading(get_string('manageqbehaviours', 'admin')); $table = new flexible_table('qbehaviouradmintable'); $table->define_baseurl($thispageurl); $table->define_columns(array('behaviour', 'numqas', 'version', 'requires', - 'available', 'delete')); + 'available', 'uninstall')); $table->define_headers(array(get_string('behaviour', 'question'), get_string('numqas', 'question'), get_string('version'), get_string('requires', 'admin'), - get_string('availableq', 'question'), get_string('delete'))); + get_string('availableq', 'question'), get_string('uninstallplugin', 'core_admin'))); $table->set_attribute('id', 'qbehaviours'); -$table->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide'); +$table->set_attribute('class', 'generaltable admintable'); $table->setup(); // Add a row for each question type. @@ -267,9 +207,11 @@ foreach ($sortedbehaviours as $behaviour => $behaviourname) { if ($needed[$behaviour]) { $row[] = ''; } else { - $row[] = html_writer::link(new moodle_url($thispageurl, - array('delete' => $behaviour, 'sesskey' => sesskey())), get_string('delete'), + $uninstallurl = core_plugin_manager::instance()->get_uninstall_url('qbehaviour_'.$behaviour, 'manage'); + if ($uninstallurl) { + $row[] = html_writer::link($uninstallurl, get_string('uninstallplugin', 'core_admin'), array('title' => get_string('uninstallbehaviour', 'question'))); + } } $table->add_data($row, $rowclass); diff --git a/admin/qtypes.php b/admin/qtypes.php index 3e865d14160..80c6c6d76d2 100644 --- a/admin/qtypes.php +++ b/admin/qtypes.php @@ -28,7 +28,6 @@ require_once(dirname(__FILE__) . '/../config.php'); require_once($CFG->libdir . '/questionlib.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); require_once($CFG->libdir . '/tablelib.php'); // Check permissions. @@ -41,7 +40,7 @@ admin_externalpage_setup('manageqtypes'); $thispageurl = new moodle_url('/admin/qtypes.php'); $qtypes = question_bank::get_all_qtypes(); -$pluginmanager = plugin_manager::instance(); +$pluginmanager = core_plugin_manager::instance(); // Get some data we will need - question counts and which types are needed. $counts = $DB->get_records_sql(" @@ -122,58 +121,6 @@ if (($down = optional_param('down', '', PARAM_PLUGIN)) && confirm_sesskey()) { redirect($thispageurl); } -// Delete. -if (($delete = optional_param('delete', '', PARAM_PLUGIN)) && confirm_sesskey()) { - // Check it is OK to delete this question type. - if ($delete == 'missingtype') { - print_error('cannotdeletemissingqtype', 'question', $thispageurl); - } - - if (!isset($qtypes[$delete]) && !get_config('qtype_' . $delete, 'version')) { - print_error('unknownquestiontype', 'question', $thispageurl, $delete); - } - - $qtypename = $qtypes[$delete]->local_name(); - if ($counts[$delete]->numquestions + $counts[$delete]->numhidden > 0) { - print_error('cannotdeleteqtypeinuse', 'question', $thispageurl, $qtypename); - } - - if ($needed[$delete] > 0) { - print_error('cannotdeleteqtypeneeded', 'question', $thispageurl, $qtypename); - } - - // If not yet confirmed, display a confirmation message. - if (!optional_param('confirm', '', PARAM_BOOL)) { - $qtypename = $qtypes[$delete]->local_name(); - echo $OUTPUT->header(); - echo $OUTPUT->heading(get_string('deleteqtypeareyousure', 'question', $qtypename)); - echo $OUTPUT->confirm(get_string('deleteqtypeareyousuremessage', 'question', $qtypename), - new moodle_url($thispageurl, array('delete' => $delete, 'confirm' => 1)), - $thispageurl); - echo $OUTPUT->footer(); - exit; - } - - // Do the deletion. - echo $OUTPUT->header(); - echo $OUTPUT->heading(get_string('deletingqtype', 'question', $qtypename)); - - // Delete any questoin configuration records mentioning this plugin. - unset_config($delete . '_disabled', 'question'); - unset_config($delete . '_sortorder', 'question'); - - // Then uninstall the plugin. - uninstall_plugin('qtype', $delete); - - $a = new stdClass(); - $a->qtype = $qtypename; - $a->directory = $qtypes[$delete]->plugin_dir(); - echo $OUTPUT->box(get_string('qtypedeletefiles', 'question', $a), 'generalbox', 'notice'); - echo $OUTPUT->continue_button($thispageurl); - echo $OUTPUT->footer(); - exit; -} - // End of process actions ================================================== // Print the page heading. @@ -184,10 +131,10 @@ echo $OUTPUT->heading(get_string('manageqtypes', 'admin')); $table = new flexible_table('qtypeadmintable'); $table->define_baseurl($thispageurl); $table->define_columns(array('questiontype', 'numquestions', 'version', 'requires', - 'availableto', 'delete', 'settings')); + 'availableto', 'uninstall', 'settings')); $table->define_headers(array(get_string('questiontype', 'question'), get_string('numquestions', 'question'), get_string('version'), get_string('requires', 'admin'), get_string('availableq', 'question'), - get_string('delete'), get_string('settings'))); + get_string('settings'), get_string('uninstallplugin', 'core_admin'))); $table->set_attribute('id', 'qtypes'); $table->set_attribute('class', 'admintable generaltable'); $table->setup(); @@ -259,15 +206,6 @@ foreach ($sortedqtypes as $qtypename => $localname) { $icons .= question_type_icon_html('down', $qtypename, 't/down', get_string('down'), ''); $row[] = $icons; - // Delete link, if available. - if ($needed[$qtypename]) { - $row[] = ''; - } else { - $row[] = html_writer::link(new moodle_url($thispageurl, - array('delete' => $qtypename, 'sesskey' => sesskey())), get_string('delete'), - array('title' => get_string('uninstallqtype', 'question'))); - } - // Settings link, if available. $settings = admin_get_root()->locate('qtypesetting' . $qtypename); if ($settings instanceof admin_externalpage) { @@ -279,6 +217,17 @@ foreach ($sortedqtypes as $qtypename => $localname) { $row[] = ''; } + // Uninstall link, if available. + if ($needed[$qtypename]) { + $row[] = ''; + } else { + $uninstallurl = core_plugin_manager::instance()->get_uninstall_url('qtype_'.$qtypename, 'manage'); + if ($uninstallurl) { + $row[] = html_writer::link($uninstallurl, get_string('uninstallplugin', 'core_admin'), + array('title' => get_string('uninstallqtype', 'question'))); + } + } + $table->add_data($row, $rowclass); } diff --git a/admin/renderer.php b/admin/renderer.php index ca1a28c6778..0f84e00c4b9 100644 --- a/admin/renderer.php +++ b/admin/renderer.php @@ -25,7 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->libdir . '/pluginlib.php'); /** * Standard HTML output renderer for core_admin subsystem @@ -121,7 +120,7 @@ class core_admin_renderer extends plugin_renderer_base { $output .= $this->header(); $output .= $this->heading(get_string('pluginscheck', 'admin')); $output .= $this->warning(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed))))); - $output .= $this->plugins_check_table(plugin_manager::instance(), $version, array('xdep' => true)); + $output .= $this->plugins_check_table(core_plugin_manager::instance(), $version, array('xdep' => true)); $output .= $this->warning(get_string('pluginschecktodo', 'admin')); $output .= $this->continue_button($reloadurl); @@ -190,15 +189,15 @@ class core_admin_renderer extends plugin_renderer_base { /** * Display the upgrade page that lists all the plugins that require attention. - * @param plugin_manager $pluginman provides information about the plugins. - * @param available_update_checker $checker provides information about available updates. + * @param core_plugin_manager $pluginman provides information about the plugins. + * @param \core\update\checker $checker provides information about available updates. * @param int $version the version of the Moodle code from version.php. * @param bool $showallplugins * @param moodle_url $reloadurl * @param moodle_url $continueurl * @return string HTML to output. */ - public function upgrade_plugin_check_page(plugin_manager $pluginman, available_update_checker $checker, + public function upgrade_plugin_check_page(core_plugin_manager $pluginman, \core\update\checker $checker, $version, $showallplugins, $reloadurl, $continueurl) { global $CFG; @@ -240,11 +239,11 @@ class core_admin_renderer extends plugin_renderer_base { /** * Prints a page with a summary of plugin deployment to be confirmed. * - * @param available_update_deployer $deployer - * @param array $data deployer's data package as returned by {@link available_update_deployer::submitted_data()} + * @param \core\update\deployer $deployer + * @param array $data deployer's data package as returned by {@link \core\update\deployer::submitted_data()} * @return string */ - public function upgrade_plugin_confirm_deploy_page(available_update_deployer $deployer, array $data) { + public function upgrade_plugin_confirm_deploy_page(\core\update\deployer $deployer, array $data) { if (!$deployer->initialized()) { throw new coding_exception('Unable to render a page for non-initialized deployer.'); @@ -302,7 +301,7 @@ class core_admin_renderer extends plugin_renderer_base { * @param bool $dbproblems warn db has problems * @param bool $maintenancemode warn in maintenance mode * @param bool $buggyiconvnomb warn iconv problems - * @param array|null $availableupdates array of available_update_info objects or null + * @param array|null $availableupdates array of \core\update\info objects or null * @param int|null $availableupdatesfetch timestamp of the most recent updates fetch or null (unknown) * * @return string HTML to output. @@ -341,12 +340,12 @@ class core_admin_renderer extends plugin_renderer_base { * bool contribonly - show only contributed extensions * bool updatesonly - show only plugins with an available update * - * @param plugin_manager $pluginman - * @param available_update_checker $checker + * @param core_plugin_manager $pluginman + * @param \core\update\checker $checker * @param array $options filtering options * @return string HTML to output. */ - public function plugin_management_page(plugin_manager $pluginman, available_update_checker $checker, array $options = array()) { + public function plugin_management_page(core_plugin_manager $pluginman, \core\update\checker $checker, array $options = array()) { global $CFG; $output = ''; @@ -377,12 +376,13 @@ class core_admin_renderer extends plugin_renderer_base { /** * Display a page to confirm the plugin uninstallation. * - * @param plugin_manager $pluginman - * @param plugininfo_base $pluginfo + * @param core_plugin_manager $pluginman + * @param \core\plugininfo\base $pluginfo * @param moodle_url $continueurl URL to continue after confirmation + * @param moodle_url $cancelurl URL to to go if cancelled * @return string */ - public function plugin_uninstall_confirm_page(plugin_manager $pluginman, plugininfo_base $pluginfo, moodle_url $continueurl) { + public function plugin_uninstall_confirm_page(core_plugin_manager $pluginman, \core\plugininfo\base $pluginfo, moodle_url $continueurl, moodle_url $cancelurl) { $output = ''; $pluginname = $pluginman->plugin_name($pluginfo->component); @@ -394,7 +394,7 @@ class core_admin_renderer extends plugin_renderer_base { $output .= $this->output->header(); $output .= $this->output->heading(get_string('uninstalling', 'core_plugin', array('name' => $pluginname))); - $output .= $this->output->confirm($confirm, $continueurl, $this->page->url); + $output .= $this->output->confirm($confirm, $continueurl, $cancelurl); $output .= $this->output->footer(); return $output; @@ -403,13 +403,13 @@ class core_admin_renderer extends plugin_renderer_base { /** * Display a page with results of plugin uninstallation and offer removal of plugin files. * - * @param plugin_manager $pluginman - * @param plugininfo_base $pluginfo + * @param core_plugin_manager $pluginman + * @param \core\plugininfo\base $pluginfo * @param progress_trace_buffer $progress * @param moodle_url $continueurl URL to continue to remove the plugin folder * @return string */ - public function plugin_uninstall_results_removable_page(plugin_manager $pluginman, plugininfo_base $pluginfo, + public function plugin_uninstall_results_removable_page(core_plugin_manager $pluginman, \core\plugininfo\base $pluginfo, progress_trace_buffer $progress, moodle_url $continueurl) { $output = ''; @@ -442,12 +442,12 @@ class core_admin_renderer extends plugin_renderer_base { /** * Display a page with results of plugin uninstallation and inform about the need to remove plugin files manually. * - * @param plugin_manager $pluginman - * @param plugininfo_base $pluginfo + * @param core_plugin_manager $pluginman + * @param \core\plugininfo\base $pluginfo * @param progress_trace_buffer $progress * @return string */ - public function plugin_uninstall_results_page(plugin_manager $pluginman, plugininfo_base $pluginfo, progress_trace_buffer $progress) { + public function plugin_uninstall_results_page(core_plugin_manager $pluginman, \core\plugininfo\base $pluginfo, progress_trace_buffer $progress) { $output = ''; $pluginname = $pluginfo->component; @@ -676,7 +676,7 @@ class core_admin_renderer extends plugin_renderer_base { * The structure of the $updates param has changed since 2.4. It contains not only updates * for the core itself, but also for all other installed plugins. * - * @param array|null $updates array of (string)component => array of available_update_info objects or null + * @param array|null $updates array of (string)component => array of \core\update\info objects or null * @param int|null $fetch timestamp of the most recent updates fetch or null (unknown) * @return string */ @@ -748,9 +748,9 @@ class core_admin_renderer extends plugin_renderer_base { /** * Helper method to render the information about the available Moodle update * - * @param available_update_info $updateinfo information about the available Moodle core update + * @param \core\update\info $updateinfo information about the available Moodle core update */ - protected function moodle_available_update_info(available_update_info $updateinfo) { + protected function moodle_available_update_info(\core\update\info $updateinfo) { $boxclasses = 'moodleupdateinfo'; $info = array(); @@ -817,12 +817,12 @@ class core_admin_renderer extends plugin_renderer_base { * (bool)full = false: whether to display up-to-date plugins, too * (bool)xdep = false: display the plugins with unsatisified dependecies only * - * @param plugin_manager $pluginman provides information about the plugins. + * @param core_plugin_manager $pluginman provides information about the plugins. * @param int $version the version of the Moodle code from version.php. * @param array $options rendering options * @return string HTML code */ - public function plugins_check_table(plugin_manager $pluginman, $version, array $options = array()) { + public function plugins_check_table(core_plugin_manager $pluginman, $version, array $options = array()) { global $CFG; $plugininfo = $pluginman->get_plugins(); @@ -915,7 +915,7 @@ class core_admin_renderer extends plugin_renderer_base { $requires = new html_table_cell($this->required_column($plugin, $pluginman, $version)); $statusisboring = in_array($statuscode, array( - plugin_manager::PLUGIN_STATUS_NODB, plugin_manager::PLUGIN_STATUS_UPTODATE)); + core_plugin_manager::PLUGIN_STATUS_NODB, core_plugin_manager::PLUGIN_STATUS_UPTODATE)); $coredependency = $plugin->is_core_dependency_satisfied($version); $otherpluginsdependencies = $pluginman->are_dependencies_satisfied($plugin->get_other_required_plugins()); @@ -991,12 +991,12 @@ class core_admin_renderer extends plugin_renderer_base { /** * Formats the information that needs to go in the 'Requires' column. - * @param plugininfo_base $plugin the plugin we are rendering the row for. - * @param plugin_manager $pluginman provides data on all the plugins. + * @param \core\plugininfo\base $plugin the plugin we are rendering the row for. + * @param core_plugin_manager $pluginman provides data on all the plugins. * @param string $version * @return string HTML code */ - protected function required_column(plugininfo_base $plugin, plugin_manager $pluginman, $version) { + protected function required_column(\core\plugininfo\base $plugin, core_plugin_manager $pluginman, $version) { $requires = array(); if (!empty($plugin->versionrequires)) { @@ -1048,11 +1048,11 @@ class core_admin_renderer extends plugin_renderer_base { /** * Prints an overview about the plugins - number of installed, number of extensions etc. * - * @param plugin_manager $pluginman provides information about the plugins + * @param core_plugin_manager $pluginman provides information about the plugins * @param array $options filtering options * @return string as usually */ - public function plugins_overview_panel(plugin_manager $pluginman, array $options = array()) { + public function plugins_overview_panel(core_plugin_manager $pluginman, array $options = array()) { global $CFG; $plugininfo = $pluginman->get_plugins(); @@ -1061,7 +1061,7 @@ class core_admin_renderer extends plugin_renderer_base { foreach ($plugininfo as $type => $plugins) { foreach ($plugins as $name => $plugin) { - if ($plugin->get_status() === plugin_manager::PLUGIN_STATUS_MISSING) { + if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) { continue; } $numtotal++; @@ -1128,11 +1128,11 @@ class core_admin_renderer extends plugin_renderer_base { * * This default implementation renders all plugins into one big table. * - * @param plugin_manager $pluginman provides information about the plugins. + * @param core_plugin_manager $pluginman provides information about the plugins. * @param array $options filtering options * @return string HTML code */ - public function plugins_control_panel(plugin_manager $pluginman, array $options = array()) { + public function plugins_control_panel(core_plugin_manager $pluginman, array $options = array()) { global $CFG; $plugininfo = $pluginman->get_plugins(); @@ -1186,8 +1186,12 @@ class core_admin_renderer extends plugin_renderer_base { ); foreach ($plugininfo as $type => $plugins) { - - $header = new html_table_cell($pluginman->plugintype_name_plural($type)); + $heading = $pluginman->plugintype_name_plural($type); + $pluginclass = core_plugin_manager::resolve_plugininfo_class($type); + if ($manageurl = $pluginclass::get_manage_url()) { + $heading = html_writer::link($manageurl, $heading); + } + $header = new html_table_cell(html_writer::tag('span', $heading, array('id'=>'plugin_type_cell_'.$type))); $header->header = true; $header->colspan = array_sum($table->headspan); $header = new html_table_row(array($header)); @@ -1214,9 +1218,9 @@ class core_admin_renderer extends plugin_renderer_base { } $status = $plugin->get_status(); $row->attributes['class'] .= ' status-'.$status; - if ($status === plugin_manager::PLUGIN_STATUS_MISSING) { + if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) { $msg = html_writer::tag('span', get_string('status_missing', 'core_plugin'), array('class' => 'statusmsg')); - } else if ($status === plugin_manager::PLUGIN_STATUS_NEW) { + } else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) { $msg = html_writer::tag('span', get_string('status_new', 'core_plugin'), array('class' => 'statusmsg')); } else { $msg = ''; @@ -1254,8 +1258,7 @@ class core_admin_renderer extends plugin_renderer_base { } $settings = new html_table_cell($settings); - if ($pluginman->can_uninstall_plugin($plugin->component)) { - $uninstallurl = $plugin->get_uninstall_url(); + if ($uninstallurl = $pluginman->get_uninstall_url($plugin->component, 'overview')) { $uninstall = html_writer::link($uninstallurl, get_string('uninstall', 'core_plugin')); } else { $uninstall = ''; @@ -1295,9 +1298,9 @@ class core_admin_renderer extends plugin_renderer_base { * The passed objects always provides at least the 'version' property containing * the (higher) version of the plugin available. * - * @param available_update_info $updateinfo information about the available update for the plugin + * @param \core\update\info $updateinfo information about the available update for the plugin */ - protected function plugin_available_update_info(available_update_info $updateinfo) { + protected function plugin_available_update_info(\core\update\info $updateinfo) { $boxclasses = 'pluginupdateinfo'; $info = array(); @@ -1326,7 +1329,7 @@ class core_admin_renderer extends plugin_renderer_base { $box .= html_writer::tag('div', get_string('updateavailable', 'core_plugin', $updateinfo->version), array('class' => 'version')); $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), ''); - $deployer = available_update_deployer::instance(); + $deployer = \core\update\deployer::instance(); if ($deployer->initialized()) { $impediments = $deployer->deployment_impediments($updateinfo); if (empty($impediments)) { diff --git a/admin/reports.php b/admin/reports.php index 879497c5222..46c244fb002 100644 --- a/admin/reports.php +++ b/admin/reports.php @@ -30,7 +30,6 @@ require_once(dirname(__FILE__) . '/../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->libdir.'/pluginlib.php'); admin_externalpage_setup('managereports'); @@ -46,7 +45,7 @@ $table->define_columns(array('name', 'version', 'uninstall')); $table->define_headers(array(get_string('plugin'), get_string('version'), $struninstall)); $table->define_baseurl($PAGE->url); $table->set_attribute('id', 'reportplugins'); -$table->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide'); +$table->set_attribute('class', 'admintable generaltable'); $table->setup(); $plugins = array(); @@ -74,7 +73,7 @@ foreach ($installed as $config) { foreach ($plugins as $plugin => $name) { $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('report_'.$plugin)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('report_'.$plugin, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } diff --git a/admin/repository.php b/admin/repository.php index 05fc7557893..1e991e4eff3 100644 --- a/admin/repository.php +++ b/admin/repository.php @@ -17,7 +17,6 @@ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->dirroot . '/repository/lib.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); $repository = optional_param('repos', '', PARAM_ALPHANUMEXT); $action = optional_param('action', '', PARAM_ALPHANUMEXT); @@ -149,7 +148,7 @@ if (($action == 'edit') || ($action == 'new')) { } if ($success) { // configs saved - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($baseurl); } else { print_error('instancenotsaved', 'repository', $baseurl); @@ -190,7 +189,7 @@ if (($action == 'edit') || ($action == 'new')) { print_error('invalidplugin', 'repository', '', $repository); } $repositorytype->update_visibility(true); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $return = true; } else if ($action == 'hide') { if (!confirm_sesskey()) { @@ -201,7 +200,7 @@ if (($action == 'edit') || ($action == 'new')) { print_error('invalidplugin', 'repository', '', $repository); } $repositorytype->update_visibility(false); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $return = true; } else if ($action == 'delete') { $repositorytype = repository::get_type_by_typename($repository); @@ -212,7 +211,7 @@ if (($action == 'edit') || ($action == 'new')) { } if ($repositorytype->delete($downloadcontents)) { - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($baseurl); } else { print_error('instancenotdeleted', 'repository', $baseurl); diff --git a/admin/repositoryinstance.php b/admin/repositoryinstance.php index 3def4f3ab5d..03738576010 100644 --- a/admin/repositoryinstance.php +++ b/admin/repositoryinstance.php @@ -17,7 +17,6 @@ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->dirroot . '/repository/lib.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); require_sesskey(); @@ -103,7 +102,7 @@ if (!empty($edit) || !empty($new)) { $data = data_submitted(); } if ($success) { - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($parenturl); } else { print_error('instancenotsaved', 'repository', $parenturl); @@ -120,7 +119,7 @@ if (!empty($edit) || !empty($new)) { } else if (!empty($hide)) { $instance = repository::get_type_by_typename($hide); $instance->hide(); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $return = true; } else if (!empty($delete)) { $instance = repository::get_instance($delete); @@ -133,7 +132,7 @@ if (!empty($edit) || !empty($new)) { if ($sure) { if ($instance->delete($downloadcontents)) { $deletedstr = get_string('instancedeleted', 'repository'); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($parenturl, $deletedstr, 3); } else { print_error('instancenotdeleted', 'repository', $parenturl); diff --git a/admin/settings/plugins.php b/admin/settings/plugins.php index 19bace3728f..0865b75e779 100644 --- a/admin/settings/plugins.php +++ b/admin/settings/plugins.php @@ -1,23 +1,38 @@ . -/* - * Please note that is file is always loaded last - it means that you can inject entries into other categories too. - */ +/** + * Load all plugins into the admin tree. + * +* Please note that is file is always loaded last - it means that you can inject entries into other categories too. +* +* @package core +* @copyright 2007 Petr Skoda {@link http://skodak.org} +* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later +*/ if ($hassiteconfig) { - require_once("$CFG->libdir/pluginlib.php"); - $allplugins = plugin_manager::instance()->get_plugins(); - $ADMIN->add('modules', new admin_page_pluginsoverview()); // activity modules $ADMIN->add('modules', new admin_category('modsettings', new lang_string('activitymodules'))); $ADMIN->add('modsettings', new admin_page_managemods()); - foreach ($allplugins['mod'] as $module) { - if (!$module->is_updated()) { - continue; - } - $module->load_settings($ADMIN, 'modsettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('mod') as $plugin) { + /** @var \core\plugininfo\mod $plugin */ + $plugin->load_settings($ADMIN, 'modsettings', $hassiteconfig); } // course formats @@ -25,32 +40,26 @@ if ($hassiteconfig) { $temp = new admin_settingpage('manageformats', new lang_string('manageformats', 'core_admin')); $temp->add(new admin_setting_manageformats()); $ADMIN->add('formatsettings', $temp); - foreach ($allplugins['format'] as $format) { - if (!$format->is_updated()) { - continue; - } - $format->load_settings($ADMIN, 'formatsettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('format') as $plugin) { + /** @var \core\plugininfo\format $plugin */ + $plugin->load_settings($ADMIN, 'formatsettings', $hassiteconfig); } // blocks $ADMIN->add('modules', new admin_category('blocksettings', new lang_string('blocks'))); $ADMIN->add('blocksettings', new admin_page_manageblocks()); - foreach ($allplugins['block'] as $block) { - if (!$block->is_updated()) { - continue; - } - $block->load_settings($ADMIN, 'blocksettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('block') as $plugin) { + /** @var \core\plugininfo\block $plugin */ + $plugin->load_settings($ADMIN, 'blocksettings', $hassiteconfig); } // message outputs $ADMIN->add('modules', new admin_category('messageoutputs', new lang_string('messageoutputs', 'message'))); $ADMIN->add('messageoutputs', new admin_page_managemessageoutputs()); $ADMIN->add('messageoutputs', new admin_page_defaultmessageoutputs()); - foreach ($allplugins['message'] as $processor) { - if (!$processor->is_updated()) { - continue; - } - $processor->load_settings($ADMIN, 'messageoutputs', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('message') as $plugin) { + /** @var \core\plugininfo\message $plugin */ + $plugin->load_settings($ADMIN, 'messageoutputs', $hassiteconfig); } // authentication plugins @@ -78,11 +87,9 @@ if ($hassiteconfig) { $temp->add(new admin_setting_configtext('recaptchaprivatekey', new lang_string('recaptchaprivatekey', 'admin'), new lang_string('configrecaptchaprivatekey', 'admin'), '', PARAM_NOTAGS)); $ADMIN->add('authsettings', $temp); - foreach ($allplugins['auth'] as $auth) { - if (!$auth->is_updated()) { - continue; - } - $auth->load_settings($ADMIN, 'authsettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('auth') as $plugin) { + /** @var \core\plugininfo\auth $plugin */ + $plugin->load_settings($ADMIN, 'authsettings', $hassiteconfig); } // Enrolment plugins @@ -90,11 +97,9 @@ if ($hassiteconfig) { $temp = new admin_settingpage('manageenrols', new lang_string('manageenrols', 'enrol')); $temp->add(new admin_setting_manageenrols()); $ADMIN->add('enrolments', $temp); - foreach($allplugins['enrol'] as $enrol) { - if (!$enrol->is_updated()) { - continue; - } - $enrol->load_settings($ADMIN, 'enrolments', $hassiteconfig); + foreach(core_plugin_manager::instance()->get_plugins_of_type('enrol') as $plugin) { + /** @var \core\plugininfo\enrol $plugin */ + $plugin->load_settings($ADMIN, 'enrolments', $hassiteconfig); } @@ -103,11 +108,9 @@ if ($hassiteconfig) { $temp = new admin_settingpage('manageeditors', new lang_string('editorsettings', 'editor')); $temp->add(new admin_setting_manageeditors()); $ADMIN->add('editorsettings', $temp); - foreach ($allplugins['editor'] as $editor) { - if (!$editor->is_updated()) { - continue; - } - $editor->load_settings($ADMIN, 'editorsettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('editor') as $plugin) { + /** @var \core\plugininfo\editor $plugin */ + $plugin->load_settings($ADMIN, 'editorsettings', $hassiteconfig); } /// License types @@ -168,11 +171,9 @@ if ($hassiteconfig) { } $ADMIN->add('filtersettings', $temp); - foreach ($allplugins['filter'] as $filter) { - if (!$filter->is_updated()) { - continue; - } - $filter->load_settings($ADMIN, 'filtersettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('filter') as $plugin) { + /** @var \core\plugininfo\filter $plugin */ + $plugin->load_settings($ADMIN, 'filtersettings', $hassiteconfig); } @@ -262,11 +263,9 @@ if ($hassiteconfig) { new lang_string('createrepository', 'repository'), $url, 'moodle/site:config', true)); $ADMIN->add('repositorysettings', new admin_externalpage('repositoryinstanceedit', new lang_string('editrepositoryinstance', 'repository'), $url, 'moodle/site:config', true)); - foreach ($allplugins['repository'] as $repositorytype) { - if (!$repositorytype->is_updated()) { - continue; - } - $repositorytype->load_settings($ADMIN, 'repositorysettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('repository') as $plugin) { + /** @var \core\plugininfo\repository $plugin */ + $plugin->load_settings($ADMIN, 'repositorysettings', $hassiteconfig); } /// Web services @@ -314,11 +313,9 @@ if ($hassiteconfig) { 'admin'), new lang_string('configenablewsdocumentation', 'admin', $wsdoclink), false)); $ADMIN->add('webservicesettings', $temp); /// links to protocol pages - foreach ($allplugins['webservice'] as $webservice) { - if (!$webservice->is_updated()) { - continue; - } - $webservice->load_settings($ADMIN, 'webservicesettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('webservice') as $plugin) { + /** @var \core\plugininfo\webservice $plugin */ + $plugin->load_settings($ADMIN, 'webservicesettings', $hassiteconfig); } /// manage token page link $ADMIN->add('webservicesettings', new admin_externalpage('addwebservicetoken', new lang_string('managetokens', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/tokens.php", 'moodle/site:config', true)); @@ -332,10 +329,6 @@ if ($hassiteconfig) { // Question type settings if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) { - if (!$hassiteconfig) { - require_once("$CFG->libdir/pluginlib.php"); - $allplugins = plugin_manager::instance()->get_plugins(); - } // Question behaviour settings. $ADMIN->add('modules', new admin_category('qbehavioursettings', new lang_string('questionbehaviours', 'admin'))); @@ -392,11 +385,9 @@ if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) get_string('responsehistory', 'question'), '', 0, $hiddenofvisible)); // Settings for particular question types. - foreach ($allplugins['qtype'] as $qtype) { - if (!$qtype->is_updated()) { - continue; - } - $qtype->load_settings($ADMIN, 'qtypesettings', $hassiteconfig); + foreach (core_plugin_manager::instance()->get_plugins_of_type('qtype') as $plugin) { + /** @var \core\plugininfo\qtype $plugin */ + $plugin->load_settings($ADMIN, 'qtypesettings', $hassiteconfig); } } @@ -406,10 +397,8 @@ if ($hassiteconfig && !empty($CFG->enableplagiarism)) { $ADMIN->add('plagiarism', new admin_externalpage('manageplagiarismplugins', new lang_string('manageplagiarism', 'plagiarism'), $CFG->wwwroot . '/' . $CFG->admin . '/plagiarism.php')); - foreach ($allplugins['plagiarism'] as $plugin) { - if (!$plugin->is_updated()) { - continue; - } + foreach (core_plugin_manager::instance()->get_plugins_of_type('plagiarism') as $plugin) { + /** @var \core\plugininfo\plagiarism $plugin */ $plugin->load_settings($ADMIN, 'plagiarism', $hassiteconfig); } } @@ -466,12 +455,10 @@ if ($hassiteconfig) { $CFG->wwwroot . '/' . $CFG->admin . '/tools.php')); } -// Now add various admin tools -foreach (core_component::get_plugin_list('tool') as $plugin => $plugindir) { - $settings_path = "$plugindir/settings.php"; - if (file_exists($settings_path)) { - include($settings_path); - } +// Now add various admin tools. +foreach (core_plugin_manager::instance()->get_plugins_of_type('tool') as $plugin) { + /** @var \core\plugininfo\tool $plugin */ + $plugin->load_settings($ADMIN, null, $hassiteconfig); } // Now add the Cache plugins @@ -497,12 +484,9 @@ if ($hassiteconfig) { $CFG->wwwroot . '/' . $CFG->admin . '/localplugins.php')); } -// extend settings for each local plugin. Note that their settings may be in any part of the -// settings tree and may be visible not only for administrators. We can not use $allplugins here -foreach (core_component::get_plugin_list('local') as $plugin => $plugindir) { - $settings_path = "$plugindir/settings.php"; - if (file_exists($settings_path)) { - include($settings_path); - continue; - } +// Extend settings for each local plugin. Note that their settings may be in any part of the +// settings tree and may be visible not only for administrators. +foreach (core_plugin_manager::instance()->get_plugins_of_type('local') as $plugin) { + /** @var \core\plugininfo\local $plugin */ + $plugin->load_settings($ADMIN, null, $hassiteconfig); } diff --git a/admin/tool/behat/cli/util.php b/admin/tool/behat/cli/util.php index ab41e3d6ad3..287a53562e4 100644 --- a/admin/tool/behat/cli/util.php +++ b/admin/tool/behat/cli/util.php @@ -164,7 +164,6 @@ require("$CFG->dirroot/lib/setup.php"); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/upgradelib.php'); require_once($CFG->libdir.'/clilib.php'); -require_once($CFG->libdir.'/pluginlib.php'); require_once($CFG->libdir.'/installlib.php'); require_once($CFG->libdir.'/testing/classes/test_lock.php'); diff --git a/admin/tool/installaddon/classes/installer.php b/admin/tool/installaddon/classes/installer.php index 37c36007c3f..1a1866c7e40 100644 --- a/admin/tool/installaddon/classes/installer.php +++ b/admin/tool/installaddon/classes/installer.php @@ -158,9 +158,8 @@ class tool_installaddon_installer { */ public function get_plugin_types_menu() { global $CFG; - require_once($CFG->libdir.'/pluginlib.php'); - $pluginman = plugin_manager::instance(); + $pluginman = core_plugin_manager::instance(); $menu = array('' => get_string('choosedots')); foreach (array_keys($pluginman->get_plugin_types()) as $plugintype) { diff --git a/admin/tool/phpunit/cli/util.php b/admin/tool/phpunit/cli/util.php index be593a27e7e..13dcacc03fe 100644 --- a/admin/tool/phpunit/cli/util.php +++ b/admin/tool/phpunit/cli/util.php @@ -90,7 +90,6 @@ require(__DIR__ . '/../../../../lib/phpunit/bootstrap.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/upgradelib.php'); require_once($CFG->libdir.'/clilib.php'); -require_once($CFG->libdir.'/pluginlib.php'); require_once($CFG->libdir.'/installlib.php'); if ($unrecognized) { diff --git a/admin/tools.php b/admin/tools.php index 5f6a4686f49..d4223b6bd88 100644 --- a/admin/tools.php +++ b/admin/tools.php @@ -45,7 +45,7 @@ $table->define_columns(array('name', 'version', 'uninstall')); $table->define_headers(array(get_string('plugin'), get_string('version'), $struninstall)); $table->define_baseurl($PAGE->url); $table->set_attribute('id', 'toolplugins'); -$table->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide'); +$table->set_attribute('class', 'admintable generaltable'); $table->setup(); $plugins = array(); @@ -73,7 +73,7 @@ foreach ($installed as $config) { foreach ($plugins as $plugin => $name) { $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('tool_'.$plugin)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('tool_'.$plugin, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } diff --git a/auth/cas/version.php b/auth/cas/version.php index b18fa1acdf8..31559583069 100644 --- a/auth/cas/version.php +++ b/auth/cas/version.php @@ -29,3 +29,5 @@ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2013052100; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2013050100; // Requires this Moodle version $plugin->component = 'auth_cas'; // Full name of the plugin (used for diagnostics) + +$plugin->dependencies = array('auth_ldap' => 2013052100); diff --git a/blocks/feedback/version.php b/blocks/feedback/version.php index 9636ae7ebab..8af781e85cf 100644 --- a/blocks/feedback/version.php +++ b/blocks/feedback/version.php @@ -28,3 +28,5 @@ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2013050100; // Requires this Moodle version $plugin->component = 'block_feedback'; // Full name of the plugin (used for diagnostics) + +$plugin->dependencies = array('mod_feedback' => 2013050100); diff --git a/blocks/glossary_random/version.php b/blocks/glossary_random/version.php index 1348e96b281..dd5d0e2000b 100644 --- a/blocks/glossary_random/version.php +++ b/blocks/glossary_random/version.php @@ -28,3 +28,5 @@ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2013050100; // Requires this Moodle version $plugin->component = 'block_glossary_random'; // Full name of the plugin (used for diagnostics) + +$plugin->dependencies = array('mod_glossary' => 2013050100); diff --git a/blocks/quiz_results/version.php b/blocks/quiz_results/version.php index 0ed1df9a28e..c16b985a631 100644 --- a/blocks/quiz_results/version.php +++ b/blocks/quiz_results/version.php @@ -28,3 +28,5 @@ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2013050100; // Requires this Moodle version $plugin->component = 'block_quiz_results'; // Full name of the plugin (used for diagnostics) + +$plugin->dependencies = array('mod_quiz' => 2013100200); diff --git a/blocks/search_forums/version.php b/blocks/search_forums/version.php index 5159923fd2c..32317cebda6 100644 --- a/blocks/search_forums/version.php +++ b/blocks/search_forums/version.php @@ -28,3 +28,5 @@ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2013050100; // Requires this Moodle version $plugin->component = 'block_search_forums'; // Full name of the plugin (used for diagnostics) + +$plugin->dependencies = array('mod_forum' => 2013092600); diff --git a/filter/manage.php b/filter/manage.php index 15b493cde35..ff6104b0ec8 100644 --- a/filter/manage.php +++ b/filter/manage.php @@ -25,7 +25,6 @@ require_once(dirname(__FILE__) . '/../config.php'); require_once($CFG->libdir . '/adminlib.php'); -require_once($CFG->libdir . '/pluginlib.php'); $contextid = required_param('contextid',PARAM_INT); $forfilter = optional_param('filter', '', PARAM_SAFEDIR); diff --git a/lang/en/question.php b/lang/en/question.php index 50f6dc3d911..86eab4c5bd1 100644 --- a/lang/en/question.php +++ b/lang/en/question.php @@ -36,8 +36,6 @@ $string['cannotcreate'] = 'Could not create new entry in question_attempts table $string['cannotcreatepath'] = 'Cannot create path: {$a}'; $string['cannotdeletebehaviourinuse'] = 'You cannot delete the behaviour \'{$a}\'. It is used by question attempts.'; $string['cannotdeletecate'] = 'You can\'t delete that category it is the default category for this context.'; -$string['cannotdeletemissingbehaviour'] = 'You cannot uninstall the missing behaviour. It is required by the system.'; -$string['cannotdeletemissingqtype'] = 'You cannot uninstall the missing question type. It is needed by the system.'; $string['cannotdeleteneededbehaviour'] = 'Cannot delete the question behaviour \'{$a}\'. There are other behaviours installed that rely on it.'; $string['cannotdeleteqtypeinuse'] = 'You cannot delete the question type \'{$a}\'. There are questions of this type in the question bank.'; $string['cannotdeleteqtypeneeded'] = 'You cannot delete the question type \'{$a}\'. There are other question types installed that rely on it.'; @@ -92,11 +90,7 @@ $string['cwrqpfsnoprob'] = 'No question categories in your site are affected by $string['defaultfor'] = 'Default for {$a}'; $string['defaultinfofor'] = 'The default category for questions shared in context \'{$a}\'.'; $string['defaultmarkmustbepositive'] = 'The default mark must be positive.'; -$string['deletebehaviourareyousure'] = 'Delete behaviour {$a}: are you sure?'; -$string['deletebehaviourareyousuremessage'] = 'You are about to completely delete the question behaviour {$a}. This will completely delete everything in the database associated with this question behaviour. Are you SURE you want to continue?'; $string['deletecoursecategorywithquestions'] = 'There are questions in the question bank associated with this course category. If you proceed, they will be deleted. You may wish to move them first, using the question bank interface.'; -$string['deleteqtypeareyousure'] = 'Delete question type {$a}: are you sure?'; -$string['deleteqtypeareyousuremessage'] = 'You are about to completely delete the question type {$a}. This will completely delete everything in the database associated with this question type. Are you SURE you want to continue?'; $string['deletequestioncheck'] = 'Are you absolutely sure you want to delete \'{$a}\'?'; $string['deletequestionscheck'] = 'Are you absolutely sure you want to delete the following questions?

{$a}'; $string['deletingbehaviour'] = 'Deleting question behaviour \'{$a}\''; @@ -254,8 +248,6 @@ $string['permissionmove'] = 'Move this question'; $string['permissionsaveasnew'] = 'Save this as a new question'; $string['permissionto'] = 'You have permission to :'; $string['published'] = 'shared'; -$string['qbehaviourdeletefiles'] = 'All data associated with the question behaviour \'{$a->behaviour}\' has been deleted from the database. To complete the deletion (and to prevent the behaviour from re-installing itself), you should now delete this directory from your server: {$a->directory}'; -$string['qtypedeletefiles'] = 'All data associated with the question type \'{$a->qtype}\' has been deleted from the database. To complete the deletion (and to prevent the question type from re-installing itself), you should now delete this directory from your server: {$a->directory}'; $string['qtypeveryshort'] = 'T'; $string['questionaffected'] = 'Question "{$a->name}" ({$a->qtype}) is in this question category but is also being used in quiz "{$a->quizname}" in another course "{$a->coursename}".'; $string['questionbank'] = 'Question bank'; diff --git a/lib/adminlib.php b/lib/adminlib.php index 857d3d1dd21..278e8023b14 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -124,7 +124,6 @@ define('INSECURE_DATAROOT_ERROR', 2); */ function uninstall_plugin($type, $name) { global $CFG, $DB, $OUTPUT; - require_once($CFG->libdir.'/pluginlib.php'); // This may take a long time. @set_time_limit(0); @@ -180,10 +179,10 @@ function uninstall_plugin($type, $name) { } // Specific plugin type cleanup. - $plugininfo = plugin_manager::instance()->get_plugin_info($component); + $plugininfo = core_plugin_manager::instance()->get_plugin_info($component); if ($plugininfo) { $plugininfo->uninstall_cleanup(); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } $plugininfo = null; @@ -5048,7 +5047,7 @@ class admin_setting_manageenrols extends admin_setting { $strusage = get_string('enrolusage', 'enrol'); $strversion = get_string('version'); - $pluginmanager = plugin_manager::instance(); + $pluginmanager = core_plugin_manager::instance(); $enrols_available = enrol_get_plugins(false); $active_enrols = enrol_get_plugins(true); @@ -5101,18 +5100,20 @@ class admin_setting_manageenrols extends admin_setting { $usage = "$ci / $cp"; // Hide/show links. + $class = ''; if (isset($active_enrols[$enrol])) { $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol)); $hideshow = ""; $hideshow .= "pix_url('t/hide') . "\" class=\"iconsmall\" alt=\"$strdisable\" />"; $enabled = true; - $displayname = "$name"; + $displayname = $name; } else if (isset($enrols_available[$enrol])) { $aurl = new moodle_url($url, array('action'=>'enable', 'enrol'=>$enrol)); $hideshow = ""; $hideshow .= "pix_url('t/show') . "\" class=\"iconsmall\" alt=\"$strenable\" />"; $enabled = false; - $displayname = "$name"; + $displayname = $name; + $class = 'dimmed_text'; } else { $hideshow = ''; $enabled = false; @@ -5155,12 +5156,16 @@ class admin_setting_manageenrols extends admin_setting { // Add uninstall info. $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } // Add a row to the table. - $table->data[] = array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $uninstall); + $row = new html_table_row(array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $uninstall)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; $printed[$enrol] = true; } @@ -5570,14 +5575,14 @@ class admin_setting_manageauths extends admin_setting { * @return string highlight */ public function output_html($data, $query='') { - global $CFG, $OUTPUT; - + global $CFG, $OUTPUT, $DB; // display strings $txt = get_strings(array('authenticationplugins', 'users', 'administration', 'settings', 'edit', 'name', 'enable', 'disable', - 'up', 'down', 'none')); + 'up', 'down', 'none', 'users')); $txt->updown = "$txt->up/$txt->down"; + $txt->uninstall = get_string('uninstallplugin', 'core_admin'); $authsavailable = core_component::get_plugin_list('auth'); get_enabled_auth_plugins(true); // fix the list of enabled auths @@ -5620,20 +5625,22 @@ class admin_setting_manageauths extends admin_setting { $return .= $OUTPUT->box_start('generalbox authsui'); $table = new html_table(); - $table->head = array($txt->name, $txt->enable, $txt->updown, $txt->settings); - $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign'); + $table->head = array($txt->name, $txt->users, $txt->enable, $txt->updown, $txt->settings, $txt->uninstall); + $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign'); $table->data = array(); $table->attributes['class'] = 'admintable generaltable'; $table->id = 'manageauthtable'; //add always enabled plugins first - $displayname = "".$displayauths['manual'].""; + $displayname = $displayauths['manual']; $settings = "{$txt->settings}"; //$settings = "{$txt->settings}"; - $table->data[] = array($displayname, '', '', $settings); - $displayname = "".$displayauths['nologin'].""; + $usercount = $DB->count_records('user', array('auth'=>'manual', 'deleted'=>0)); + $table->data[] = array($displayname, $usercount, '', '', $settings, ''); + $displayname = $displayauths['nologin']; $settings = "{$txt->settings}"; - $table->data[] = array($displayname, '', '', $settings); + $usercount = $DB->count_records('user', array('auth'=>'nologin', 'deleted'=>0)); + $table->data[] = array($displayname, $usercount, '', '', $settings, ''); // iterate through auth plugins and add to the display table @@ -5644,22 +5651,26 @@ class admin_setting_manageauths extends admin_setting { if ($auth == 'manual' or $auth == 'nologin') { continue; } + $class = ''; // hide/show link if (in_array($auth, $authsenabled)) { $hideshow = ""; $hideshow .= "pix_url('t/hide') . "\" class=\"iconsmall\" alt=\"disable\" />"; // $hideshow = ""; $enabled = true; - $displayname = "$name"; + $displayname = $name; } else { $hideshow = ""; $hideshow .= "pix_url('t/show') . "\" class=\"iconsmall\" alt=\"enable\" />"; // $hideshow = ""; $enabled = false; - $displayname = "$name"; + $displayname = $name; + $class = 'dimmed_text'; } + $usercount = $DB->count_records('user', array('auth'=>$auth, 'deleted'=>0)); + // up/down link (only if auth is enabled) $updown = ''; if ($enabled) { @@ -5687,8 +5698,18 @@ class admin_setting_manageauths extends admin_setting { $settings = "{$txt->settings}"; } - // add a row to the table - $table->data[] =array($displayname, $hideshow, $updown, $settings); + // Uninstall link. + $uninstall = ''; + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('auth_'.$auth, 'manage')) { + $uninstall = html_writer::link($uninstallurl, $txt->uninstall); + } + + // Add a row to the table. + $row = new html_table_row(array($displayname, $usercount, $hideshow, $updown, $settings, $uninstall)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; } $return .= html_writer::table($table); $return .= get_string('configauthenticationplugins', 'admin').'
'.get_string('tablenosave', 'filters'); @@ -5802,7 +5823,7 @@ class admin_setting_manageeditors extends admin_setting { $table = new html_table(); $table->head = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall); - $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign'); + $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign'); $table->id = 'editormanagement'; $table->attributes['class'] = 'admintable generaltable'; $table->data = array(); @@ -5813,19 +5834,21 @@ class admin_setting_manageeditors extends admin_setting { $url = "editors.php?sesskey=" . sesskey(); foreach ($editors_available as $editor => $name) { // hide/show link + $class = ''; if (in_array($editor, $active_editors)) { $hideshow = ""; $hideshow .= "pix_url('t/hide') . "\" class=\"iconsmall\" alt=\"disable\" />"; // $hideshow = ""; $enabled = true; - $displayname = "$name"; + $displayname = $name; } else { $hideshow = ""; $hideshow .= "pix_url('t/show') . "\" class=\"iconsmall\" alt=\"enable\" />"; // $hideshow = ""; $enabled = false; - $displayname = "$name"; + $displayname = $name; + $class = 'dimmed_text'; } // up/down link (only if auth is enabled) @@ -5857,12 +5880,16 @@ class admin_setting_manageeditors extends admin_setting { } $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('editor_'.$editor)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('editor_'.$editor, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } - // add a row to the table - $table->data[] =array($displayname, $hideshow, $updown, $settings, $uninstall); + // Add a row to the table. + $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; } $return .= html_writer::table($table); $return .= get_string('configeditorplugins', 'editor').'
'.get_string('tablenosave', 'admin'); @@ -6019,7 +6046,7 @@ class admin_setting_manageformats extends admin_setting { if (parent::is_related($query)) { return true; } - $formats = plugin_manager::instance()->get_plugins_of_type('format'); + $formats = core_plugin_manager::instance()->get_plugins_of_type('format'); foreach ($formats as $format) { if (strpos($format->component, $query) !== false || strpos(core_text::strtolower($format->displayname), $query) !== false) { @@ -6042,7 +6069,7 @@ class admin_setting_manageformats extends admin_setting { $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main'); $return .= $OUTPUT->box_start('generalbox formatsui'); - $formats = plugin_manager::instance()->get_plugins_of_type('format'); + $formats = core_plugin_manager::instance()->get_plugins_of_type('format'); // display strings $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default')); @@ -6052,8 +6079,7 @@ class admin_setting_manageformats extends admin_setting { $table = new html_table(); $table->head = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings); $table->align = array('left', 'center', 'center', 'center', 'center'); - $table->width = '90%'; - $table->attributes['class'] = 'manageformattable generaltable'; + $table->attributes['class'] = 'manageformattable generaltable admintable'; $table->data = array(); $cnt = 0; @@ -6063,8 +6089,9 @@ class admin_setting_manageformats extends admin_setting { $url = new moodle_url('/admin/courseformats.php', array('sesskey' => sesskey(), 'format' => $format->name)); $isdefault = ''; + $class = ''; if ($format->is_enabled()) { - $strformatname = html_writer::tag('span', $format->displayname); + $strformatname = $format->displayname; if ($defaultformat === $format->name) { $hideshow = $txt->default; } else { @@ -6072,7 +6099,8 @@ class admin_setting_manageformats extends admin_setting { $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall'))); } } else { - $strformatname = html_writer::tag('span', $format->displayname, array('class' => 'dimmed_text')); + $strformatname = $format->displayname; + $class = 'dimmed_text'; $hideshow = html_writer::link($url->out(false, array('action' => 'enable')), $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall'))); } @@ -6095,10 +6123,14 @@ class admin_setting_manageformats extends admin_setting { $settings = html_writer::link($format->get_settings_url(), $txt->settings); } $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('format_'.$format->name)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('format_'.$format->name, 'manage')) { $uninstall = html_writer::link($uninstallurl, $txt->uninstall); } - $table->data[] =array($strformatname, $hideshow, $updown, $uninstall, $settings); + $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; } $return .= html_writer::table($table); $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings')); @@ -6324,10 +6356,9 @@ function admin_get_root($reload=false, $requirefulltree=true) { */ function admin_apply_default_settings($node=NULL, $unconditional=true) { global $CFG; - require_once($CFG->libdir.'/pluginlib.php'); if (is_null($node)) { - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); $node = admin_get_root(true, true); } @@ -6353,7 +6384,7 @@ function admin_apply_default_settings($node=NULL, $unconditional=true) { } } // Just in case somebody modifies the list of active plugins directly. - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } /** diff --git a/lib/classes/component.php b/lib/classes/component.php index e193399b316..3ec6026ffa8 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -53,6 +53,10 @@ class core_component { protected static $plugins = null; /** @var null cache of core subsystems */ protected static $subsystems = null; + /** @var null subplugin type parents */ + protected static $parents = null; + /** @var null subplugins */ + protected static $subplugins = null; /** @var null list of all known classes that can be autoloaded */ protected static $classmap = null; /** @var null list of some known files that can be included. */ @@ -124,6 +128,8 @@ class core_component { self::$plugintypes = $cache['plugintypes']; self::$plugins = $cache['plugins']; self::$subsystems = $cache['subsystems']; + self::$parents = $cache['parents']; + self::$subplugins = $cache['subplugins']; self::$classmap = $cache['classmap']; self::$filemap = $cache['filemap']; return; @@ -161,6 +167,8 @@ class core_component { self::$plugintypes = $cache['plugintypes']; self::$plugins = $cache['plugins']; self::$subsystems = $cache['subsystems']; + self::$parents = $cache['parents']; + self::$subplugins = $cache['subplugins']; self::$classmap = $cache['classmap']; self::$filemap = $cache['filemap']; return; @@ -244,6 +252,8 @@ class core_component { 'subsystems' => self::$subsystems, 'plugintypes' => self::$plugintypes, 'plugins' => self::$plugins, + 'parents' => self::$parents, + 'subplugins' => self::$subplugins, 'classmap' => self::$classmap, 'filemap' => self::$filemap, 'version' => self::$version, @@ -260,7 +270,7 @@ $cache = '.var_export($cache, true).'; protected static function fill_all_caches() { self::$subsystems = self::fetch_subsystems(); - self::$plugintypes = self::fetch_plugintypes(); + list(self::$plugintypes, self::$parents, self::$subplugins) = self::fetch_plugintypes(); self::$plugins = array(); foreach (self::$plugintypes as $type => $fulldir) { @@ -403,8 +413,9 @@ $cache = '.var_export($cache, true).'; 'tool' => $CFG->dirroot.'/'.$CFG->admin.'/tool', 'cachestore' => $CFG->dirroot.'/cache/stores', 'cachelock' => $CFG->dirroot.'/cache/locks', - ); + $parents = array(); + $subplugins = array(); if (!empty($CFG->themedir) and is_dir($CFG->themedir) ) { $types['theme'] = $CFG->themedir; @@ -417,66 +428,80 @@ $cache = '.var_export($cache, true).'; // Local subplugins must be after local plugins. continue; } - $subplugins = self::fetch_subplugins($type, $types[$type]); - foreach($subplugins as $subtype => $subplugin) { - if (isset($types[$subtype])) { - error_log("Invalid subtype '$subtype', duplicate detected."); + $plugins = self::fetch_plugins($type, $types[$type]); + foreach ($plugins as $plugin => $fulldir) { + $subtypes = self::fetch_subtypes($fulldir); + if (!$subtypes) { continue; } - $types[$subtype] = $subplugin; + $subplugins[$type.'_'.$plugin] = array(); + foreach($subtypes as $subtype => $subdir) { + if (isset($types[$subtype])) { + error_log("Invalid subtype '$subtype', duplicate detected."); + continue; + } + $types[$subtype] = $subdir; + $parents[$subtype] = $type.'_'.$plugin; + $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self::fetch_plugins($subtype, $subdir)); + } } } - // Local is always last! $types['local'] = $CFG->dirroot.'/local'; if (in_array('local', self::$supportsubplugins)) { - $subplugins = self::fetch_subplugins('local', $types['local']); - foreach($subplugins as $subtype => $subplugin) { - if (isset($types[$subtype])) { - error_log("Invalid subtype '$subtype', duplicate detected."); + $type = 'local'; + $plugins = self::fetch_plugins($type, $types[$type]); + foreach ($plugins as $plugin => $fulldir) { + $subtypes = self::fetch_subtypes($fulldir); + if (!$subtypes) { continue; } - $types[$subtype] = $subplugin; + $subplugins[$type.'_'.$plugin] = array(); + foreach($subtypes as $subtype => $subdir) { + if (isset($types[$subtype])) { + error_log("Invalid subtype '$subtype', duplicate detected."); + continue; + } + $types[$subtype] = $subdir; + $parents[$subtype] = $type.'_'.$plugin; + $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self::fetch_plugins($subtype, $subdir)); + } } } - return $types; + return array($types, $parents, $subplugins); } /** - * Returns list of subtypes defined in given plugin type. - * @param string $type - * @param string $fulldir + * Returns list of subtypes. + * @param string $ownerdir * @return array */ - protected static function fetch_subplugins($type, $fulldir) { + protected static function fetch_subtypes($ownerdir) { global $CFG; $types = array(); - $subpluginowners = self::fetch_plugins($type, $fulldir); - foreach ($subpluginowners as $ownerdir) { - if (file_exists("$ownerdir/db/subplugins.php")) { - $subplugins = array(); - include("$ownerdir/db/subplugins.php"); - foreach ($subplugins as $subtype => $dir) { - if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) { - error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present."); - continue; - } - if (isset(self::$subsystems[$subtype])) { - error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem."); - continue; - } - if ($CFG->admin !== 'admin' and strpos($dir, 'admin/') === 0) { - $dir = preg_replace('|^admin/|', "$CFG->admin/", $dir); - } - if (!is_dir("$CFG->dirroot/$dir")) { - error_log("Invalid subtype directory '$dir' detected in '$ownerdir'."); - continue; - } - $types[$subtype] = "$CFG->dirroot/$dir"; + if (file_exists("$ownerdir/db/subplugins.php")) { + $subplugins = array(); + include("$ownerdir/db/subplugins.php"); + foreach ($subplugins as $subtype => $dir) { + if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) { + error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present."); + continue; } + if (isset(self::$subsystems[$subtype])) { + error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem."); + continue; + } + if ($CFG->admin !== 'admin' and strpos($dir, 'admin/') === 0) { + $dir = preg_replace('|^admin/|', "$CFG->admin/", $dir); + } + if (!is_dir("$CFG->dirroot/$dir")) { + error_log("Invalid subtype directory '$dir' detected in '$ownerdir'."); + continue; + } + $types[$subtype] = "$CFG->dirroot/$dir"; } } return $types; @@ -894,6 +919,37 @@ $cache = '.var_export($cache, true).'; return $return; } + /** + * Returns parent of this subplugin type. + * + * @param string $type + * @return string parent component or null + */ + public static function get_subtype_parent($type) { + self::init(); + + if (isset(self::$parents[$type])) { + return self::$parents[$type]; + } + + return null; + } + + /** + * Return all subplugins of this component. + * @param string $component. + * @return array $subtype=>array($component, ..), null if no subtypes defined + */ + public static function get_subplugins($component) { + self::init(); + + if (isset(self::$subplugins[$component])) { + return self::$subplugins[$component]; + } + + return null; + } + /** * Returns hash of all versions including core and all plugins. * diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php new file mode 100644 index 00000000000..929b503fce3 --- /dev/null +++ b/lib/classes/plugin_manager.php @@ -0,0 +1,1262 @@ +. + +/** + * Defines classes used for plugins management + * + * This library provides a unified interface to various plugin types in + * Moodle. It is mainly used by the plugins management admin page and the + * plugins check page during the upgrade. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Singleton class providing general plugins management functionality. + */ +class core_plugin_manager { + + /** the plugin is shipped with standard Moodle distribution */ + const PLUGIN_SOURCE_STANDARD = 'std'; + /** the plugin is added extension */ + const PLUGIN_SOURCE_EXTENSION = 'ext'; + + /** the plugin uses neither database nor capabilities, no versions */ + const PLUGIN_STATUS_NODB = 'nodb'; + /** the plugin is up-to-date */ + const PLUGIN_STATUS_UPTODATE = 'uptodate'; + /** the plugin is about to be installed */ + const PLUGIN_STATUS_NEW = 'new'; + /** the plugin is about to be upgraded */ + const PLUGIN_STATUS_UPGRADE = 'upgrade'; + /** the standard plugin is about to be deleted */ + const PLUGIN_STATUS_DELETE = 'delete'; + /** the version at the disk is lower than the one already installed */ + const PLUGIN_STATUS_DOWNGRADE = 'downgrade'; + /** the plugin is installed but missing from disk */ + const PLUGIN_STATUS_MISSING = 'missing'; + + /** @var core_plugin_manager holds the singleton instance */ + protected static $singletoninstance; + /** @var array of raw plugins information */ + protected $pluginsinfo = null; + /** @var array of raw subplugins information */ + protected $subpluginsinfo = null; + /** @var array list of installed plugins $name=>$version */ + protected $installedplugins = null; + /** @var array list of all enabled plugins $name=>$name */ + protected $enabledplugins = null; + /** @var array list of all enabled plugins $name=>$diskversion */ + protected $presentplugins = null; + /** @var array reordered list of plugin types */ + protected $plugintypes = null; + + /** + * Direct initiation not allowed, use the factory method {@link self::instance()} + */ + protected function __construct() { + } + + /** + * Sorry, this is singleton + */ + protected function __clone() { + } + + /** + * Factory method for this class + * + * @return core_plugin_manager the singleton instance + */ + public static function instance() { + if (is_null(self::$singletoninstance)) { + self::$singletoninstance = new self(); + } + return self::$singletoninstance; + } + + /** + * Reset all caches. + * @param bool $phpunitreset + */ + public static function reset_caches($phpunitreset = false) { + if ($phpunitreset) { + self::$singletoninstance = null; + } else { + if (self::$singletoninstance) { + self::$singletoninstance->pluginsinfo = null; + self::$singletoninstance->subpluginsinfo = null; + self::$singletoninstance->installedplugins = null; + self::$singletoninstance->enabledplugins = null; + self::$singletoninstance->presentplugins = null; + self::$singletoninstance->plugintypes = null; + } + } + $cache = cache::make('core', 'plugin_manager'); + $cache->purge(); + } + + /** + * Returns the result of {@link core_component::get_plugin_types()} ordered for humans + * + * @see self::reorder_plugin_types() + * @return array (string)name => (string)location + */ + public function get_plugin_types() { + if (func_num_args() > 0) { + if (!func_get_arg(0)) { + throw coding_exception('core_plugin_manager->get_plugin_types() does not support relative paths.'); + } + } + if ($this->plugintypes) { + return $this->plugintypes; + } + + $this->plugintypes = $this->reorder_plugin_types(core_component::get_plugin_types()); + return $this->plugintypes; + } + + /** + * Load list of installed plugins, + * always call before using $this->installedplugins. + * + * This method is caching results for all plugins. + */ + protected function load_installed_plugins() { + global $DB, $CFG; + + if ($this->installedplugins) { + return; + } + + if (empty($CFG->version)) { + // Nothing installed yet. + $this->installedplugins = array(); + return; + } + + $cache = cache::make('core', 'plugin_manager'); + $installed = $cache->get('installed'); + + if (is_array($installed)) { + $this->installedplugins = $installed; + return; + } + + $this->installedplugins = array(); + + if ($CFG->version < 2013092001.02) { + // We did not upgrade the database yet. + $modules = $DB->get_records('modules', array(), 'name ASC', 'id, name, version'); + foreach ($modules as $module) { + $this->installedplugins['mod'][$module->name] = $module->version; + } + $blocks = $DB->get_records('block', array(), 'name ASC', 'id, name, version'); + foreach ($blocks as $block) { + $this->installedplugins['block'][$block->name] = $block->version; + } + } + + $versions = $DB->get_records('config_plugins', array('name'=>'version')); + foreach ($versions as $version) { + $parts = explode('_', $version->plugin, 2); + if (!isset($parts[1])) { + // Invalid component, there must be at least one "_". + continue; + } + // Do not verify here if plugin type and name are valid. + $this->installedplugins[$parts[0]][$parts[1]] = $version->value; + } + + foreach ($this->installedplugins as $key => $value) { + ksort($this->installedplugins[$key]); + } + + $cache->set('installed', $this->installedplugins); + } + + /** + * Return list of installed plugins of given type. + * @param string $type + * @return array $name=>$version + */ + public function get_installed_plugins($type) { + $this->load_installed_plugins(); + if (isset($this->installedplugins[$type])) { + return $this->installedplugins[$type]; + } + return array(); + } + + /** + * Load list of all enabled plugins, + * call before using $this->enabledplugins. + * + * This method is caching results from individual plugin info classes. + */ + protected function load_enabled_plugins() { + global $CFG; + + if ($this->enabledplugins) { + return; + } + + if (empty($CFG->version)) { + $this->enabledplugins = array(); + return; + } + + $cache = cache::make('core', 'plugin_manager'); + $enabled = $cache->get('enabled'); + + if (is_array($enabled)) { + $this->enabledplugins = $enabled; + return; + } + + $this->enabledplugins = array(); + + require_once($CFG->libdir.'/adminlib.php'); + + $plugintypes = core_component::get_plugin_types(); + foreach ($plugintypes as $plugintype => $fulldir) { + $plugininfoclass = self::resolve_plugininfo_class($plugintype); + if (class_exists($plugininfoclass)) { + $enabled = $plugininfoclass::get_enabled_plugins(); + if (!is_array($enabled)) { + continue; + } + $this->enabledplugins[$plugintype] = $enabled; + } + } + + $cache->set('enabled', $this->enabledplugins); + } + + /** + * Get list of enabled plugins of given type, + * the result may contain missing plugins. + * + * @param string $type + * @return array|null list of enabled plugins of this type, null if unknown + */ + public function get_enabled_plugins($type) { + $this->load_enabled_plugins(); + if (isset($this->enabledplugins[$type])) { + return $this->enabledplugins[$type]; + } + return null; + } + + /** + * Load list of all present plugins - call before using $this->presentplugins. + */ + protected function load_present_plugins() { + if ($this->presentplugins) { + return; + } + + $cache = cache::make('core', 'plugin_manager'); + $present = $cache->get('present'); + + if (is_array($present)) { + $this->presentplugins = $present; + return; + } + + $this->presentplugins = array(); + + $plugintypes = core_component::get_plugin_types(); + foreach ($plugintypes as $type => $typedir) { + $plugs = core_component::get_plugin_list($type); + foreach ($plugs as $plug => $fullplug) { + $plugin = new stdClass(); + $plugin->version = null; + $module = $plugin; + @include($fullplug.'/version.php'); + $this->presentplugins[$type][$plug] = $plugin; + } + } + + $cache->set('present', $this->presentplugins); + } + + /** + * Get list of present plugins of given type. + * + * @param string $type + * @return array|null list of presnet plugins $name=>$diskversion, null if unknown + */ + public function get_present_plugins($type) { + $this->load_present_plugins(); + if (isset($this->presentplugins[$type])) { + return $this->presentplugins[$type]; + } + return null; + } + + /** + * Returns a tree of known plugins and information about them + * + * @return array 2D array. The first keys are plugin type names (e.g. qtype); + * the second keys are the plugin local name (e.g. multichoice); and + * the values are the corresponding objects extending {@link \core\plugininfo\base} + */ + public function get_plugins() { + $this->init_pluginsinfo_property(); + + // Make sure all types are initialised. + foreach ($this->pluginsinfo as $plugintype => $list) { + if ($list === null) { + $this->get_plugins_of_type($plugintype); + } + } + + return $this->pluginsinfo; + } + + /** + * Returns list of known plugins of the given type. + * + * This method returns the subset of the tree returned by {@link self::get_plugins()}. + * If the given type is not known, empty array is returned. + * + * @param string $type plugin type, e.g. 'mod' or 'workshopallocation' + * @return \core\plugininfo\base[] (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link \core\plugininfo\base} + */ + public function get_plugins_of_type($type) { + global $CFG; + + $this->init_pluginsinfo_property(); + + if (!array_key_exists($type, $this->pluginsinfo)) { + return array(); + } + + if (is_array($this->pluginsinfo[$type])) { + return $this->pluginsinfo[$type]; + } + + $types = core_component::get_plugin_types(); + + /** @var \core\plugininfo\base $plugintypeclass */ + $plugintypeclass = self::resolve_plugininfo_class($type); + $plugins = $plugintypeclass::get_plugins($type, $types[$type], $plugintypeclass); + $this->pluginsinfo[$type] = $plugins; + + if (empty($CFG->disableupdatenotifications) and !during_initial_install()) { + // Append the information about available updates provided by {@link \core\update\checker()}. + $provider = \core\update\checker::instance(); + foreach ($plugins as $plugininfoholder) { + $plugininfoholder->check_available_updates($provider); + } + } + + return $this->pluginsinfo[$type]; + } + + /** + * Init placeholder array for plugin infos. + */ + protected function init_pluginsinfo_property() { + if (is_array($this->pluginsinfo)) { + return; + } + $this->pluginsinfo = array(); + + $plugintypes = $this->get_plugin_types(); + + foreach ($plugintypes as $plugintype => $plugintyperootdir) { + $this->pluginsinfo[$plugintype] = null; + } + } + + /** + * Find the plugin info class for given type. + * + * @param string $type + * @return string name of pluginfo class for give plugin type + */ + public static function resolve_plugininfo_class($type) { + $parent = core_component::get_subtype_parent($type); + + if ($parent) { + $class = '\\'.$parent.'\plugininfo\\' . $type; + if (class_exists($class)) { + $plugintypeclass = $class; + } else { + if ($dir = core_component::get_component_directory($parent)) { + // BC only - use namespace instead! + if (file_exists("$dir/adminlib.php")) { + global $CFG; + include_once("$dir/adminlib.php"); + } + if (class_exists('plugininfo_' . $type)) { + $plugintypeclass = 'plugininfo_' . $type; + debugging('Class "'.$plugintypeclass.'" is deprecated, migrate to "'.$class.'"', DEBUG_DEVELOPER); + } else { + debugging('Subplugin type "'.$type.'" should define class "'.$class.'"', DEBUG_DEVELOPER); + $plugintypeclass = '\core\plugininfo\general'; + } + } else { + $plugintypeclass = '\core\plugininfo\general'; + } + } + } else { + $class = '\core\plugininfo\\' . $type; + if (class_exists($class)) { + $plugintypeclass = $class; + } else { + debugging('All standard types including "'.$type.'" should have plugininfo class!', DEBUG_DEVELOPER); + $plugintypeclass = '\core\plugininfo\general'; + } + } + + if (!in_array('core\plugininfo\base', class_parents($plugintypeclass))) { + throw new coding_exception('Class ' . $plugintypeclass . ' must extend \core\plugininfo\base'); + } + + return $plugintypeclass; + } + + /** + * Returns list of all known subplugins of the given plugin. + * + * For plugins that do not provide subplugins (i.e. there is no support for it), + * empty array is returned. + * + * @param string $component full component name, e.g. 'mod_workshop' + * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link \core\plugininfo\base} + */ + public function get_subplugins_of_plugin($component) { + + $pluginfo = $this->get_plugin_info($component); + + if (is_null($pluginfo)) { + return array(); + } + + $subplugins = $this->get_subplugins(); + + if (!isset($subplugins[$pluginfo->component])) { + return array(); + } + + $list = array(); + + foreach ($subplugins[$pluginfo->component] as $subdata) { + foreach ($this->get_plugins_of_type($subdata->type) as $subpluginfo) { + $list[$subpluginfo->component] = $subpluginfo; + } + } + + return $list; + } + + /** + * Returns list of plugins that define their subplugins and the information + * about them from the db/subplugins.php file. + * + * @return array with keys like 'mod_quiz', and values the data from the + * corresponding db/subplugins.php file. + */ + public function get_subplugins() { + + if (is_array($this->subpluginsinfo)) { + return $this->subpluginsinfo; + } + + $plugintypes = core_component::get_plugin_types(); + + $this->subpluginsinfo = array(); + foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) { + foreach (core_component::get_plugin_list($type) as $plugin => $componentdir) { + $component = $type.'_'.$plugin; + $subplugins = core_component::get_subplugins($component); + if (!$subplugins) { + continue; + } + $this->subpluginsinfo[$component] = array(); + foreach ($subplugins as $subplugintype => $ignored) { + $subplugin = new stdClass(); + $subplugin->type = $subplugintype; + $subplugin->typerootdir = $plugintypes[$subplugintype]; + $this->subpluginsinfo[$component][$subplugintype] = $subplugin; + } + } + } + return $this->subpluginsinfo; + } + + /** + * Returns the name of the plugin that defines the given subplugin type + * + * If the given subplugin type is not actually a subplugin, returns false. + * + * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz + * @return false|string the name of the parent plugin, eg. mod_workshop + */ + public function get_parent_of_subplugin($subplugintype) { + $parent = core_component::get_subtype_parent($subplugintype); + if (!$parent) { + return false; + } + return $parent; + } + + /** + * Returns a localized name of a given plugin + * + * @param string $component name of the plugin, eg mod_workshop or auth_ldap + * @return string + */ + public function plugin_name($component) { + + $pluginfo = $this->get_plugin_info($component); + + if (is_null($pluginfo)) { + throw new moodle_exception('err_unknown_plugin', 'core_plugin', '', array('plugin' => $component)); + } + + return $pluginfo->displayname; + } + + /** + * Returns a localized name of a plugin typed in singular form + * + * Most plugin types define their names in core_plugin lang file. In case of subplugins, + * we try to ask the parent plugin for the name. In the worst case, we will return + * the value of the passed $type parameter. + * + * @param string $type the type of the plugin, e.g. mod or workshopform + * @return string + */ + public function plugintype_name($type) { + + if (get_string_manager()->string_exists('type_' . $type, 'core_plugin')) { + // For most plugin types, their names are defined in core_plugin lang file. + return get_string('type_' . $type, 'core_plugin'); + + } else if ($parent = $this->get_parent_of_subplugin($type)) { + // If this is a subplugin, try to ask the parent plugin for the name. + if (get_string_manager()->string_exists('subplugintype_' . $type, $parent)) { + return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type, $parent); + } else { + return $this->plugin_name($parent) . ' / ' . $type; + } + + } else { + return $type; + } + } + + /** + * Returns a localized name of a plugin type in plural form + * + * Most plugin types define their names in core_plugin lang file. In case of subplugins, + * we try to ask the parent plugin for the name. In the worst case, we will return + * the value of the passed $type parameter. + * + * @param string $type the type of the plugin, e.g. mod or workshopform + * @return string + */ + public function plugintype_name_plural($type) { + + if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) { + // For most plugin types, their names are defined in core_plugin lang file. + return get_string('type_' . $type . '_plural', 'core_plugin'); + + } else if ($parent = $this->get_parent_of_subplugin($type)) { + // If this is a subplugin, try to ask the parent plugin for the name. + if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) { + return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent); + } else { + return $this->plugin_name($parent) . ' / ' . $type; + } + + } else { + return $type; + } + } + + /** + * Returns information about the known plugin, or null + * + * @param string $component frankenstyle component name. + * @return \core\plugininfo\base|null the corresponding plugin information. + */ + public function get_plugin_info($component) { + list($type, $name) = core_component::normalize_component($component); + $plugins = $this->get_plugins(); + if (isset($plugins[$type][$name])) { + return $plugins[$type][$name]; + } else { + return null; + } + } + + /** + * Check to see if the current version of the plugin seems to be a checkout of an external repository. + * + * @see \core\update\deployer::plugin_external_source() + * @param string $component frankenstyle component name + * @return false|string + */ + public function plugin_external_source($component) { + + $plugininfo = $this->get_plugin_info($component); + + if (is_null($plugininfo)) { + return false; + } + + $pluginroot = $plugininfo->rootdir; + + if (is_dir($pluginroot.'/.git')) { + return 'git'; + } + + if (is_dir($pluginroot.'/CVS')) { + return 'cvs'; + } + + if (is_dir($pluginroot.'/.svn')) { + return 'svn'; + } + + return false; + } + + /** + * Get a list of any other plugins that require this one. + * @param string $component frankenstyle component name. + * @return array of frankensyle component names that require this one. + */ + public function other_plugins_that_require($component) { + $others = array(); + foreach ($this->get_plugins() as $type => $plugins) { + foreach ($plugins as $plugin) { + $required = $plugin->get_other_required_plugins(); + if (isset($required[$component])) { + $others[] = $plugin->component; + } + } + } + return $others; + } + + /** + * Check a dependencies list against the list of installed plugins. + * @param array $dependencies compenent name to required version or ANY_VERSION. + * @return bool true if all the dependencies are satisfied. + */ + public function are_dependencies_satisfied($dependencies) { + foreach ($dependencies as $component => $requiredversion) { + $otherplugin = $this->get_plugin_info($component); + if (is_null($otherplugin)) { + return false; + } + + if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) { + return false; + } + } + + return true; + } + + /** + * Checks all dependencies for all installed plugins + * + * This is used by install and upgrade. The array passed by reference as the second + * argument is populated with the list of plugins that have failed dependencies (note that + * a single plugin can appear multiple times in the $failedplugins). + * + * @param int $moodleversion the version from version.php. + * @param array $failedplugins to return the list of plugins with non-satisfied dependencies + * @return bool true if all the dependencies are satisfied for all plugins. + */ + public function all_plugins_ok($moodleversion, &$failedplugins = array()) { + + $return = true; + foreach ($this->get_plugins() as $type => $plugins) { + foreach ($plugins as $plugin) { + + if (!$plugin->is_core_dependency_satisfied($moodleversion)) { + $return = false; + $failedplugins[] = $plugin->component; + } + + if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) { + $return = false; + $failedplugins[] = $plugin->component; + } + } + } + + return $return; + } + + /** + * Is it possible to uninstall the given plugin? + * + * False is returned if the plugininfo subclass declares the uninstall should + * not be allowed via {@link \core\plugininfo\base::is_uninstall_allowed()} or if the + * core vetoes it (e.g. becase the plugin or some of its subplugins is required + * by some other installed plugin). + * + * @param string $component full frankenstyle name, e.g. mod_foobar + * @return bool + */ + public function can_uninstall_plugin($component) { + + $pluginfo = $this->get_plugin_info($component); + + if (is_null($pluginfo)) { + return false; + } + + if (!$this->common_uninstall_check($pluginfo)) { + return false; + } + + // Verify only if something else requires the subplugins, do not verify their common_uninstall_check()! + $subplugins = $this->get_subplugins_of_plugin($pluginfo->component); + foreach ($subplugins as $subpluginfo) { + // Check if there are some other plugins requiring this subplugin + // (but the parent and siblings). + foreach ($this->other_plugins_that_require($subpluginfo->component) as $requiresme) { + $ismyparent = ($pluginfo->component === $requiresme); + $ismysibling = in_array($requiresme, array_keys($subplugins)); + if (!$ismyparent and !$ismysibling) { + return false; + } + } + } + + // Check if there are some other plugins requiring this plugin + // (but its subplugins). + foreach ($this->other_plugins_that_require($pluginfo->component) as $requiresme) { + $ismysubplugin = in_array($requiresme, array_keys($subplugins)); + if (!$ismysubplugin) { + return false; + } + } + + return true; + } + + /** + * Returns uninstall URL if exists. + * + * @param string $component + * @param string $return either 'overview' or 'manage' + * @return moodle_url uninstall URL, null if uninstall not supported + */ + public function get_uninstall_url($component, $return = 'overview') { + if (!$this->can_uninstall_plugin($component)) { + return null; + } + + $pluginfo = $this->get_plugin_info($component); + + if (is_null($pluginfo)) { + return null; + } + + if (method_exists($pluginfo, 'get_uninstall_url')) { + debugging('plugininfo method get_uninstall_url() is deprecated, all plugins should be uninstalled via standard URL only.'); + return $pluginfo->get_uninstall_url($return); + } + + return $pluginfo->get_default_uninstall_url($return); + } + + /** + * Uninstall the given plugin. + * + * Automatically cleans-up all remaining configuration data, log records, events, + * files from the file pool etc. + * + * In the future, the functionality of {@link uninstall_plugin()} function may be moved + * into this method and all the code should be refactored to use it. At the moment, we + * mimic this future behaviour by wrapping that function call. + * + * @param string $component + * @param progress_trace $progress traces the process + * @return bool true on success, false on errors/problems + */ + public function uninstall_plugin($component, progress_trace $progress) { + + $pluginfo = $this->get_plugin_info($component); + + if (is_null($pluginfo)) { + return false; + } + + // Give the pluginfo class a chance to execute some steps. + $result = $pluginfo->uninstall($progress); + if (!$result) { + return false; + } + + // Call the legacy core function to uninstall the plugin. + ob_start(); + uninstall_plugin($pluginfo->type, $pluginfo->name); + $progress->output(ob_get_clean()); + + return true; + } + + /** + * Checks if there are some plugins with a known available update + * + * @return bool true if there is at least one available update + */ + public function some_plugins_updatable() { + foreach ($this->get_plugins() as $type => $plugins) { + foreach ($plugins as $plugin) { + if ($plugin->available_updates()) { + return true; + } + } + } + + return false; + } + + /** + * Check to see if the given plugin folder can be removed by the web server process. + * + * @param string $component full frankenstyle component + * @return bool + */ + public function is_plugin_folder_removable($component) { + + $pluginfo = $this->get_plugin_info($component); + + if (is_null($pluginfo)) { + return false; + } + + // To be able to remove the plugin folder, its parent must be writable, too. + if (!is_writable(dirname($pluginfo->rootdir))) { + return false; + } + + // Check that the folder and all its content is writable (thence removable). + return $this->is_directory_removable($pluginfo->rootdir); + } + + /** + * Defines a list of all plugins that were originally shipped in the standard Moodle distribution, + * but are not anymore and are deleted during upgrades. + * + * The main purpose of this list is to hide missing plugins during upgrade. + * + * @param string $type plugin type + * @param string $name plugin name + * @return bool + */ + public static function is_deleted_standard_plugin($type, $name) { + // Do not include plugins that were removed during upgrades to versions that are + // not supported as source versions for upgrade any more. For example, at MOODLE_23_STABLE + // branch, listed should be no plugins that were removed at 1.9.x - 2.1.x versions as + // Moodle 2.3 supports upgrades from 2.2.x only. + $plugins = array( + 'qformat' => array('blackboard'), + 'enrol' => array('authorize'), + 'tool' => array('bloglevelupgrade'), + ); + + if (!isset($plugins[$type])) { + return false; + } + return in_array($name, $plugins[$type]); + } + + /** + * Defines a white list of all plugins shipped in the standard Moodle distribution + * + * @param string $type + * @return false|array array of standard plugins or false if the type is unknown + */ + public static function standard_plugins_list($type) { + + $standard_plugins = array( + + 'assignment' => array( + 'offline', 'online', 'upload', 'uploadsingle' + ), + + 'assignsubmission' => array( + 'comments', 'file', 'onlinetext' + ), + + 'assignfeedback' => array( + 'comments', 'file', 'offline', 'editpdf' + ), + + 'atto' => array( + 'bold', 'clear', 'html', 'image', 'indent', 'italic', 'link', + 'media', 'orderedlist', 'outdent', 'strike', 'title', + 'underline', 'unlink', 'unorderedlist' + ), + + 'auth' => array( + 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet', + 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius', + 'shibboleth', 'webservice' + ), + + 'block' => array( + 'activity_modules', 'admin_bookmarks', 'badges', 'blog_menu', + 'blog_recent', 'blog_tags', 'calendar_month', + 'calendar_upcoming', 'comments', 'community', + 'completionstatus', 'course_list', 'course_overview', + 'course_summary', 'feedback', 'glossary_random', 'html', + 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile', + 'navigation', 'news_items', 'online_users', 'participants', + 'private_files', 'quiz_results', 'recent_activity', + 'rss_client', 'search_forums', 'section_links', + 'selfcompletion', 'settings', 'site_main_menu', + 'social_activities', 'tag_flickr', 'tag_youtube', 'tags' + ), + + 'booktool' => array( + 'exportimscp', 'importhtml', 'print' + ), + + 'cachelock' => array( + 'file' + ), + + 'cachestore' => array( + 'file', 'memcache', 'memcached', 'mongodb', 'session', 'static' + ), + + 'calendartype' => array( + 'gregorian' + ), + + 'coursereport' => array( + // Deprecated! + ), + + 'datafield' => array( + 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu', + 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url' + ), + + 'datapreset' => array( + 'imagegallery' + ), + + 'editor' => array( + 'textarea', 'tinymce', 'atto' + ), + + 'enrol' => array( + 'category', 'cohort', 'database', 'flatfile', + 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet', + 'paypal', 'self' + ), + + 'filter' => array( + 'activitynames', 'algebra', 'censor', 'emailprotect', + 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy', + 'urltolink', 'data', 'glossary' + ), + + 'format' => array( + 'singleactivity', 'social', 'topics', 'weeks' + ), + + 'gradeexport' => array( + 'ods', 'txt', 'xls', 'xml' + ), + + 'gradeimport' => array( + 'csv', 'xml' + ), + + 'gradereport' => array( + 'grader', 'outcomes', 'overview', 'user' + ), + + 'gradingform' => array( + 'rubric', 'guide' + ), + + 'local' => array( + ), + + 'message' => array( + 'email', 'jabber', 'popup' + ), + + 'mnetservice' => array( + 'enrol' + ), + + 'mod' => array( + 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder', + 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page', + 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop' + ), + + 'plagiarism' => array( + ), + + 'portfolio' => array( + 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa' + ), + + 'profilefield' => array( + 'checkbox', 'datetime', 'menu', 'text', 'textarea' + ), + + 'qbehaviour' => array( + 'adaptive', 'adaptivenopenalty', 'deferredcbm', + 'deferredfeedback', 'immediatecbm', 'immediatefeedback', + 'informationitem', 'interactive', 'interactivecountback', + 'manualgraded', 'missing' + ), + + 'qformat' => array( + 'aiken', 'blackboard_six', 'examview', 'gift', + 'learnwise', 'missingword', 'multianswer', 'webct', + 'xhtml', 'xml' + ), + + 'qtype' => array( + 'calculated', 'calculatedmulti', 'calculatedsimple', + 'description', 'essay', 'match', 'missingtype', 'multianswer', + 'multichoice', 'numerical', 'random', 'randomsamatch', + 'shortanswer', 'truefalse' + ), + + 'quiz' => array( + 'grading', 'overview', 'responses', 'statistics' + ), + + 'quizaccess' => array( + 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate', + 'password', 'safebrowser', 'securewindow', 'timelimit' + ), + + 'report' => array( + 'backups', 'completion', 'configlog', 'courseoverview', + 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats', 'performance' + ), + + 'repository' => array( + 'alfresco', 'areafiles', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem', + 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot', + 'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav', + 'wikimedia', 'youtube' + ), + + 'scormreport' => array( + 'basic', + 'interactions', + 'graphs', + 'objectives' + ), + + 'tinymce' => array( + 'ctrlhelp', 'dragmath', 'managefiles', 'moodleemoticon', 'moodleimage', + 'moodlemedia', 'moodlenolink', 'pdw', 'spellchecker', 'wrap' + ), + + 'theme' => array( + 'afterburner', 'anomaly', 'arialist', 'base', 'binarius', 'bootstrapbase', + 'boxxie', 'brick', 'canvas', 'clean', 'formal_white', 'formfactor', + 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble', + 'nonzero', 'overlay', 'serenity', 'sky_high', 'splash', + 'standard', 'standardold' + ), + + 'tool' => array( + 'assignmentupgrade', 'behat', 'capability', 'customlang', + 'dbtransfer', 'generator', 'health', 'innodb', 'installaddon', + 'langimport', 'multilangupgrade', 'phpunit', 'profiling', + 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', + 'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'xmldb' + ), + + 'webservice' => array( + 'amf', 'rest', 'soap', 'xmlrpc' + ), + + 'workshopallocation' => array( + 'manual', 'random', 'scheduled' + ), + + 'workshopeval' => array( + 'best' + ), + + 'workshopform' => array( + 'accumulative', 'comments', 'numerrors', 'rubric' + ) + ); + + if (isset($standard_plugins[$type])) { + return $standard_plugins[$type]; + } else { + return false; + } + } + + /** + * Reorders plugin types into a sequence to be displayed + * + * For technical reasons, plugin types returned by {@link core_component::get_plugin_types()} are + * in a certain order that does not need to fit the expected order for the display. + * Particularly, activity modules should be displayed first as they represent the + * real heart of Moodle. They should be followed by other plugin types that are + * used to build the courses (as that is what one expects from LMS). After that, + * other supportive plugin types follow. + * + * @param array $types associative array + * @return array same array with altered order of items + */ + protected function reorder_plugin_types(array $types) { + $fix = array('mod' => $types['mod']); + foreach (core_component::get_plugin_list('mod') as $plugin => $fulldir) { + if (!$subtypes = core_component::get_subplugins('mod_'.$plugin)) { + continue; + } + foreach ($subtypes as $subtype => $ignored) { + $fix[$subtype] = $types[$subtype]; + } + } + + $fix['mod'] = $types['mod']; + $fix['block'] = $types['block']; + $fix['qtype'] = $types['qtype']; + $fix['qbehaviour'] = $types['qbehaviour']; + $fix['qformat'] = $types['qformat']; + $fix['filter'] = $types['filter']; + + $fix['editor'] = $types['editor']; + foreach (core_component::get_plugin_list('editor') as $plugin => $fulldir) { + if (!$subtypes = core_component::get_subplugins('editor_'.$plugin)) { + continue; + } + foreach ($subtypes as $subtype => $ignored) { + $fix[$subtype] = $types[$subtype]; + } + } + + $fix['enrol'] = $types['enrol']; + $fix['auth'] = $types['auth']; + $fix['tool'] = $types['tool']; + foreach (core_component::get_plugin_list('tool') as $plugin => $fulldir) { + if (!$subtypes = core_component::get_subplugins('tool_'.$plugin)) { + continue; + } + foreach ($subtypes as $subtype => $ignored) { + $fix[$subtype] = $types[$subtype]; + } + } + + foreach ($types as $type => $path) { + if (!isset($fix[$type])) { + $fix[$type] = $path; + } + } + return $fix; + } + + /** + * Check if the given directory can be removed by the web server process. + * + * This recursively checks that the given directory and all its contents + * it writable. + * + * @param string $fullpath + * @return boolean + */ + protected function is_directory_removable($fullpath) { + + if (!is_writable($fullpath)) { + return false; + } + + if (is_dir($fullpath)) { + $handle = opendir($fullpath); + } else { + return false; + } + + $result = true; + + while ($filename = readdir($handle)) { + + if ($filename === '.' or $filename === '..') { + continue; + } + + $subfilepath = $fullpath.'/'.$filename; + + if (is_dir($subfilepath)) { + $result = $result && $this->is_directory_removable($subfilepath); + + } else { + $result = $result && is_writable($subfilepath); + } + } + + closedir($handle); + + return $result; + } + + /** + * Helper method that implements common uninstall prerequisites + * + * @param \core\plugininfo\base $pluginfo + * @return bool + */ + protected function common_uninstall_check(\core\plugininfo\base $pluginfo) { + + if (!$pluginfo->is_uninstall_allowed()) { + // The plugin's plugininfo class declares it should not be uninstalled. + return false; + } + + if ($pluginfo->get_status() === self::PLUGIN_STATUS_NEW) { + // The plugin is not installed. It should be either installed or removed from the disk. + // Relying on this temporary state may be tricky. + return false; + } + + if (method_exists($pluginfo, 'get_uninstall_url') and is_null($pluginfo->get_uninstall_url())) { + // Backwards compatibility. + debugging('\core\plugininfo\base subclasses should use is_uninstall_allowed() instead of returning null in get_uninstall_url()', + DEBUG_DEVELOPER); + return false; + } + + return true; + } +} diff --git a/lib/classes/plugininfo/auth.php b/lib/classes/plugininfo/auth.php new file mode 100644 index 00000000000..f92771deac5 --- /dev/null +++ b/lib/classes/plugininfo/auth.php @@ -0,0 +1,133 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage, admin_externalpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for authentication plugins + */ +class auth extends base { + public function is_uninstall_allowed() { + global $DB; + + if (in_array($this->name, array('manual', 'nologin', 'webservice', 'mnet'))) { + return false; + } + + return !$DB->record_exists('user', array('auth'=>$this->name)); + } + + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $CFG; + + // These two are always enabled and can't be disabled. + $enabled = array('nologin'=>'nologin', 'manual'=>'manual'); + foreach (explode(',', $CFG->auth) as $auth) { + $enabled[$auth] = $auth; + } + + return $enabled; + } + + public function get_settings_section_name() { + return 'authsetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $auth = $this; // Also to be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = null; + if (file_exists($this->full_path('settings.php'))) { + // TODO: finish implementation of common settings - locking, etc. + $settings = new admin_settingpage($section, $this->displayname, + 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + } else { + $settingsurl = new moodle_url('/admin/auth_config.php', array('auth' => $this->name)); + $settings = new admin_externalpage($section, $this->displayname, + $settingsurl, 'moodle/site:config', $this->is_enabled() === false); + } + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/settings.php', array('section'=>'manageauths')); + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $CFG; + + if (!empty($CFG->auth)) { + $auths = explode(',', $CFG->auth); + $auths = array_unique($auths); + } else { + $auths = array(); + } + if (($key = array_search($this->name, $auths)) !== false) { + unset($auths[$key]); + set_config('auth', implode(',', $auths)); + } + + if (!empty($CFG->registerauth) and $CFG->registerauth === $this->name) { + unset_config('registerauth'); + } + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/base.php b/lib/classes/plugininfo/base.php new file mode 100644 index 00000000000..73b01f9b6de --- /dev/null +++ b/lib/classes/plugininfo/base.php @@ -0,0 +1,593 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use core_component, core_plugin_manager, moodle_url, coding_exception; + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Base class providing access to the information about a plugin + * + * @property-read string component the component name, type_name + */ +abstract class base { + + /** @var string the plugintype name, eg. mod, auth or workshopform */ + public $type; + /** @var string full path to the location of all the plugins of this type */ + public $typerootdir; + /** @var string the plugin name, eg. assignment, ldap */ + public $name; + /** @var string the localized plugin name */ + public $displayname; + /** @var string the plugin source, one of core_plugin_manager::PLUGIN_SOURCE_xxx constants */ + public $source; + /** @var string fullpath to the location of this plugin */ + public $rootdir; + /** @var int|string the version of the plugin's source code */ + public $versiondisk; + /** @var int|string the version of the installed plugin */ + public $versiondb; + /** @var int|float|string required version of Moodle core */ + public $versionrequires; + /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */ + public $dependencies; + /** @var int number of instances of the plugin - not supported yet */ + public $instances; + /** @var int order of the plugin among other plugins of the same type - not supported yet */ + public $sortorder; + /** @var array|null array of {@link \core\update\info} for this plugin */ + public $availableupdates; + + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + return null; + } + + /** + * Gathers and returns the information about all plugins of the given type, + * either on disk or previously installed. + * + * @param string $type the name of the plugintype, eg. mod, auth or workshopform + * @param string $typerootdir full path to the location of the plugin dir + * @param string $typeclass the name of the actually called class + * @return array of plugintype classes, indexed by the plugin name + */ + public static function get_plugins($type, $typerootdir, $typeclass) { + // Get the information about plugins at the disk. + $plugins = core_component::get_plugin_list($type); + $return = array(); + foreach ($plugins as $pluginname => $pluginrootdir) { + $return[$pluginname] = self::make_plugin_instance($type, $typerootdir, + $pluginname, $pluginrootdir, $typeclass); + } + + // Fetch missing incorrectly uninstalled plugins. + $manager = core_plugin_manager::instance(); + $plugins = $manager->get_installed_plugins($type); + + foreach ($plugins as $name => $version) { + if (isset($return[$name])) { + continue; + } + $plugin = new $typeclass(); + $plugin->type = $type; + $plugin->typerootdir = $typerootdir; + $plugin->name = $name; + $plugin->rootdir = null; + $plugin->displayname = $name; + $plugin->versiondb = $version; + $plugin->init_is_standard(); + + $return[$name] = $plugin; + } + + return $return; + } + + /** + * Makes a new instance of the plugininfo class + * + * @param string $type the plugin type, eg. 'mod' + * @param string $typerootdir full path to the location of all the plugins of this type + * @param string $name the plugin name, eg. 'workshop' + * @param string $namerootdir full path to the location of the plugin + * @param string $typeclass the name of class that holds the info about the plugin + * @return base the instance of $typeclass + */ + protected static function make_plugin_instance($type, $typerootdir, $name, $namerootdir, $typeclass) { + $plugin = new $typeclass(); + $plugin->type = $type; + $plugin->typerootdir = $typerootdir; + $plugin->name = $name; + $plugin->rootdir = $namerootdir; + + $plugin->init_display_name(); + $plugin->load_disk_version(); + $plugin->load_db_version(); + $plugin->init_is_standard(); + + return $plugin; + } + + /** + * Is this plugin already installed and updated? + * @return bool true if plugin installed and upgraded. + */ + public function is_installed_and_upgraded() { + if (!$this->rootdir) { + return false; + } + if ($this->versiondb === null and $this->versiondisk === null) { + // There is no version.php or version info inside, + // for now let's pretend it is ok. + // TODO: return false once we require version in each plugin. + return true; + } + + return ((float)$this->versiondb === (float)$this->versiondisk); + } + + /** + * Sets {@link $displayname} property to a localized name of the plugin + */ + public function init_display_name() { + if (!get_string_manager()->string_exists('pluginname', $this->component)) { + $this->displayname = '[pluginname,' . $this->component . ']'; + } else { + $this->displayname = get_string('pluginname', $this->component); + } + } + + /** + * Magic method getter, redirects to read only values. + * + * @param string $name + * @return mixed + */ + public function __get($name) { + switch ($name) { + case 'component': return $this->type . '_' . $this->name; + + default: + debugging('Invalid plugin property accessed! '.$name); + return null; + } + } + + /** + * Return the full path name of a file within the plugin. + * + * No check is made to see if the file exists. + * + * @param string $relativepath e.g. 'version.php'. + * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'. + */ + public function full_path($relativepath) { + if (empty($this->rootdir)) { + return ''; + } + return $this->rootdir . '/' . $relativepath; + } + + /** + * Sets {@link $versiondisk} property to a numerical value representing the + * version of the plugin's source code. + * + * If the value is null after calling this method, either the plugin + * does not use versioning (typically does not have any database + * data) or is missing from disk. + */ + public function load_disk_version() { + $versions = core_plugin_manager::instance()->get_present_plugins($this->type); + + $this->versiondisk = null; + $this->versionrequires = null; + $this->dependencies = array(); + + if (!isset($versions[$this->name])) { + return; + } + + $plugin = $versions[$this->name]; + + if (isset($plugin->version)) { + $this->versiondisk = $plugin->version; + } + if (isset($plugin->requires)) { + $this->versionrequires = $plugin->requires; + } + if (isset($plugin->dependencies)) { + $this->dependencies = $plugin->dependencies; + } + } + + /** + * Get the list of other plugins that this plugin requires to be installed. + * + * @return array with keys the frankenstyle plugin name, and values either + * a version string (like '2011101700') or the constant ANY_VERSION. + */ + public function get_other_required_plugins() { + if (is_null($this->dependencies)) { + $this->load_disk_version(); + } + return $this->dependencies; + } + + /** + * Is this is a subplugin? + * + * @return boolean + */ + public function is_subplugin() { + return ($this->get_parent_plugin() !== false); + } + + /** + * If I am a subplugin, return the name of my parent plugin. + * + * @return string|bool false if not a subplugin, name of the parent otherwise + */ + public function get_parent_plugin() { + return $this->get_plugin_manager()->get_parent_of_subplugin($this->type); + } + + /** + * Sets {@link $versiondb} property to a numerical value representing the + * currently installed version of the plugin. + * + * If the value is null after calling this method, either the plugin + * does not use versioning (typically does not have any database + * data) or has not been installed yet. + */ + public function load_db_version() { + $versions = core_plugin_manager::instance()->get_installed_plugins($this->type); + + if (isset($versions[$this->name])) { + $this->versiondb = $versions[$this->name]; + } else { + $this->versiondb = null; + } + } + + /** + * Sets {@link $source} property to one of core_plugin_manager::PLUGIN_SOURCE_xxx + * constants. + * + * If the property's value is null after calling this method, then + * the type of the plugin has not been recognized and you should throw + * an exception. + */ + public function init_is_standard() { + + $standard = core_plugin_manager::standard_plugins_list($this->type); + + if ($standard !== false) { + $standard = array_flip($standard); + if (isset($standard[$this->name])) { + $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD; + } else if (!is_null($this->versiondb) and is_null($this->versiondisk) + and core_plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) { + $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD; // To be deleted. + } else { + $this->source = core_plugin_manager::PLUGIN_SOURCE_EXTENSION; + } + } + } + + /** + * Returns true if the plugin is shipped with the official distribution + * of the current Moodle version, false otherwise. + * + * @return bool + */ + public function is_standard() { + return $this->source === core_plugin_manager::PLUGIN_SOURCE_STANDARD; + } + + /** + * Returns true if the the given Moodle version is enough to run this plugin + * + * @param string|int|double $moodleversion + * @return bool + */ + public function is_core_dependency_satisfied($moodleversion) { + + if (empty($this->versionrequires)) { + return true; + + } else { + return (double)$this->versionrequires <= (double)$moodleversion; + } + } + + /** + * Returns the status of the plugin + * + * @return string one of core_plugin_manager::PLUGIN_STATUS_xxx constants + */ + public function get_status() { + + if (is_null($this->versiondb) and is_null($this->versiondisk)) { + return core_plugin_manager::PLUGIN_STATUS_NODB; + + } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) { + return core_plugin_manager::PLUGIN_STATUS_NEW; + + } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) { + if (core_plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) { + return core_plugin_manager::PLUGIN_STATUS_DELETE; + } else { + return core_plugin_manager::PLUGIN_STATUS_MISSING; + } + + } else if ((float)$this->versiondb === (float)$this->versiondisk) { + // Note: the float comparison should work fine here + // because there are no arithmetic operations with the numbers. + return core_plugin_manager::PLUGIN_STATUS_UPTODATE; + + } else if ($this->versiondb < $this->versiondisk) { + return core_plugin_manager::PLUGIN_STATUS_UPGRADE; + + } else if ($this->versiondb > $this->versiondisk) { + return core_plugin_manager::PLUGIN_STATUS_DOWNGRADE; + + } else { + // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-) + throw new coding_exception('Unable to determine plugin state, check the plugin versions'); + } + } + + /** + * Returns the information about plugin availability + * + * True means that the plugin is enabled. False means that the plugin is + * disabled. Null means that the information is not available, or the + * plugin does not support configurable availability or the availability + * can not be changed. + * + * @return null|bool + */ + public function is_enabled() { + if (!$this->rootdir) { + // Plugin missing. + return false; + } + + $enabled = core_plugin_manager::instance()->get_enabled_plugins($this->type); + + if (!is_array($enabled)) { + return null; + } + + return isset($enabled[$this->name]); + } + + /** + * Populates the property {@link $availableupdates} with the information provided by + * available update checker + * + * @param \core\update\checker $provider the class providing the available update info + */ + public function check_available_updates(\core\update\checker $provider) { + global $CFG; + + if (isset($CFG->updateminmaturity)) { + $minmaturity = $CFG->updateminmaturity; + } else { + // This can happen during the very first upgrade to 2.3 . + $minmaturity = MATURITY_STABLE; + } + + $this->availableupdates = $provider->get_update_info($this->component, + array('minmaturity' => $minmaturity)); + } + + /** + * If there are updates for this plugin available, returns them. + * + * Returns array of {@link \core\update\info} objects, if some update + * is available. Returns null if there is no update available or if the update + * availability is unknown. + * + * @return array|null + */ + public function available_updates() { + + if (empty($this->availableupdates) or !is_array($this->availableupdates)) { + return null; + } + + $updates = array(); + + foreach ($this->availableupdates as $availableupdate) { + if ($availableupdate->version > $this->versiondisk) { + $updates[] = $availableupdate; + } + } + + if (empty($updates)) { + return null; + } + + return $updates; + } + + /** + * Returns the node name used in admin settings menu for this plugin settings (if applicable) + * + * @return null|string node name or null if plugin does not create settings node (default) + */ + public function get_settings_section_name() { + return null; + } + + /** + * Returns the URL of the plugin settings screen + * + * Null value means that the plugin either does not have the settings screen + * or its location is not available via this library. + * + * @return null|moodle_url + */ + public function get_settings_url() { + $section = $this->get_settings_section_name(); + if ($section === null) { + return null; + } + $settings = admin_get_root()->locate($section); + if ($settings && $settings instanceof \admin_settingpage) { + return new moodle_url('/admin/settings.php', array('section' => $section)); + } else if ($settings && $settings instanceof \admin_externalpage) { + return new moodle_url($settings->url); + } else { + return null; + } + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + } + + /** + * Should there be a way to uninstall the plugin via the administration UI. + * + * By default uninstallation is not allowed, plugin developers must enable it explicitly! + * + * @return bool + */ + public function is_uninstall_allowed() { + return false; + } + + /** + * Optional extra warning before uninstallation, for example number of uses in courses. + * + * @return string + */ + public function get_uninstall_extra_warning() { + return ''; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + // Override when extending class, + // do not forget to call parent::pre_uninstall_cleanup() at the end. + } + + /** + * Returns relative directory of the plugin with heading '/' + * + * @return string + */ + public function get_dir() { + global $CFG; + + return substr($this->rootdir, strlen($CFG->dirroot)); + } + + /** + * Hook method to implement certain steps when uninstalling the plugin. + * + * This hook is called by {@link core_plugin_manager::uninstall_plugin()} so + * it is basically usable only for those plugin types that use the default + * uninstall tool provided by {@link self::get_default_uninstall_url()}. + * + * @param \progress_trace $progress traces the process + * @return bool true on success, false on failure + */ + public function uninstall(\progress_trace $progress) { + return true; + } + + /** + * Where should we return after plugin of this type is uninstalled? + * @param string $return + * @return moodle_url + */ + public function get_return_url_after_uninstall($return) { + if ($return === 'manage') { + if ($url = $this->get_manage_url()) { + return $url; + } + } + return new moodle_url('/admin/plugins.php#plugin_type_cell_'.$this->type); + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return null; + } + + /** + * Returns URL to a script that handles common plugin uninstall procedure. + * + * This URL is intended for all plugin uninstallations. + * + * @param string $return either 'overview' or 'manage' + * @return moodle_url + */ + public final function get_default_uninstall_url($return = 'overview') { + return new moodle_url('/admin/plugins.php', array( + 'sesskey' => sesskey(), + 'uninstall' => $this->component, + 'confirm' => 0, + 'return' => $return, + )); + } + + /** + * Provides access to the core_plugin_manager singleton. + * + * @return core_plugin_manager + */ + protected function get_plugin_manager() { + return core_plugin_manager::instance(); + } +} diff --git a/lib/classes/plugininfo/block.php b/lib/classes/plugininfo/block.php new file mode 100644 index 00000000000..bce3f997844 --- /dev/null +++ b/lib/classes/plugininfo/block.php @@ -0,0 +1,169 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage, admin_externalpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for page side blocks + */ +class block extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + + return $DB->get_records_menu('block', array('visible'=>1), 'name ASC', 'name, name AS val'); + } + + /** + * Magic method getter, redirects to read only values. + * + * For block plugins pretends the object has 'visible' property for compatibility + * with plugins developed for Moodle version below 2.4 + * + * @param string $name + * @return mixed + */ + public function __get($name) { + if ($name === 'visible') { + debugging('This is now an instance of plugininfo_block, please use $block->is_enabled() instead of $block->visible', DEBUG_DEVELOPER); + return ($this->is_enabled() !== false); + } + return parent::__get($name); + } + + public function init_display_name() { + + if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) { + $this->displayname = get_string('pluginname', 'block_' . $this->name); + + } else if (($block = block_instance($this->name)) !== false) { + $this->displayname = $block->get_title(); + + } else { + parent::init_display_name(); + } + } + + public function get_settings_section_name() { + return 'blocksetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $block = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + $section = $this->get_settings_section_name(); + + if (!$hassiteconfig || (($blockinstance = block_instance($this->name)) === false)) { + return; + } + + $settings = null; + if ($blockinstance->has_config()) { + if (file_exists($this->full_path('settings.php'))) { + $settings = new admin_settingpage($section, $this->displayname, + 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + } + } + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + public function is_uninstall_allowed() { + if ($this->name === 'settings' or $this->name === 'navigation') { + return false; + } + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/blocks.php'); + } + + /** + * Warning with number of block instances. + * + * @return string + */ + public function get_uninstall_extra_warning() { + global $DB; + + if (!$count = $DB->count_records('block_instances', array('blockname'=>$this->name))) { + return ''; + } + + return '

'.get_string('uninstallextraconfirmblock', 'core_plugin', array('instances'=>$count)).'

'; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $DB, $CFG; + + if ($block = $DB->get_record('block', array('name'=>$this->name))) { + // Inform block it's about to be deleted. + if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) { + $blockobject = block_instance($block->name); + if ($blockobject) { + $blockobject->before_delete(); // Only if we can create instance, block might have been already removed. + } + } + + // First delete instances and related contexts. + $instances = $DB->get_records('block_instances', array('blockname' => $block->name)); + foreach ($instances as $instance) { + blocks_delete_instance($instance); + } + + // Delete block. + $DB->delete_records('block', array('id'=>$block->id)); + } + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/cachelock.php b/lib/classes/plugininfo/cachelock.php new file mode 100644 index 00000000000..a0be611fe44 --- /dev/null +++ b/lib/classes/plugininfo/cachelock.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class cachelock extends base { + + public function is_uninstall_allowed() { + return false; + } +} diff --git a/lib/classes/plugininfo/cachestore.php b/lib/classes/plugininfo/cachestore.php new file mode 100644 index 00000000000..cf3aeac6942 --- /dev/null +++ b/lib/classes/plugininfo/cachestore.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class cachestore extends base { + + public function is_uninstall_allowed() { + return false; + } +} diff --git a/lib/classes/plugininfo/calendartype.php b/lib/classes/plugininfo/calendartype.php new file mode 100644 index 00000000000..e4888b9d039 --- /dev/null +++ b/lib/classes/plugininfo/calendartype.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class calendartype extends base { + + public function is_uninstall_allowed() { + return false; + } +} diff --git a/lib/classes/plugininfo/coursereport.php b/lib/classes/plugininfo/coursereport.php new file mode 100644 index 00000000000..29db2c00896 --- /dev/null +++ b/lib/classes/plugininfo/coursereport.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class coursereport extends base { + + public function is_uninstall_allowed() { + return true; + } +} diff --git a/lib/classes/plugininfo/editor.php b/lib/classes/plugininfo/editor.php new file mode 100644 index 00000000000..2873176fe21 --- /dev/null +++ b/lib/classes/plugininfo/editor.php @@ -0,0 +1,100 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage; + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Class for HTML editors + */ +class editor extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $CFG; + + if (empty($CFG->texteditors)) { + return array('tinymce'=>'tinymce', 'textarea'=>'textarea'); + } + + $enabled = array(); + foreach (explode(',', $CFG->texteditors) as $editor) { + $enabled[$editor] = $editor; + } + + return $enabled; + } + + public function get_settings_section_name() { + return 'editorsettings' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $editor = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + /** + * Basic textarea editor can not be uninstalled. + */ + public function is_uninstall_allowed() { + if ($this->name === 'textarea') { + return false; + } else { + return true; + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/settings.php', array('section'=>'manageeditors')); + } +} diff --git a/lib/classes/plugininfo/enrol.php b/lib/classes/plugininfo/enrol.php new file mode 100644 index 00000000000..8a6b49eda1e --- /dev/null +++ b/lib/classes/plugininfo/enrol.php @@ -0,0 +1,161 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage, admin_externalpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for enrolment plugins + */ +class enrol extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $CFG; + + $enabled = array(); + foreach (explode(',', $CFG->enrol_plugins_enabled) as $enrol) { + $enabled[$enrol] = $enrol; + } + + return $enabled; + } + + public function get_settings_section_name() { + if (file_exists($this->full_path('settings.php'))) { + return 'enrolsettings' . $this->name; + } else { + return null; + } + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $enrol = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + + include($this->full_path('settings.php')); // This may also set $settings to null! + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + public function is_uninstall_allowed() { + if ($this->name === 'manual') { + return false; + } + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/settings.php', array('section'=>'manageenrols')); + } + + /** + * Return warning with number of activities and number of affected courses. + * + * @return string + */ + public function get_uninstall_extra_warning() { + global $DB, $OUTPUT; + + $sql = "SELECT COUNT('x') + FROM {user_enrolments} ue + JOIN {enrol} e ON e.id = ue.enrolid + WHERE e.enrol = :plugin"; + $count = $DB->count_records_sql($sql, array('plugin'=>$this->name)); + + if (!$count) { + return ''; + } + + $migrateurl = new moodle_url('/admin/enrol.php', array('action'=>'migrate', 'enrol'=>$this->name, 'sesskey'=>sesskey())); + $migrate = new \single_button($migrateurl, get_string('migratetomanual', 'core_enrol')); + $button = $OUTPUT->render($migrate); + + $result = '

'.get_string('uninstallextraconfirmenrol', 'core_plugin', array('enrolments'=>$count)).'

'; + $result .= $button; + + return $result; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $DB, $CFG; + + // NOTE: this is a bit brute force way - it will not trigger events and hooks properly. + + // Nuke all role assignments. + role_unassign_all(array('component'=>'enrol_'.$this->name)); + + // Purge participants. + $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($this->name)); + + // Purge enrol instances. + $DB->delete_records('enrol', array('enrol'=>$this->name)); + + // Tweak enrol settings. + if (!empty($CFG->enrol_plugins_enabled)) { + $enabledenrols = explode(',', $CFG->enrol_plugins_enabled); + $enabledenrols = array_unique($enabledenrols); + $enabledenrols = array_flip($enabledenrols); + unset($enabledenrols[$this->name]); + $enabledenrols = array_flip($enabledenrols); + if (is_array($enabledenrols)) { + set_config('enrol_plugins_enabled', implode(',', $enabledenrols)); + } + } + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/filter.php b/lib/classes/plugininfo/filter.php new file mode 100644 index 00000000000..7cf227e2a43 --- /dev/null +++ b/lib/classes/plugininfo/filter.php @@ -0,0 +1,115 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage, admin_externalpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for text filters + */ +class filter extends base { + + public function init_display_name() { + if (!get_string_manager()->string_exists('filtername', $this->component)) { + $this->displayname = '[filtername,' . $this->component . ']'; + } else { + $this->displayname = get_string('filtername', $this->component); + } + } + + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB, $CFG; + require_once("$CFG->libdir/filterlib.php"); + + $enabled = array(); + $filters = $DB->get_records_select('filter_active', "active <> :disabled", array('disabled'=>TEXTFILTER_DISABLED), 'filter ASC', 'id, filter'); + foreach ($filters as $filter) { + $enabled[$filter->filter] = $filter->filter; + } + + return $enabled; + } + + public function get_settings_section_name() { + return 'filtersetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $filter = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('filtersettings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/filters.php'); + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $DB; + + $DB->delete_records('filter_active', array('filter' => $this->name)); + $DB->delete_records('filter_config', array('filter' => $this->name)); + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/format.php b/lib/classes/plugininfo/format.php new file mode 100644 index 00000000000..d0d161d9a2b --- /dev/null +++ b/lib/classes/plugininfo/format.php @@ -0,0 +1,173 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage, core_plugin_manager; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for course formats + */ +class format extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + + $plugins = core_plugin_manager::instance()->get_installed_plugins('format'); + if (!$plugins) { + return array(); + } + $installed = array(); + foreach ($plugins as $plugin => $version) { + $installed[] = 'format_'.$plugin; + } + + list($installed, $params) = $DB->get_in_or_equal($installed, SQL_PARAMS_NAMED); + $disabled = $DB->get_records_select('config_plugins', "plugin $installed AND name = 'disabled'", $params, 'plugin ASC'); + foreach ($disabled as $conf) { + if (empty($conf->value)) { + continue; + } + list($type, $name) = explode('_', $conf->plugin, 2); + unset($plugins[$name]); + } + + $enabled = array(); + foreach ($plugins as $plugin => $version) { + $enabled[$plugin] = $plugin; + } + + return $enabled; + } + + /** + * Gathers and returns the information about all plugins of the given type + * + * @param string $type the name of the plugintype, eg. mod, auth or workshopform + * @param string $typerootdir full path to the location of the plugin dir + * @param string $typeclass the name of the actually called class + * @return array of plugintype classes, indexed by the plugin name + */ + public static function get_plugins($type, $typerootdir, $typeclass) { + global $CFG; + $formats = parent::get_plugins($type, $typerootdir, $typeclass); + require_once($CFG->dirroot.'/course/lib.php'); + $order = get_sorted_course_formats(); + $sortedformats = array(); + foreach ($order as $formatname) { + $sortedformats[$formatname] = $formats[$formatname]; + } + return $sortedformats; + } + + public function get_settings_section_name() { + return 'formatsetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + public function is_uninstall_allowed() { + if ($this->name !== get_config('moodlecourse', 'format') && $this->name !== 'site') { + return true; + } else { + return false; + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/settings.php', array('section'=>'manageformats')); + } + + public function get_uninstall_extra_warning() { + global $DB; + + $coursecount = $DB->count_records('course', array('format' => $this->name)); + + if (!$coursecount) { + return ''; + } + + $defaultformat = $this->get_plugin_manager()->plugin_name('format_'.get_config('moodlecourse', 'format')); + $message = get_string( + 'formatuninstallwithcourses', 'core_admin', + (object)array('count' => $coursecount, 'format' => $this->displayname, + 'defaultformat' => $defaultformat)); + + return $message; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $DB; + + if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $this->name) { + $courses = $DB->get_records('course', array('format' => $this->name), 'id'); + $data = (object)array('id' => null, 'format' => $defaultformat); + foreach ($courses as $record) { + $data->id = $record->id; + update_course($data); + } + } + + $DB->delete_records('course_format_options', array('format' => $this->name)); + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/general.php b/lib/classes/plugininfo/general.php new file mode 100644 index 00000000000..e3d36d853c5 --- /dev/null +++ b/lib/classes/plugininfo/general.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + + +/** + * General class for all plugin types that do not have their own class + */ +class general extends base { + public function is_uninstall_allowed() { + return false; + } +} diff --git a/lib/classes/plugininfo/gradeexport.php b/lib/classes/plugininfo/gradeexport.php new file mode 100644 index 00000000000..c3f47d2aa4a --- /dev/null +++ b/lib/classes/plugininfo/gradeexport.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class gradeexport extends base { + + public function is_uninstall_allowed() { + return true; + } +} diff --git a/lib/classes/plugininfo/gradeimport.php b/lib/classes/plugininfo/gradeimport.php new file mode 100644 index 00000000000..7fbf6742a57 --- /dev/null +++ b/lib/classes/plugininfo/gradeimport.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class gradeimport extends base { + + public function is_uninstall_allowed() { + return true; + } +} diff --git a/lib/classes/plugininfo/gradereport.php b/lib/classes/plugininfo/gradereport.php new file mode 100644 index 00000000000..cb2a3851158 --- /dev/null +++ b/lib/classes/plugininfo/gradereport.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class gradereport extends base { + + public function is_uninstall_allowed() { + return true; + } +} diff --git a/lib/classes/plugininfo/gradingform.php b/lib/classes/plugininfo/gradingform.php new file mode 100644 index 00000000000..77137af993b --- /dev/null +++ b/lib/classes/plugininfo/gradingform.php @@ -0,0 +1,36 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class gradingform extends base { + + public function is_uninstall_allowed() { + return false; + } +} diff --git a/lib/classes/plugininfo/local.php b/lib/classes/plugininfo/local.php new file mode 100644 index 00000000000..f1d0e79f8bd --- /dev/null +++ b/lib/classes/plugininfo/local.php @@ -0,0 +1,70 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for local plugins + */ +class local extends base { + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/localplugins.php'); + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (file_exists($this->full_path('settings.php'))) { + include($this->full_path('settings.php')); + } + } +} diff --git a/lib/classes/plugininfo/message.php b/lib/classes/plugininfo/message.php new file mode 100644 index 00000000000..9f87f57e3be --- /dev/null +++ b/lib/classes/plugininfo/message.php @@ -0,0 +1,104 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for messaging processors + */ +class message extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + return $DB->get_records_menu('message_processors', array('enabled'=>1), 'name ASC', 'name, name AS val'); + } + + public function get_settings_section_name() { + return 'messagesetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig) { + return; + } + $section = $this->get_settings_section_name(); + + $settings = null; + $processors = get_message_processors(); + if (isset($processors[$this->name])) { + $processor = $processors[$this->name]; + if ($processor->available && $processor->hassettings) { + $settings = new admin_settingpage($section, $this->displayname, + 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + } + } + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/message.php'); + } + + public function is_uninstall_allowed() { + return true; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $CFG; + + require_once($CFG->libdir.'/messagelib.php'); + message_processor_uninstall($this->name); + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/mnetservice.php b/lib/classes/plugininfo/mnetservice.php new file mode 100644 index 00000000000..9809080a4e8 --- /dev/null +++ b/lib/classes/plugininfo/mnetservice.php @@ -0,0 +1,42 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class representing an MNet service + */ +class mnetservice extends base { + + public function is_enabled() { + global $CFG; + + if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') { + return false; + } else { + return parent::is_enabled(); + } + } +} diff --git a/lib/classes/plugininfo/mod.php b/lib/classes/plugininfo/mod.php new file mode 100644 index 00000000000..d936fbc69eb --- /dev/null +++ b/lib/classes/plugininfo/mod.php @@ -0,0 +1,200 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for activity modules + */ +class mod extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + return $DB->get_records_menu('modules', array('visible'=>1), 'name ASC', 'name, name AS val'); + } + + /** + * Magic method getter, redirects to read only values. + * + * For module plugins we pretend the object has 'visible' property for compatibility + * with plugins developed for Moodle version below 2.4 + * + * @param string $name + * @return mixed + */ + public function __get($name) { + if ($name === 'visible') { + debugging('This is now an instance of plugininfo_mod, please use $module->is_enabled() instead of $module->visible', DEBUG_DEVELOPER); + return ($this->is_enabled() !== false); + } + return parent::__get($name); + } + + public function init_display_name() { + if (get_string_manager()->string_exists('pluginname', $this->component)) { + $this->displayname = get_string('pluginname', $this->component); + } else { + $this->displayname = get_string('modulename', $this->component); + } + } + + public function get_settings_section_name() { + return 'modsetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $module = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + /** + * Allow all activity modules but Forum to be uninstalled. + * + * This exception for the Forum has been hard-coded in Moodle since ages, + * we may want to re-think it one day. + */ + public function is_uninstall_allowed() { + if ($this->name === 'forum') { + return false; + } else { + return true; + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/modules.php'); + } + + /** + * Return warning with number of activities and number of affected courses. + * + * @return string + */ + public function get_uninstall_extra_warning() { + global $DB; + + if (!$module = $DB->get_record('modules', array('name'=>$this->name))) { + return ''; + } + + if (!$count = $DB->count_records('course_modules', array('module'=>$module->id))) { + return ''; + } + + $sql = "SELECT COUNT('x') + FROM ( + SELECT course + FROM {course_modules} + WHERE module = :mid + GROUP BY course + ) c"; + $courses = $DB->count_records_sql($sql, array('mid'=>$module->id)); + + return '

'.get_string('uninstallextraconfirmmod', 'core_plugin', array('instances'=>$count, 'courses'=>$courses)).'

'; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $DB, $CFG; + + if (!$module = $DB->get_record('modules', array('name' => $this->name))) { + parent::uninstall_cleanup(); + return; + } + + // Delete all the relevant instances from all course sections. + if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) { + foreach ($coursemods as $coursemod) { + // Do not verify results, there is not much we can do anyway. + delete_mod_from_section($coursemod->id, $coursemod->section); + } + } + + // Increment course.cacherev for courses that used this module. + // This will force cache rebuilding on the next request. + increment_revision_number('course', 'cacherev', + "id IN (SELECT DISTINCT course + FROM {course_modules} + WHERE module=?)", + array($module->id)); + + // Delete all the course module records. + $DB->delete_records('course_modules', array('module' => $module->id)); + + // Delete module contexts. + if ($coursemods) { + foreach ($coursemods as $coursemod) { + \context_helper::delete_instance(CONTEXT_MODULE, $coursemod->id); + } + } + + // Delete the module entry itself. + $DB->delete_records('modules', array('name' => $module->name)); + + // Cleanup the gradebook. + require_once($CFG->libdir.'/gradelib.php'); + grade_uninstalled_module($module->name); + + // Do not look for legacy $module->name . '_uninstall any more, + // they should have migrated to db/uninstall.php by now. + + parent::uninstall_cleanup(); + } +} diff --git a/lib/classes/plugininfo/plagiarism.php b/lib/classes/plugininfo/plagiarism.php new file mode 100644 index 00000000000..372599c0fa8 --- /dev/null +++ b/lib/classes/plugininfo/plagiarism.php @@ -0,0 +1,67 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_externalpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for plagiarism plugins + */ +class plagiarism extends base { + + public function get_settings_section_name() { + return 'plagiarism'. $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + // No redirects here!!! + $section = $this->get_settings_section_name(); + $settingsurl = new moodle_url($this->get_dir().'/settings.php'); + $settings = new admin_externalpage($section, $this->displayname, $settingsurl, + 'moodle/site:config', $this->is_enabled() === false); + $adminroot->add($parentnodename, $settings); + } + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/plagiarism.php'); + } +} diff --git a/lib/classes/plugininfo/portfolio.php b/lib/classes/plugininfo/portfolio.php new file mode 100644 index 00000000000..4ae156d5463 --- /dev/null +++ b/lib/classes/plugininfo/portfolio.php @@ -0,0 +1,57 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use core_component, core_plugin_manager, moodle_url, coding_exception; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for portfolios + */ +class portfolio extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + + $enabled = array(); + $rs = $DB->get_recordset('portfolio_instance', array('visible'=>1), 'plugin ASC', 'plugin'); + foreach ($rs as $repository) { + $enabled[$repository->plugin] = $repository->plugin; + } + + return $enabled; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/portfolio.php'); + } +} diff --git a/lib/classes/plugininfo/profilefield.php b/lib/classes/plugininfo/profilefield.php new file mode 100644 index 00000000000..14b8b67818f --- /dev/null +++ b/lib/classes/plugininfo/profilefield.php @@ -0,0 +1,47 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class profilefield extends base { + + public function is_uninstall_allowed() { + global $DB; + return !$DB->record_exists('user_info_field', array('datatype'=>$this->name)); + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/user/profile/index.php'); + } +} diff --git a/lib/classes/plugininfo/qbehaviour.php b/lib/classes/plugininfo/qbehaviour.php new file mode 100644 index 00000000000..b33e22601a0 --- /dev/null +++ b/lib/classes/plugininfo/qbehaviour.php @@ -0,0 +1,108 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, core_plugin_manager; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for question behaviours. + */ +class qbehaviour extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + $plugins = core_plugin_manager::instance()->get_installed_plugins('qbehaviour'); + if (!$plugins) { + return array(); + } + if ($disabled = get_config('question', 'disabledbehaviours')) { + $disabled = explode(',', $disabled); + } else { + $disabled = array(); + } + + $enabled = array(); + foreach ($plugins as $plugin => $version) { + if (in_array($plugin, $disabled)) { + continue; + } + $enabled[$plugin] = $plugin; + } + + return $enabled; + } + + public function is_uninstall_allowed() { + global $DB; + + return !$DB->record_exists('question_attempts', array('behaviour' => $this->name)); + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + if ($disabledbehaviours = get_config('question', 'disabledbehaviours')) { + $disabledbehaviours = explode(',', $disabledbehaviours); + $disabledbehaviours = array_unique($disabledbehaviours); + } else { + $disabledbehaviours = array(); + } + if (($key = array_search($this->name, $disabledbehaviours)) !== false) { + unset($disabledbehaviours[$key]); + set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question'); + } + + if ($behaviourorder = get_config('question', 'behavioursortorder')) { + $behaviourorder = explode(',', $behaviourorder); + $behaviourorder = array_unique($behaviourorder); + } else { + $behaviourorder = array(); + } + if (($key = array_search($this->name, $behaviourorder)) !== false) { + unset($behaviourorder[$key]); + set_config('behavioursortorder', implode(',', $behaviourorder), 'question'); + } + + parent::uninstall_cleanup(); + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/qbehaviours.php'); + } +} + diff --git a/lib/classes/plugininfo/qformat.php b/lib/classes/plugininfo/qformat.php new file mode 100644 index 00000000000..40ac26edb8e --- /dev/null +++ b/lib/classes/plugininfo/qformat.php @@ -0,0 +1,35 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for question types + */ +class qformat extends base { + public function is_uninstall_allowed() { + return true; + } +} diff --git a/lib/classes/plugininfo/qtype.php b/lib/classes/plugininfo/qtype.php new file mode 100644 index 00000000000..375ee1a10f0 --- /dev/null +++ b/lib/classes/plugininfo/qtype.php @@ -0,0 +1,126 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage, core_plugin_manager; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for question types + */ +class qtype extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + + $plugins = core_plugin_manager::instance()->get_installed_plugins('qtype'); + if (!$plugins) { + return array(); + } + $installed = array(); + foreach ($plugins as $plugin => $version) { + $installed[] = $plugin.'_disabled'; + } + + list($installed, $params) = $DB->get_in_or_equal($installed, SQL_PARAMS_NAMED); + $disabled = $DB->get_records_select('config_plugins', "name $installed AND plugin = 'question'", $params, 'plugin ASC'); + foreach ($disabled as $conf) { + if (empty($conf->value)) { + continue; + } + $name = substr($conf->name, 0, -9); + unset($plugins[$name]); + } + + $enabled = array(); + foreach ($plugins as $plugin => $version) { + $enabled[$plugin] = $plugin; + } + + return $enabled; + } + + public function is_uninstall_allowed() { + global $DB; + + return !$DB->record_exists('question', array('qtype' => $this->name)); + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/qtypes.php'); + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + // Delete any question configuration records mentioning this plugin. + unset_config($this->name . '_disabled', 'question'); + unset_config($this->name . '_sortorder', 'question'); + + parent::uninstall_cleanup(); + } + + public function get_settings_section_name() { + return 'qtypesetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $qtype = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = null; + $systemcontext = \context_system::instance(); + if (($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) && + file_exists($this->full_path('settings.php'))) { + $settings = new admin_settingpage($section, $this->displayname, + 'moodle/question:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + } + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } +} diff --git a/lib/classes/plugininfo/report.php b/lib/classes/plugininfo/report.php new file mode 100644 index 00000000000..c3eb73232b6 --- /dev/null +++ b/lib/classes/plugininfo/report.php @@ -0,0 +1,46 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class report extends base { + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/reports.php'); + } +} diff --git a/lib/classes/plugininfo/repository.php b/lib/classes/plugininfo/repository.php new file mode 100644 index 00000000000..487b1378c3a --- /dev/null +++ b/lib/classes/plugininfo/repository.php @@ -0,0 +1,70 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_externalpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for repositories + */ +class repository extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + return $DB->get_records_menu('repository', array('visible'=>1), 'type ASC', 'type, type AS val'); + } + + public function get_settings_section_name() { + return 'repositorysettings'.$this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + if (!$this->is_installed_and_upgraded()) { + return; + } + + if ($hassiteconfig && $this->is_enabled()) { + // Completely no access to repository setting when it is not enabled. + $sectionname = $this->get_settings_section_name(); + $settingsurl = new moodle_url('/admin/repository.php', + array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name)); + $settings = new admin_externalpage($sectionname, $this->displayname, + $settingsurl, 'moodle/site:config', false); + $adminroot->add($parentnodename, $settings); + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/repository.php'); + } +} diff --git a/lib/classes/plugininfo/theme.php b/lib/classes/plugininfo/theme.php new file mode 100644 index 00000000000..d5106ea9676 --- /dev/null +++ b/lib/classes/plugininfo/theme.php @@ -0,0 +1,80 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for themes + */ +class theme extends base { + public function is_uninstall_allowed() { + global $CFG; + + if ($this->name === 'standard' or $this->name === 'base' or $this->name === 'bootstrapbase') { + // All of these are protected for now. + return false; + } + + if (!empty($CFG->theme) and $CFG->theme === $this->name) { + // Cannot uninstall default theme. + return false; + } + + return true; + } + + /** + * Pre-uninstall hook. + * + * This is intended for disabling of plugin, some DB table purging, etc. + * + * NOTE: to be called from uninstall_plugin() only. + * @private + */ + public function uninstall_cleanup() { + global $DB; + + $DB->set_field('course', 'theme', '', array('theme'=>$this->name)); + $DB->set_field('course_categories', 'theme', '', array('theme'=>$this->name)); + $DB->set_field('user', 'theme', '', array('theme'=>$this->name)); + $DB->set_field('mnet_host', 'theme', '', array('theme'=>$this->name)); + + unset_config('thememobile'); + unset_config('themetablet'); + unset_config('themelegacy'); + + parent::uninstall_cleanup(); + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/theme/index.php'); + } +} diff --git a/lib/classes/plugininfo/tool.php b/lib/classes/plugininfo/tool.php new file mode 100644 index 00000000000..d4e24ba7b71 --- /dev/null +++ b/lib/classes/plugininfo/tool.php @@ -0,0 +1,70 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for admin tool plugins + */ +class tool extends base { + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/tools.php'); + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (file_exists($this->full_path('settings.php'))) { + include($this->full_path('settings.php')); + } + } +} diff --git a/lib/classes/plugininfo/webservice.php b/lib/classes/plugininfo/webservice.php new file mode 100644 index 00000000000..dce102dc6c9 --- /dev/null +++ b/lib/classes/plugininfo/webservice.php @@ -0,0 +1,84 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use part_of_admin_tree, admin_settingpage; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class for webservice protocols + */ +class webservice extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $CFG; + + if (empty($CFG->enablewebservices) or empty($CFG->webserviceprotocols)) { + return array(); + } + + $enabled = array(); + foreach (explode(',', $CFG->webserviceprotocols) as $protocol) { + $enabled[$protocol] = $protocol; + } + + return $enabled; + } + + public function get_settings_section_name() { + return 'webservicesetting' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $webservice = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + public function is_uninstall_allowed() { + return false; + } +} diff --git a/lib/classes/update/checker.php b/lib/classes/update/checker.php new file mode 100644 index 00000000000..f3d358239a5 --- /dev/null +++ b/lib/classes/update/checker.php @@ -0,0 +1,809 @@ +. + +/** + * Defines classes used for updates. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\update; + +use html_writer, coding_exception, core_component; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Singleton class that handles checking for available updates + */ +class checker { + + /** @var \core\update\checker holds the singleton instance */ + protected static $singletoninstance; + /** @var null|int the timestamp of when the most recent response was fetched */ + protected $recentfetch = null; + /** @var null|array the recent response from the update notification provider */ + protected $recentresponse = null; + /** @var null|string the numerical version of the local Moodle code */ + protected $currentversion = null; + /** @var null|string the release info of the local Moodle code */ + protected $currentrelease = null; + /** @var null|string branch of the local Moodle code */ + protected $currentbranch = null; + /** @var array of (string)frankestyle => (string)version list of additional plugins deployed at this site */ + protected $currentplugins = array(); + + /** + * Direct initiation not allowed, use the factory method {@link self::instance()} + */ + protected function __construct() { + } + + /** + * Sorry, this is singleton + */ + protected function __clone() { + } + + /** + * Factory method for this class + * + * @return \core\update\checker the singleton instance + */ + public static function instance() { + if (is_null(self::$singletoninstance)) { + self::$singletoninstance = new self(); + } + return self::$singletoninstance; + } + + /** + * Reset any caches + * @param bool $phpunitreset + */ + public static function reset_caches($phpunitreset = false) { + if ($phpunitreset) { + self::$singletoninstance = null; + } + } + + /** + * Returns the timestamp of the last execution of {@link fetch()} + * + * @return int|null null if it has never been executed or we don't known + */ + public function get_last_timefetched() { + + $this->restore_response(); + + if (!empty($this->recentfetch)) { + return $this->recentfetch; + + } else { + return null; + } + } + + /** + * Fetches the available update status from the remote site + * + * @throws checker_exception + */ + public function fetch() { + $response = $this->get_response(); + $this->validate_response($response); + $this->store_response($response); + } + + /** + * Returns the available update information for the given component + * + * This method returns null if the most recent response does not contain any information + * about it. The returned structure is an array of available updates for the given + * component. Each update info is an object with at least one property called + * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'. + * + * For the 'core' component, the method returns real updates only (those with higher version). + * For all other components, the list of all known remote updates is returned and the caller + * (usually the {@link core_plugin_manager}) is supposed to make the actual comparison of versions. + * + * @param string $component frankenstyle + * @param array $options with supported keys 'minmaturity' and/or 'notifybuilds' + * @return null|array null or array of \core\update\info objects + */ + public function get_update_info($component, array $options = array()) { + + if (!isset($options['minmaturity'])) { + $options['minmaturity'] = 0; + } + + if (!isset($options['notifybuilds'])) { + $options['notifybuilds'] = false; + } + + if ($component === 'core') { + $this->load_current_environment(); + } + + $this->restore_response(); + + if (empty($this->recentresponse['updates'][$component])) { + return null; + } + + $updates = array(); + foreach ($this->recentresponse['updates'][$component] as $info) { + $update = new info($component, $info); + if (isset($update->maturity) and ($update->maturity < $options['minmaturity'])) { + continue; + } + if ($component === 'core') { + if ($update->version <= $this->currentversion) { + continue; + } + if (empty($options['notifybuilds']) and $this->is_same_release($update->release)) { + continue; + } + } + $updates[] = $update; + } + + if (empty($updates)) { + return null; + } + + return $updates; + } + + /** + * The method being run via cron.php + */ + public function cron() { + global $CFG; + + if (!$this->cron_autocheck_enabled()) { + $this->cron_mtrace('Automatic check for available updates not enabled, skipping.'); + return; + } + + $now = $this->cron_current_timestamp(); + + if ($this->cron_has_fresh_fetch($now)) { + $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.'); + return; + } + + if ($this->cron_has_outdated_fetch($now)) { + $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', ''); + $this->cron_execute(); + return; + } + + $offset = $this->cron_execution_offset(); + $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time + if ($now > $start + $offset) { + $this->cron_mtrace('Regular daily check for available updates ... ', ''); + $this->cron_execute(); + return; + } + } + + /* === End of public API === */ + + /** + * Makes cURL request to get data from the remote site + * + * @return string raw request result + * @throws checker_exception + */ + protected function get_response() { + global $CFG; + require_once($CFG->libdir.'/filelib.php'); + + $curl = new \curl(array('proxy' => true)); + $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params(), $this->prepare_request_options()); + $curlerrno = $curl->get_errno(); + if (!empty($curlerrno)) { + throw new checker_exception('err_response_curl', 'cURL error '.$curlerrno.': '.$curl->error); + } + $curlinfo = $curl->get_info(); + if ($curlinfo['http_code'] != 200) { + throw new checker_exception('err_response_http_code', $curlinfo['http_code']); + } + return $response; + } + + /** + * Makes sure the response is valid, has correct API format etc. + * + * @param string $response raw response as returned by the {@link self::get_response()} + * @throws checker_exception + */ + protected function validate_response($response) { + + $response = $this->decode_response($response); + + if (empty($response)) { + throw new checker_exception('err_response_empty'); + } + + if (empty($response['status']) or $response['status'] !== 'OK') { + throw new checker_exception('err_response_status', $response['status']); + } + + if (empty($response['apiver']) or $response['apiver'] !== '1.2') { + throw new checker_exception('err_response_format_version', $response['apiver']); + } + + if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) { + throw new checker_exception('err_response_target_version', $response['forbranch']); + } + } + + /** + * Decodes the raw string response from the update notifications provider + * + * @param string $response as returned by {@link self::get_response()} + * @return array decoded response structure + */ + protected function decode_response($response) { + return json_decode($response, true); + } + + /** + * Stores the valid fetched response for later usage + * + * This implementation uses the config_plugins table as the permanent storage. + * + * @param string $response raw valid data returned by {@link self::get_response()} + */ + protected function store_response($response) { + + set_config('recentfetch', time(), 'core_plugin'); + set_config('recentresponse', $response, 'core_plugin'); + + $this->restore_response(true); + } + + /** + * Loads the most recent raw response record we have fetched + * + * After this method is called, $this->recentresponse is set to an array. If the + * array is empty, then either no data have been fetched yet or the fetched data + * do not have expected format (and thence they are ignored and a debugging + * message is displayed). + * + * This implementation uses the config_plugins table as the permanent storage. + * + * @param bool $forcereload reload even if it was already loaded + */ + protected function restore_response($forcereload = false) { + + if (!$forcereload and !is_null($this->recentresponse)) { + // We already have it, nothing to do. + return; + } + + $config = get_config('core_plugin'); + + if (!empty($config->recentresponse) and !empty($config->recentfetch)) { + try { + $this->validate_response($config->recentresponse); + $this->recentfetch = $config->recentfetch; + $this->recentresponse = $this->decode_response($config->recentresponse); + } catch (checker_exception $e) { + // The server response is not valid. Behave as if no data were fetched yet. + // This may happen when the most recent update info (cached locally) has been + // fetched with the previous branch of Moodle (like during an upgrade from 2.x + // to 2.y) or when the API of the response has changed. + $this->recentresponse = array(); + } + + } else { + $this->recentresponse = array(); + } + } + + /** + * Compares two raw {@link $recentresponse} records and returns the list of changed updates + * + * This method is used to populate potential update info to be sent to site admins. + * + * @param array $old + * @param array $new + * @throws checker_exception + * @return array parts of $new['updates'] that have changed + */ + protected function compare_responses(array $old, array $new) { + + if (empty($new)) { + return array(); + } + + if (!array_key_exists('updates', $new)) { + throw new checker_exception('err_response_format'); + } + + if (empty($old)) { + return $new['updates']; + } + + if (!array_key_exists('updates', $old)) { + throw new checker_exception('err_response_format'); + } + + $changes = array(); + + foreach ($new['updates'] as $newcomponent => $newcomponentupdates) { + if (empty($old['updates'][$newcomponent])) { + $changes[$newcomponent] = $newcomponentupdates; + continue; + } + foreach ($newcomponentupdates as $newcomponentupdate) { + $inold = false; + foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) { + if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) { + $inold = true; + } + } + if (!$inold) { + if (!isset($changes[$newcomponent])) { + $changes[$newcomponent] = array(); + } + $changes[$newcomponent][] = $newcomponentupdate; + } + } + } + + return $changes; + } + + /** + * Returns the URL to send update requests to + * + * During the development or testing, you can set $CFG->alternativeupdateproviderurl + * to a custom URL that will be used. Otherwise the standard URL will be returned. + * + * @return string URL + */ + protected function prepare_request_url() { + global $CFG; + + if (!empty($CFG->config_php_settings['alternativeupdateproviderurl'])) { + return $CFG->config_php_settings['alternativeupdateproviderurl']; + } else { + return 'https://download.moodle.org/api/1.2/updates.php'; + } + } + + /** + * Sets the properties currentversion, currentrelease, currentbranch and currentplugins + * + * @param bool $forcereload + */ + protected function load_current_environment($forcereload=false) { + global $CFG; + + if (!is_null($this->currentversion) and !$forcereload) { + // Nothing to do. + return; + } + + $version = null; + $release = null; + + require($CFG->dirroot.'/version.php'); + $this->currentversion = $version; + $this->currentrelease = $release; + $this->currentbranch = moodle_major_version(true); + + $pluginman = \core_plugin_manager::instance(); + foreach ($pluginman->get_plugins() as $type => $plugins) { + foreach ($plugins as $plugin) { + if (!$plugin->is_standard()) { + $this->currentplugins[$plugin->component] = $plugin->versiondisk; + } + } + } + } + + /** + * Returns the list of HTTP params to be sent to the updates provider URL + * + * @return array of (string)param => (string)value + */ + protected function prepare_request_params() { + global $CFG; + + $this->load_current_environment(); + $this->restore_response(); + + $params = array(); + $params['format'] = 'json'; + + if (isset($this->recentresponse['ticket'])) { + $params['ticket'] = $this->recentresponse['ticket']; + } + + if (isset($this->currentversion)) { + $params['version'] = $this->currentversion; + } else { + throw new coding_exception('Main Moodle version must be already known here'); + } + + if (isset($this->currentbranch)) { + $params['branch'] = $this->currentbranch; + } else { + throw new coding_exception('Moodle release must be already known here'); + } + + $plugins = array(); + foreach ($this->currentplugins as $plugin => $version) { + $plugins[] = $plugin.'@'.$version; + } + if (!empty($plugins)) { + $params['plugins'] = implode(',', $plugins); + } + + return $params; + } + + /** + * Returns the list of cURL options to use when fetching available updates data + * + * @return array of (string)param => (string)value + */ + protected function prepare_request_options() { + $options = array( + 'CURLOPT_SSL_VERIFYHOST' => 2, // This is the default in {@link curl} class but just in case. + 'CURLOPT_SSL_VERIFYPEER' => true, + ); + + return $options; + } + + /** + * Returns the current timestamp + * + * @return int the timestamp + */ + protected function cron_current_timestamp() { + return time(); + } + + /** + * Output cron debugging info + * + * @see mtrace() + * @param string $msg output message + * @param string $eol end of line + */ + protected function cron_mtrace($msg, $eol = PHP_EOL) { + mtrace($msg, $eol); + } + + /** + * Decide if the autocheck feature is disabled in the server setting + * + * @return bool true if autocheck enabled, false if disabled + */ + protected function cron_autocheck_enabled() { + global $CFG; + + if (empty($CFG->updateautocheck)) { + return false; + } else { + return true; + } + } + + /** + * Decide if the recently fetched data are still fresh enough + * + * @param int $now current timestamp + * @return bool true if no need to re-fetch, false otherwise + */ + protected function cron_has_fresh_fetch($now) { + $recent = $this->get_last_timefetched(); + + if (empty($recent)) { + return false; + } + + if ($now < $recent) { + $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!'); + return true; + } + + if ($now - $recent > 24 * HOURSECS) { + return false; + } + + return true; + } + + /** + * Decide if the fetch is outadated or even missing + * + * @param int $now current timestamp + * @return bool false if no need to re-fetch, true otherwise + */ + protected function cron_has_outdated_fetch($now) { + $recent = $this->get_last_timefetched(); + + if (empty($recent)) { + return true; + } + + if ($now < $recent) { + $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!'); + return false; + } + + if ($now - $recent > 48 * HOURSECS) { + return true; + } + + return false; + } + + /** + * Returns the cron execution offset for this site + * + * The main {@link self::cron()} is supposed to run every night in some random time + * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called + * execution offset, that is the amount of time after 01:00 AM. The offset value is + * initially generated randomly and then used consistently at the site. This way, the + * regular checks against the download.moodle.org server are spread in time. + * + * @return int the offset number of seconds from range 1 sec to 5 hours + */ + protected function cron_execution_offset() { + global $CFG; + + if (empty($CFG->updatecronoffset)) { + set_config('updatecronoffset', rand(1, 5 * HOURSECS)); + } + + return $CFG->updatecronoffset; + } + + /** + * Fetch available updates info and eventually send notification to site admins + */ + protected function cron_execute() { + + try { + $this->restore_response(); + $previous = $this->recentresponse; + $this->fetch(); + $this->restore_response(true); + $current = $this->recentresponse; + $changes = $this->compare_responses($previous, $current); + $notifications = $this->cron_notifications($changes); + $this->cron_notify($notifications); + $this->cron_mtrace('done'); + } catch (checker_exception $e) { + $this->cron_mtrace('FAILED!'); + } + } + + /** + * Given the list of changes in available updates, pick those to send to site admins + * + * @param array $changes as returned by {@link self::compare_responses()} + * @return array of \core\update\info objects to send to site admins + */ + protected function cron_notifications(array $changes) { + global $CFG; + + $notifications = array(); + $pluginman = \core_plugin_manager::instance(); + $plugins = $pluginman->get_plugins(true); + + foreach ($changes as $component => $componentchanges) { + if (empty($componentchanges)) { + continue; + } + $componentupdates = $this->get_update_info($component, + array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds)); + if (empty($componentupdates)) { + continue; + } + // Notify only about those $componentchanges that are present in $componentupdates + // to respect the preferences. + foreach ($componentchanges as $componentchange) { + foreach ($componentupdates as $componentupdate) { + if ($componentupdate->version == $componentchange['version']) { + if ($component == 'core') { + // In case of 'core', we already know that the $componentupdate + // is a real update with higher version ({@see self::get_update_info()}). + // We just perform additional check for the release property as there + // can be two Moodle releases having the same version (e.g. 2.4.0 and 2.5dev shortly + // after the release). We can do that because we have the release info + // always available for the core. + if ((string)$componentupdate->release === (string)$componentchange['release']) { + $notifications[] = $componentupdate; + } + } else { + // Use the core_plugin_manager to check if the detected $componentchange + // is a real update with higher version. That is, the $componentchange + // is present in the array of {@link \core\update\info} objects + // returned by the plugin's available_updates() method. + list($plugintype, $pluginname) = core_component::normalize_component($component); + if (!empty($plugins[$plugintype][$pluginname])) { + $availableupdates = $plugins[$plugintype][$pluginname]->available_updates(); + if (!empty($availableupdates)) { + foreach ($availableupdates as $availableupdate) { + if ($availableupdate->version == $componentchange['version']) { + $notifications[] = $componentupdate; + } + } + } + } + } + } + } + } + } + + return $notifications; + } + + /** + * Sends the given notifications to site admins via messaging API + * + * @param array $notifications array of \core\update\info objects to send + */ + protected function cron_notify(array $notifications) { + global $CFG; + + if (empty($notifications)) { + return; + } + + $admins = get_admins(); + + if (empty($admins)) { + return; + } + + $this->cron_mtrace('sending notifications ... ', ''); + + $text = get_string('updatenotifications', 'core_admin') . PHP_EOL; + $html = html_writer::tag('h1', get_string('updatenotifications', 'core_admin')) . PHP_EOL; + + $coreupdates = array(); + $pluginupdates = array(); + + foreach ($notifications as $notification) { + if ($notification->component == 'core') { + $coreupdates[] = $notification; + } else { + $pluginupdates[] = $notification; + } + } + + if (!empty($coreupdates)) { + $text .= PHP_EOL . get_string('updateavailable', 'core_admin') . PHP_EOL; + $html .= html_writer::tag('h2', get_string('updateavailable', 'core_admin')) . PHP_EOL; + $html .= html_writer::start_tag('ul') . PHP_EOL; + foreach ($coreupdates as $coreupdate) { + $html .= html_writer::start_tag('li'); + if (isset($coreupdate->release)) { + $text .= get_string('updateavailable_release', 'core_admin', $coreupdate->release); + $html .= html_writer::tag('strong', get_string('updateavailable_release', 'core_admin', $coreupdate->release)); + } + if (isset($coreupdate->version)) { + $text .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version); + $html .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version); + } + if (isset($coreupdate->maturity)) { + $text .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')'; + $html .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')'; + } + $text .= PHP_EOL; + $html .= html_writer::end_tag('li') . PHP_EOL; + } + $text .= PHP_EOL; + $html .= html_writer::end_tag('ul') . PHP_EOL; + + $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/index.php'); + $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL; + $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/index.php', $CFG->wwwroot.'/'.$CFG->admin.'/index.php')); + $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL; + } + + if (!empty($pluginupdates)) { + $text .= PHP_EOL . get_string('updateavailableforplugin', 'core_admin') . PHP_EOL; + $html .= html_writer::tag('h2', get_string('updateavailableforplugin', 'core_admin')) . PHP_EOL; + + $html .= html_writer::start_tag('ul') . PHP_EOL; + foreach ($pluginupdates as $pluginupdate) { + $html .= html_writer::start_tag('li'); + $text .= get_string('pluginname', $pluginupdate->component); + $html .= html_writer::tag('strong', get_string('pluginname', $pluginupdate->component)); + + $text .= ' ('.$pluginupdate->component.')'; + $html .= ' ('.$pluginupdate->component.')'; + + $text .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version); + $html .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version); + + $text .= PHP_EOL; + $html .= html_writer::end_tag('li') . PHP_EOL; + } + $text .= PHP_EOL; + $html .= html_writer::end_tag('ul') . PHP_EOL; + + $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php'); + $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL; + $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/plugins.php', $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php')); + $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL; + } + + $a = array('siteurl' => $CFG->wwwroot); + $text .= get_string('updatenotificationfooter', 'core_admin', $a) . PHP_EOL; + $a = array('siteurl' => html_writer::link($CFG->wwwroot, $CFG->wwwroot)); + $html .= html_writer::tag('footer', html_writer::tag('p', get_string('updatenotificationfooter', 'core_admin', $a), + array('style' => 'font-size:smaller; color:#333;'))); + + foreach ($admins as $admin) { + $message = new \stdClass(); + $message->component = 'moodle'; + $message->name = 'availableupdate'; + $message->userfrom = get_admin(); + $message->userto = $admin; + $message->subject = get_string('updatenotificationsubject', 'core_admin', array('siteurl' => $CFG->wwwroot)); + $message->fullmessage = $text; + $message->fullmessageformat = FORMAT_PLAIN; + $message->fullmessagehtml = $html; + $message->smallmessage = get_string('updatenotifications', 'core_admin'); + $message->notification = 1; + message_send($message); + } + } + + /** + * Compare two release labels and decide if they are the same + * + * @param string $remote release info of the available update + * @param null|string $local release info of the local code, defaults to $release defined in version.php + * @return boolean true if the releases declare the same minor+major version + */ + protected function is_same_release($remote, $local=null) { + + if (is_null($local)) { + $this->load_current_environment(); + $local = $this->currentrelease; + } + + $pattern = '/^([0-9\.\+]+)([^(]*)/'; + + preg_match($pattern, $remote, $remotematches); + preg_match($pattern, $local, $localmatches); + + $remotematches[1] = str_replace('+', '', $remotematches[1]); + $localmatches[1] = str_replace('+', '', $localmatches[1]); + + if ($remotematches[1] === $localmatches[1] and rtrim($remotematches[2]) === rtrim($localmatches[2])) { + return true; + } else { + return false; + } + } +} diff --git a/lib/classes/update/checker_exception.php b/lib/classes/update/checker_exception.php new file mode 100644 index 00000000000..77d8eb9a0c0 --- /dev/null +++ b/lib/classes/update/checker_exception.php @@ -0,0 +1,40 @@ +. + +/** + * Defines classes used for updates. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\update; + +defined('MOODLE_INTERNAL') || die(); + +/** + * General exception thrown by the {@link \core\update\checker} class + */ +class checker_exception extends \moodle_exception { + + /** + * @param string $errorcode exception description identifier + * @param mixed $debuginfo debugging data to display + */ + public function __construct($errorcode, $debuginfo=null) { + parent::__construct($errorcode, 'core_plugin', '', null, print_r($debuginfo, true)); + } +} diff --git a/lib/classes/update/deployer.php b/lib/classes/update/deployer.php new file mode 100644 index 00000000000..89c47d68bdf --- /dev/null +++ b/lib/classes/update/deployer.php @@ -0,0 +1,586 @@ +. + +/** + * Defines classes used for updates. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\update; + +use coding_exception, core_component, moodle_url; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Implements a communication bridge to the mdeploy.php utility + */ +class deployer { + + const HTTP_PARAM_PREFIX = 'updteautodpldata_'; // Hey, even Google has not heard of such a prefix! So it MUST be safe :-p. + const HTTP_PARAM_CHECKER = 'datapackagesize'; // Name of the parameter that holds the number of items in the received data items. + + /** @var \core\update\deployer holds the singleton instance */ + protected static $singletoninstance; + /** @var moodle_url URL of a page that includes the deployer UI */ + protected $callerurl; + /** @var moodle_url URL to return after the deployment */ + protected $returnurl; + + /** + * Direct instantiation not allowed, use the factory method {@link self::instance()} + */ + protected function __construct() { + } + + /** + * Sorry, this is singleton + */ + protected function __clone() { + } + + /** + * Factory method for this class + * + * @return \core\update\deployer the singleton instance + */ + public static function instance() { + if (is_null(self::$singletoninstance)) { + self::$singletoninstance = new self(); + } + return self::$singletoninstance; + } + + /** + * Reset caches used by this script + * + * @param bool $phpunitreset is this called as a part of PHPUnit reset? + */ + public static function reset_caches($phpunitreset = false) { + if ($phpunitreset) { + self::$singletoninstance = null; + } + } + + /** + * Is automatic deployment enabled? + * + * @return bool + */ + public function enabled() { + global $CFG; + + if (!empty($CFG->disableupdateautodeploy)) { + // The feature is prohibited via config.php. + return false; + } + + return get_config('updateautodeploy'); + } + + /** + * Sets some base properties of the class to make it usable. + * + * @param moodle_url $callerurl the base URL of a script that will handle the class'es form data + * @param moodle_url $returnurl the final URL to return to when the deployment is finished + */ + public function initialize(moodle_url $callerurl, moodle_url $returnurl) { + + if (!$this->enabled()) { + throw new coding_exception('Unable to initialize the deployer, the feature is not enabled.'); + } + + $this->callerurl = $callerurl; + $this->returnurl = $returnurl; + } + + /** + * Has the deployer been initialized? + * + * Initialized deployer means that the following properties were set: + * callerurl, returnurl + * + * @return bool + */ + public function initialized() { + + if (!$this->enabled()) { + return false; + } + + if (empty($this->callerurl)) { + return false; + } + + if (empty($this->returnurl)) { + return false; + } + + return true; + } + + /** + * Returns a list of reasons why the deployment can not happen + * + * If the returned array is empty, the deployment seems to be possible. The returned + * structure is an associative array with keys representing individual impediments. + * Possible keys are: missingdownloadurl, missingdownloadmd5, notwritable. + * + * @param \core\update\info $info + * @return array + */ + public function deployment_impediments(info $info) { + + $impediments = array(); + + if (empty($info->download)) { + $impediments['missingdownloadurl'] = true; + } + + if (empty($info->downloadmd5)) { + $impediments['missingdownloadmd5'] = true; + } + + if (!empty($info->download) and !$this->update_downloadable($info->download)) { + $impediments['notdownloadable'] = true; + } + + if (!$this->component_writable($info->component)) { + $impediments['notwritable'] = true; + } + + return $impediments; + } + + /** + * Check to see if the current version of the plugin seems to be a checkout of an external repository. + * + * @see core_plugin_manager::plugin_external_source() + * @param \core\update\info $info + * @return false|string + */ + public function plugin_external_source(info $info) { + + $paths = core_component::get_plugin_types(); + list($plugintype, $pluginname) = core_component::normalize_component($info->component); + $pluginroot = $paths[$plugintype].'/'.$pluginname; + + if (is_dir($pluginroot.'/.git')) { + return 'git'; + } + + if (is_dir($pluginroot.'/CVS')) { + return 'cvs'; + } + + if (is_dir($pluginroot.'/.svn')) { + return 'svn'; + } + + return false; + } + + /** + * Prepares a renderable widget to confirm installation of an available update. + * + * @param \core\update\info $info component version to deploy + * @return \renderable + */ + public function make_confirm_widget(info $info) { + + if (!$this->initialized()) { + throw new coding_exception('Illegal method call - deployer not initialized.'); + } + + $params = $this->data_to_params(array( + 'updateinfo' => (array)$info, // See http://www.php.net/manual/en/language.types.array.php#language.types.array.casting . + )); + + $widget = new \single_button( + new moodle_url($this->callerurl, $params), + get_string('updateavailableinstall', 'core_admin'), + 'post' + ); + + return $widget; + } + + /** + * Prepares a renderable widget to execute installation of an available update. + * + * @param \core\update\info $info component version to deploy + * @param moodle_url $returnurl URL to return after the installation execution + * @return \renderable + */ + public function make_execution_widget(info $info, moodle_url $returnurl = null) { + global $CFG; + + if (!$this->initialized()) { + throw new coding_exception('Illegal method call - deployer not initialized.'); + } + + $pluginrootpaths = core_component::get_plugin_types(); + + list($plugintype, $pluginname) = core_component::normalize_component($info->component); + + if (empty($pluginrootpaths[$plugintype])) { + throw new coding_exception('Unknown plugin type root location', $plugintype); + } + + list($passfile, $password) = $this->prepare_authorization(); + + if (is_null($returnurl)) { + $returnurl = new moodle_url('/admin'); + } else { + $returnurl = $returnurl; + } + + $params = array( + 'upgrade' => true, + 'type' => $plugintype, + 'name' => $pluginname, + 'typeroot' => $pluginrootpaths[$plugintype], + 'package' => $info->download, + 'md5' => $info->downloadmd5, + 'dataroot' => $CFG->dataroot, + 'dirroot' => $CFG->dirroot, + 'passfile' => $passfile, + 'password' => $password, + 'returnurl' => $returnurl->out(false), + ); + + if (!empty($CFG->proxyhost)) { + // MDL-36973 - Beware - we should call just !is_proxybypass() here. But currently, our + // cURL wrapper class does not do it. So, to have consistent behaviour, we pass proxy + // setting regardless the $CFG->proxybypass setting. Once the {@link curl} class is + // fixed, the condition should be amended. + if (true or !is_proxybypass($info->download)) { + if (empty($CFG->proxyport)) { + $params['proxy'] = $CFG->proxyhost; + } else { + $params['proxy'] = $CFG->proxyhost.':'.$CFG->proxyport; + } + + if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) { + $params['proxyuserpwd'] = $CFG->proxyuser.':'.$CFG->proxypassword; + } + + if (!empty($CFG->proxytype)) { + $params['proxytype'] = $CFG->proxytype; + } + } + } + + $widget = new \single_button( + new moodle_url('/mdeploy.php', $params), + get_string('updateavailableinstall', 'core_admin'), + 'post' + ); + + return $widget; + } + + /** + * Returns array of data objects passed to this tool. + * + * @return array + */ + public function submitted_data() { + + $data = $this->params_to_data($_POST); + + if (empty($data) or empty($data[self::HTTP_PARAM_CHECKER])) { + return false; + } + + if (!empty($data['updateinfo']) and is_object($data['updateinfo'])) { + $updateinfo = $data['updateinfo']; + if (!empty($updateinfo->component) and !empty($updateinfo->version)) { + $data['updateinfo'] = new info($updateinfo->component, (array)$updateinfo); + } + } + + if (!empty($data['callerurl'])) { + $data['callerurl'] = new moodle_url($data['callerurl']); + } + + if (!empty($data['returnurl'])) { + $data['returnurl'] = new moodle_url($data['returnurl']); + } + + return $data; + } + + /** + * Handles magic getters and setters for protected properties. + * + * @param string $name method name, e.g. set_returnurl() + * @param array $arguments arguments to be passed to the array + */ + public function __call($name, array $arguments = array()) { + + if (substr($name, 0, 4) === 'set_') { + $property = substr($name, 4); + if (empty($property)) { + throw new coding_exception('Invalid property name (empty)'); + } + if (empty($arguments)) { + $arguments = array(true); // Default value for flag-like properties. + } + // Make sure it is a protected property. + $isprotected = false; + $reflection = new \ReflectionObject($this); + foreach ($reflection->getProperties(\ReflectionProperty::IS_PROTECTED) as $reflectionproperty) { + if ($reflectionproperty->getName() === $property) { + $isprotected = true; + break; + } + } + if (!$isprotected) { + throw new coding_exception('Unable to set property - it does not exist or it is not protected'); + } + $value = reset($arguments); + $this->$property = $value; + return; + } + + if (substr($name, 0, 4) === 'get_') { + $property = substr($name, 4); + if (empty($property)) { + throw new coding_exception('Invalid property name (empty)'); + } + if (!empty($arguments)) { + throw new coding_exception('No parameter expected'); + } + // Make sure it is a protected property. + $isprotected = false; + $reflection = new \ReflectionObject($this); + foreach ($reflection->getProperties(\ReflectionProperty::IS_PROTECTED) as $reflectionproperty) { + if ($reflectionproperty->getName() === $property) { + $isprotected = true; + break; + } + } + if (!$isprotected) { + throw new coding_exception('Unable to get property - it does not exist or it is not protected'); + } + return $this->$property; + } + } + + /** + * Generates a random token and stores it in a file in moodledata directory. + * + * @return array of the (string)filename and (string)password in this order + */ + public function prepare_authorization() { + global $CFG; + + make_upload_directory('mdeploy/auth/'); + + $attempts = 0; + $success = false; + + while (!$success and $attempts < 5) { + $attempts++; + + $passfile = $this->generate_passfile(); + $password = $this->generate_password(); + $now = time(); + + $filepath = $CFG->dataroot.'/mdeploy/auth/'.$passfile; + + if (!file_exists($filepath)) { + $success = file_put_contents($filepath, $password . PHP_EOL . $now . PHP_EOL, LOCK_EX); + chmod($filepath, $CFG->filepermissions); + } + } + + if ($success) { + return array($passfile, $password); + + } else { + throw new \moodle_exception('unable_prepare_authorization', 'core_plugin'); + } + } + + /* === End of external API === */ + + /** + * Prepares an array of HTTP parameters that can be passed to another page. + * + * @param array|object $data associative array or an object holding the data, data JSON-able + * @return array suitable as a param for moodle_url + */ + protected function data_to_params($data) { + + // Append some our own data. + if (!empty($this->callerurl)) { + $data['callerurl'] = $this->callerurl->out(false); + } + if (!empty($this->returnurl)) { + $data['returnurl'] = $this->returnurl->out(false); + } + + // Finally append the count of items in the package. + $data[self::HTTP_PARAM_CHECKER] = count($data); + + // Generate params. + $params = array(); + foreach ($data as $name => $value) { + $transname = self::HTTP_PARAM_PREFIX.$name; + $transvalue = json_encode($value); + $params[$transname] = $transvalue; + } + + return $params; + } + + /** + * Converts HTTP parameters passed to the script into native PHP data + * + * @param array $params such as $_REQUEST or $_POST + * @return array data passed for this class + */ + protected function params_to_data(array $params) { + + if (empty($params)) { + return array(); + } + + $data = array(); + foreach ($params as $name => $value) { + if (strpos($name, self::HTTP_PARAM_PREFIX) === 0) { + $realname = substr($name, strlen(self::HTTP_PARAM_PREFIX)); + $realvalue = json_decode($value); + $data[$realname] = $realvalue; + } + } + + return $data; + } + + /** + * Returns a random string to be used as a filename of the password storage. + * + * @return string + */ + protected function generate_passfile() { + return clean_param(uniqid('mdeploy_', true), PARAM_FILE); + } + + /** + * Returns a random string to be used as the authorization token + * + * @return string + */ + protected function generate_password() { + return complex_random_string(); + } + + /** + * Checks if the given component's directory is writable + * + * For the purpose of the deployment, the web server process has to have + * write access to all files in the component's directory (recursively) and for the + * directory itself. + * + * @see worker::move_directory_source_precheck() + * @param string $component normalized component name + * @return boolean + */ + protected function component_writable($component) { + + list($plugintype, $pluginname) = core_component::normalize_component($component); + + $directory = core_component::get_plugin_directory($plugintype, $pluginname); + + if (is_null($directory)) { + throw new coding_exception('Unknown component location', $component); + } + + return $this->directory_writable($directory); + } + + /** + * Checks if the mdeploy.php will be able to fetch the ZIP from the given URL + * + * This is mainly supposed to check if the transmission over HTTPS would + * work. That is, if the CA certificates are present at the server. + * + * @param string $downloadurl the URL of the ZIP package to download + * @return bool + */ + protected function update_downloadable($downloadurl) { + global $CFG; + + $curloptions = array( + 'CURLOPT_SSL_VERIFYHOST' => 2, // This is the default in {@link curl} class but just in case. + 'CURLOPT_SSL_VERIFYPEER' => true, + ); + + $curl = new \curl(array('proxy' => true)); + $result = $curl->head($downloadurl, $curloptions); + $errno = $curl->get_errno(); + if (empty($errno)) { + return true; + } else { + return false; + } + } + + /** + * Checks if the directory and all its contents (recursively) is writable + * + * @param string $path full path to a directory + * @return boolean + */ + private function directory_writable($path) { + + if (!is_writable($path)) { + return false; + } + + if (is_dir($path)) { + $handle = opendir($path); + } else { + return false; + } + + $result = true; + + while ($filename = readdir($handle)) { + $filepath = $path.'/'.$filename; + + if ($filename === '.' or $filename === '..') { + continue; + } + + if (is_dir($filepath)) { + $result = $result && $this->directory_writable($filepath); + + } else { + $result = $result && is_writable($filepath); + } + } + + closedir($handle); + + return $result; + } +} diff --git a/lib/classes/update/info.php b/lib/classes/update/info.php new file mode 100644 index 00000000000..82bb3f7846d --- /dev/null +++ b/lib/classes/update/info.php @@ -0,0 +1,65 @@ +. + +/** + * Defines classes used for updates. + * + * @package core + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\update; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Defines the structure of objects returned by {@link \core\update\checker::get_update_info()} + */ +class info { + + /** @var string frankenstyle component name */ + public $component; + /** @var int the available version of the component */ + public $version; + /** @var string|null optional release name */ + public $release = null; + /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */ + public $maturity = null; + /** @var string|null optional URL of a page with more info about the update */ + public $url = null; + /** @var string|null optional URL of a ZIP package that can be downloaded and installed */ + public $download = null; + /** @var string|null of self::download is set, then this must be the MD5 hash of the ZIP */ + public $downloadmd5 = null; + + /** + * Creates new instance of the class + * + * The $info array must provide at least the 'version' value and optionally all other + * values to populate the object's properties. + * + * @param string $name the frankenstyle component name + * @param array $info associative array with other properties + */ + public function __construct($name, array $info) { + $this->component = $name; + foreach ($info as $k => $v) { + if (property_exists('\core\update\info', $k) and $k != 'component') { + $this->$k = $v; + } + } + } +} diff --git a/lib/cronlib.php b/lib/cronlib.php index 0f95c752bb8..f16719620a0 100644 --- a/lib/cronlib.php +++ b/lib/cronlib.php @@ -415,8 +415,7 @@ function cron_run() { // If enabled, fetch information about available updates and eventually notify site admins if (empty($CFG->disableupdatenotifications)) { - require_once($CFG->libdir.'/pluginlib.php'); - $updateschecker = available_update_checker::instance(); + $updateschecker = \core\update\checker::instance(); $updateschecker->cron(); } diff --git a/lib/db/caches.php b/lib/db/caches.php index c6ca0865265..a663e25e755 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -141,7 +141,7 @@ $definitions = array( 'staticaccelerationsize' => 2, ), - // Cache used by the {@link plugin_manager} class. + // Cache used by the {@link core_plugin_manager} class. // NOTE: this must be a shared cache. 'plugin_manager' => array( 'mode' => cache_store::MODE_APPLICATION, diff --git a/lib/editor/atto/classes/plugininfo/atto.php b/lib/editor/atto/classes/plugininfo/atto.php new file mode 100644 index 00000000000..ed52dc48610 --- /dev/null +++ b/lib/editor/atto/classes/plugininfo/atto.php @@ -0,0 +1,35 @@ +. + +/** + * Subplugin info class. + * + * @package editor_atto + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace editor_atto\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class atto extends base { + public function is_uninstall_allowed() { + return true; + } +} diff --git a/lib/editor/tinymce/adminlib.php b/lib/editor/tinymce/adminlib.php index 2bc6130f068..684a9140faa 100644 --- a/lib/editor/tinymce/adminlib.php +++ b/lib/editor/tinymce/adminlib.php @@ -25,69 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -/** - * Editor subplugin info class. - * - * @package editor_tinymce - * @copyright 2012 Petr Skoda {@link http://skodak.org} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class plugininfo_tinymce extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - $disabledsubplugins = array(); - $config = get_config('editor_tinymce', 'disabledsubplugins'); - if ($config) { - $config = explode(',', $config); - foreach ($config as $sp) { - $sp = trim($sp); - if ($sp !== '') { - $disabledsubplugins[$sp] = $sp; - } - } - } - - $enabled = array(); - $installed = core_component::get_plugin_list('tinymce'); - foreach ($installed as $plugin => $fulldir) { - if (isset($disabledsubplugins[$plugin])) { - continue; - } - $enabled[$plugin] = $plugin; - } - - return $enabled; - } - - public function is_uninstall_allowed() { - return true; - } - - public function get_settings_section_name() { - return 'tinymce'.$this->name.'settings'; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - - $settings = null; - if ($hassiteconfig && file_exists($this->full_path('settings.php'))) { - $section = $this->get_settings_section_name(); - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } -} - - /** * Special class for TinyMCE subplugin administration. * @@ -165,10 +102,9 @@ class tiynce_subplugins_settings extends admin_setting { public function output_html($data, $query='') { global $CFG, $OUTPUT, $PAGE; require_once("$CFG->libdir/editorlib.php"); - require_once("$CFG->libdir/pluginlib.php"); require_once(__DIR__.'/lib.php'); $tinymce = new tinymce_texteditor(); - $pluginmanager = plugin_manager::instance(); + $pluginmanager = core_plugin_manager::instance(); // display strings $strbuttons = get_string('availablebuttons', 'editor_tinymce'); @@ -188,7 +124,7 @@ class tiynce_subplugins_settings extends admin_setting { $table->head = array($strname, $strbuttons, $strversion, $strenable, $strsettings, $struninstall); $table->align = array('left', 'left', 'center', 'center', 'center', 'center'); $table->data = array(); - $table->width = '100%'; + $table->attributes['class'] = 'admintable generaltable'; // Iterate through subplugins. foreach ($subplugins as $name => $dir) { @@ -201,6 +137,7 @@ class tiynce_subplugins_settings extends admin_setting { $plugininfo = $pluginmanager->get_plugin_info('tinymce_'.$name); // Add hide/show link. + $class = ''; if (!$version) { $hideshow = ''; $displayname = html_writer::tag('span', $name, array('class'=>'error')); @@ -208,12 +145,13 @@ class tiynce_subplugins_settings extends admin_setting { $url = new moodle_url('/lib/editor/tinymce/subplugins.php', array('sesskey'=>sesskey(), 'return'=>'settings', 'disable'=>$name)); $hideshow = html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('t/hide'), 'class'=>'iconsmall', 'alt'=>$strdisable)); $hideshow = html_writer::link($url, $hideshow); - $displayname = html_writer::tag('span', $namestr); + $displayname = $namestr; } else { $url = new moodle_url('/lib/editor/tinymce/subplugins.php', array('sesskey'=>sesskey(), 'return'=>'settings', 'enable'=>$name)); $hideshow = html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('t/show'), 'class'=>'iconsmall', 'alt'=>$strenable)); $hideshow = html_writer::link($url, $hideshow); - $displayname = html_writer::tag('span', $namestr, array('class'=>'dimmed_text')); + $displayname = $namestr; + $class = 'dimmed_text'; } if ($PAGE->theme->resolve_image_location('icon', 'tinymce_' . $name, false)) { @@ -238,12 +176,16 @@ class tiynce_subplugins_settings extends admin_setting { // Add uninstall info. $uninstall = ''; - if ($uninstallurl = plugin_manager::instance()->get_uninstall_url('tinymce_' . $name)) { + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('tinymce_' . $name, 'manage')) { $uninstall = html_writer::link($uninstallurl, $struninstall); } // Add a row to the table. - $table->data[] = array($displayname, $buttons, $version, $hideshow, $settings, $uninstall); + $row = new html_table_row(array($displayname, $buttons, $version, $hideshow, $settings, $uninstall)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; } $return .= html_writer::table($table); $return .= html_writer::tag('p', get_string('tablenosave', 'admin')); diff --git a/lib/editor/tinymce/classes/plugininfo/tinymce.php b/lib/editor/tinymce/classes/plugininfo/tinymce.php new file mode 100644 index 00000000000..f652ea0172e --- /dev/null +++ b/lib/editor/tinymce/classes/plugininfo/tinymce.php @@ -0,0 +1,98 @@ +. + +/** + * Subplugin info class. + * + * @package editor_tinymce + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace editor_tinymce\plugininfo; + +use core\plugininfo\base, moodle_url, part_of_admin_tree, admin_settingpage, core_component; + +defined('MOODLE_INTERNAL') || die(); + + +class tinymce extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + $disabledsubplugins = array(); + $config = get_config('editor_tinymce', 'disabledsubplugins'); + if ($config) { + $config = explode(',', $config); + foreach ($config as $sp) { + $sp = trim($sp); + if ($sp !== '') { + $disabledsubplugins[$sp] = $sp; + } + } + } + + $enabled = array(); + $installed = core_component::get_plugin_list('tinymce'); + foreach ($installed as $plugin => $fulldir) { + if (isset($disabledsubplugins[$plugin])) { + continue; + } + $enabled[$plugin] = $plugin; + } + + return $enabled; + } + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/settings.php', array('section'=>'editorsettingstinymce')); + } + + public function get_settings_section_name() { + return 'tinymce'.$this->name.'settings'; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } +} diff --git a/lib/editor/tinymce/settings.php b/lib/editor/tinymce/settings.php index a2a9e0c45e7..3699c518dd1 100644 --- a/lib/editor/tinymce/settings.php +++ b/lib/editor/tinymce/settings.php @@ -47,8 +47,8 @@ fontselect,fontsizeselect,wrap,code,search,replace,|,cleanup,removeformat,pastet $ADMIN->add('editortinymce', $settings); unset($settings); -require_once("$CFG->libdir/pluginlib.php"); -foreach (plugin_manager::instance()->get_plugins_of_type('tinymce') as $plugin) { +foreach (core_plugin_manager::instance()->get_plugins_of_type('tinymce') as $plugin) { + /** @var \editor_tinymce\plugininfo\tinymce $plugin */ $plugin->load_settings($ADMIN, 'editortinymce', $hassiteconfig); } diff --git a/lib/editor/tinymce/subplugins.php b/lib/editor/tinymce/subplugins.php index 6eb01c41660..8ef80d671c8 100644 --- a/lib/editor/tinymce/subplugins.php +++ b/lib/editor/tinymce/subplugins.php @@ -24,7 +24,6 @@ require(__DIR__ . '/../../../config.php'); require_once($CFG->libdir.'/adminlib.php'); -require_once($CFG->libdir.'/pluginlib.php'); $disable = optional_param('disable', '', PARAM_PLUGIN); $enable = optional_param('enable', '', PARAM_PLUGIN); @@ -62,6 +61,6 @@ if ($disable) { } set_config('disabledsubplugins', implode(',', $disabled), 'editor_tinymce'); -plugin_manager::reset_caches(); +core_plugin_manager::reset_caches(); redirect($returnurl); diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 5e714a42f67..88e32888c18 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -1595,8 +1595,8 @@ function purge_all_caches() { theme_reset_all_caches(); get_string_manager()->reset_caches(); core_text::reset_caches(); - if (class_exists('plugin_manager')) { - plugin_manager::reset_caches(); + if (class_exists('core_plugin_manager')) { + core_plugin_manager::reset_caches(); } // Bump up cacherev field for all courses. diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 7461fb47388..fc51e267e71 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -215,14 +215,14 @@ class phpunit_util extends testing_util { get_fast_modinfo(0, 0, true); // Reset other singletons. - if (class_exists('plugin_manager')) { - plugin_manager::reset_caches(true); + if (class_exists('core_plugin_manager')) { + core_plugin_manager::reset_caches(true); } - if (class_exists('available_update_checker')) { - available_update_checker::reset_caches(true); + if (class_exists('\core\update\checker')) { + \core\update\checker::reset_caches(true); } - if (class_exists('available_update_deployer')) { - available_update_deployer::reset_caches(true); + if (class_exists('\core\update\deployer')) { + \core\update\deployer::reset_caches(true); } // purge dataroot directory diff --git a/lib/pluginlib.php b/lib/pluginlib.php index 742a0d9ef8c..6716a069987 100644 --- a/lib/pluginlib.php +++ b/lib/pluginlib.php @@ -23,4244 +23,27 @@ * plugins check page during the upgrade. * * @package core - * @subpackage admin * @copyright 2011 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); + /** - * Singleton class providing general plugins management functionality + * @deprecated since 2.6 - use core_plugin_manager instead. */ -class plugin_manager { - - /** the plugin is shipped with standard Moodle distribution */ - const PLUGIN_SOURCE_STANDARD = 'std'; - /** the plugin is added extension */ - const PLUGIN_SOURCE_EXTENSION = 'ext'; - - /** the plugin uses neither database nor capabilities, no versions */ - const PLUGIN_STATUS_NODB = 'nodb'; - /** the plugin is up-to-date */ - const PLUGIN_STATUS_UPTODATE = 'uptodate'; - /** the plugin is about to be installed */ - const PLUGIN_STATUS_NEW = 'new'; - /** the plugin is about to be upgraded */ - const PLUGIN_STATUS_UPGRADE = 'upgrade'; - /** the standard plugin is about to be deleted */ - const PLUGIN_STATUS_DELETE = 'delete'; - /** the version at the disk is lower than the one already installed */ - const PLUGIN_STATUS_DOWNGRADE = 'downgrade'; - /** the plugin is installed but missing from disk */ - const PLUGIN_STATUS_MISSING = 'missing'; - - /** @var plugin_manager holds the singleton instance */ - protected static $singletoninstance; - /** @var array of raw plugins information */ - protected $pluginsinfo = null; - /** @var array of raw subplugins information */ - protected $subpluginsinfo = null; - /** @var array list of installed plugins $name=>$version */ - protected $installedplugins = null; - /** @var array list of all enabled plugins $name=>$name */ - protected $enabledplugins = null; - /** @var array list of all enabled plugins $name=>$diskversion */ - protected $presentplugins = null; - - /** - * Direct initiation not allowed, use the factory method {@link self::instance()} - */ - protected function __construct() { - } - - /** - * Sorry, this is singleton - */ - protected function __clone() { - } - - /** - * Factory method for this class - * - * @return plugin_manager the singleton instance - */ +class plugin_manager extends core_plugin_manager { + // BC only. public static function instance() { - if (is_null(self::$singletoninstance)) { - self::$singletoninstance = new self(); - } - return self::$singletoninstance; - } - - /** - * Reset all caches. - * @param bool $phpunitreset - */ - public static function reset_caches($phpunitreset = false) { - if ($phpunitreset) { - self::$singletoninstance = null; - } else { - if (self::$singletoninstance) { - self::$singletoninstance->pluginsinfo = null; - self::$singletoninstance->subpluginsinfo = null; - self::$singletoninstance->installedplugins = null; - self::$singletoninstance->enabledplugins = null; - self::$singletoninstance->presentplugins = null; - } - } - $cache = cache::make('core', 'plugin_manager'); - $cache->purge(); - } - - /** - * Returns the result of {@link core_component::get_plugin_types()} ordered for humans - * - * @see self::reorder_plugin_types() - * @return array (string)name => (string)location - */ - public function get_plugin_types() { - if (func_num_args() > 0) { - if (!func_get_arg(0)) { - throw coding_exception('plugin_manager->get_plugin_types() does not support relative paths.'); - } - } - return $this->reorder_plugin_types(core_component::get_plugin_types()); - } - - /** - * Load list of installed plugins, - * always call before using $this->installedplugins. - * - * This method is caching results for all plugins. - */ - protected function load_installed_plugins() { - global $DB, $CFG; - - if ($this->installedplugins) { - return; - } - - if (empty($CFG->version)) { - // Nothing installed yet. - $this->installedplugins = array(); - return; - } - - $cache = cache::make('core', 'plugin_manager'); - $installed = $cache->get('installed'); - - if (is_array($installed)) { - $this->installedplugins = $installed; - return; - } - - $this->installedplugins = array(); - - if ($CFG->version < 2013092001.02) { - // We did not upgrade the database yet. - $modules = $DB->get_records('modules', array(), 'name ASC', 'id, name, version'); - foreach ($modules as $module) { - $this->installedplugins['mod'][$module->name] = $module->version; - } - $blocks = $DB->get_records('block', array(), 'name ASC', 'id, name, version'); - foreach ($blocks as $block) { - $this->installedplugins['block'][$block->name] = $block->version; - } - } - - $versions = $DB->get_records('config_plugins', array('name'=>'version')); - foreach ($versions as $version) { - $parts = explode('_', $version->plugin, 2); - if (!isset($parts[1])) { - // Invalid component, there must be at least one "_". - continue; - } - // Do not verify here if plugin type and name are valid. - $this->installedplugins[$parts[0]][$parts[1]] = $version->value; - } - - foreach ($this->installedplugins as $key => $value) { - ksort($this->installedplugins[$key]); - } - - $cache->set('installed', $this->installedplugins); - } - - /** - * Return list of installed plugins of given type. - * @param string $type - * @return array $name=>$version - */ - public function get_installed_plugins($type) { - $this->load_installed_plugins(); - if (isset($this->installedplugins[$type])) { - return $this->installedplugins[$type]; - } - return array(); - } - - /** - * Load list of all enabled plugins, - * call before using $this->enabledplugins. - * - * This method is caching results from individual plugin info classes. - */ - protected function load_enabled_plugins() { - global $CFG; - - if ($this->enabledplugins) { - return; - } - - if (empty($CFG->version)) { - $this->enabledplugins = array(); - return; - } - - $cache = cache::make('core', 'plugin_manager'); - $enabled = $cache->get('enabled'); - - if (is_array($enabled)) { - $this->enabledplugins = $enabled; - return; - } - - $this->enabledplugins = array(); - - require_once($CFG->libdir.'/adminlib.php'); - - $plugintypes = core_component::get_plugin_types(); - foreach ($plugintypes as $plugintype => $fulldir) { - // Hack: include mod and editor subplugin management classes first, - // the adminlib.php is supposed to contain extra admin settings too. - $plugininfoclass = 'plugininfo_' . $plugintype; - if (!class_exists($plugininfoclass) and file_exists("$fulldir/adminlib.php")) { - include_once("$fulldir/adminlib.php"); - } - if (class_exists($plugininfoclass)) { - $enabled = $plugininfoclass::get_enabled_plugins(); - if (!is_array($enabled)) { - continue; - } - $this->enabledplugins[$plugintype] = $enabled; - } - } - - $cache->set('enabled', $this->enabledplugins); - } - - /** - * Get list of enabled plugins of given type, - * the result may contain missing plugins. - * - * @param string $type - * @return array|null list of enabled plugins of this type, null if unknown - */ - public function get_enabled_plugins($type) { - $this->load_enabled_plugins(); - if (isset($this->enabledplugins[$type])) { - return $this->enabledplugins[$type]; - } - return null; - } - - /** - * Load list of all present plugins - call before using $this->presentplugins. - */ - protected function load_present_plugins() { - if ($this->presentplugins) { - return; - } - - $cache = cache::make('core', 'plugin_manager'); - $present = $cache->get('present'); - - if (is_array($present)) { - $this->presentplugins = $present; - return; - } - - $this->presentplugins = array(); - - $plugintypes = core_component::get_plugin_types(); - foreach ($plugintypes as $type => $typedir) { - $plugs = core_component::get_plugin_list($type); - foreach ($plugs as $plug => $fullplug) { - $plugin = new stdClass(); - $plugin->version = null; - $module = $plugin; - @include($fullplug.'/version.php'); - $this->presentplugins[$type][$plug] = $plugin; - } - } - - $cache->set('present', $this->presentplugins); - } - - /** - * Get list of present plugins of given type. - * - * @param string $type - * @return array|null list of presnet plugins $name=>$diskversion, null if unknown - */ - public function get_present_plugins($type) { - $this->load_present_plugins(); - if (isset($this->presentplugins[$type])) { - return $this->presentplugins[$type]; - } - return null; - } - - /** - * Returns a tree of known plugins and information about them - * - * @return array 2D array. The first keys are plugin type names (e.g. qtype); - * the second keys are the plugin local name (e.g. multichoice); and - * the values are the corresponding objects extending {@link plugininfo_base} - */ - public function get_plugins() { - global $CFG; - - if (is_array($this->pluginsinfo)) { - return $this->pluginsinfo; - } - - $this->pluginsinfo = array(); - - // Hack: include mod and editor subplugin management classes first, - // the adminlib.php is supposed to contain extra admin settings too. - require_once($CFG->libdir.'/adminlib.php'); - foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) { - foreach (core_component::get_plugin_list($type) as $dir) { - if (file_exists("$dir/adminlib.php")) { - include_once("$dir/adminlib.php"); - } - } - } - $plugintypes = $this->get_plugin_types(); - foreach ($plugintypes as $plugintype => $plugintyperootdir) { - if (in_array($plugintype, array('base', 'general'))) { - throw new coding_exception('Illegal usage of reserved word for plugin type'); - } - if (class_exists('plugininfo_' . $plugintype)) { - $plugintypeclass = 'plugininfo_' . $plugintype; - } else { - $plugintypeclass = 'plugininfo_general'; - } - if (!in_array('plugininfo_base', class_parents($plugintypeclass))) { - throw new coding_exception('Class ' . $plugintypeclass . ' must extend plugininfo_base'); - } - $plugins = $plugintypeclass::get_plugins($plugintype, $plugintyperootdir, $plugintypeclass); - $this->pluginsinfo[$plugintype] = $plugins; - } - - if (empty($CFG->disableupdatenotifications) and !during_initial_install()) { - // append the information about available updates provided by {@link available_update_checker()} - $provider = available_update_checker::instance(); - foreach ($this->pluginsinfo as $plugintype => $plugins) { - foreach ($plugins as $plugininfoholder) { - $plugininfoholder->check_available_updates($provider); - } - } - } - - return $this->pluginsinfo; - } - - /** - * Returns list of known plugins of the given type. - * - * This method returns the subset of the tree returned by {@link self::get_plugins()}. - * If the given type is not known, empty array is returned. - * - * @param string $type plugin type, e.g. 'mod' or 'workshopallocation' - * @return array (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link plugininfo_base} - */ - public function get_plugins_of_type($type) { - - $plugins = $this->get_plugins(); - - if (!isset($plugins[$type])) { - return array(); - } - - return $plugins[$type]; - } - - /** - * Returns list of all known subplugins of the given plugin. - * - * For plugins that do not provide subplugins (i.e. there is no support for it), - * empty array is returned. - * - * @param string $component full component name, e.g. 'mod_workshop' - * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link plugininfo_base} - */ - public function get_subplugins_of_plugin($component) { - - $pluginfo = $this->get_plugin_info($component); - - if (is_null($pluginfo)) { - return array(); - } - - $subplugins = $this->get_subplugins(); - - if (!isset($subplugins[$pluginfo->component])) { - return array(); - } - - $list = array(); - - foreach ($subplugins[$pluginfo->component] as $subdata) { - foreach ($this->get_plugins_of_type($subdata->type) as $subpluginfo) { - $list[$subpluginfo->component] = $subpluginfo; - } - } - - return $list; - } - - /** - * Returns list of plugins that define their subplugins and the information - * about them from the db/subplugins.php file. - * - * @return array with keys like 'mod_quiz', and values the data from the - * corresponding db/subplugins.php file. - */ - public function get_subplugins() { - - if (is_array($this->subpluginsinfo)) { - return $this->subpluginsinfo; - } - - $this->subpluginsinfo = array(); - foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) { - foreach (core_component::get_plugin_list($type) as $component => $ownerdir) { - $componentsubplugins = array(); - if (file_exists($ownerdir . '/db/subplugins.php')) { - $subplugins = array(); - include($ownerdir . '/db/subplugins.php'); - foreach ($subplugins as $subplugintype => $subplugintyperootdir) { - $subplugin = new stdClass(); - $subplugin->type = $subplugintype; - $subplugin->typerootdir = $subplugintyperootdir; - $componentsubplugins[$subplugintype] = $subplugin; - } - $this->subpluginsinfo[$type . '_' . $component] = $componentsubplugins; - } - } - } - - return $this->subpluginsinfo; - } - - /** - * Returns the name of the plugin that defines the given subplugin type - * - * If the given subplugin type is not actually a subplugin, returns false. - * - * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz - * @return false|string the name of the parent plugin, eg. mod_workshop - */ - public function get_parent_of_subplugin($subplugintype) { - - $parent = false; - foreach ($this->get_subplugins() as $pluginname => $subplugintypes) { - if (isset($subplugintypes[$subplugintype])) { - $parent = $pluginname; - break; - } - } - - return $parent; - } - - /** - * Returns a localized name of a given plugin - * - * @param string $component name of the plugin, eg mod_workshop or auth_ldap - * @return string - */ - public function plugin_name($component) { - - $pluginfo = $this->get_plugin_info($component); - - if (is_null($pluginfo)) { - throw new moodle_exception('err_unknown_plugin', 'core_plugin', '', array('plugin' => $component)); - } - - return $pluginfo->displayname; - } - - /** - * Returns a localized name of a plugin typed in singular form - * - * Most plugin types define their names in core_plugin lang file. In case of subplugins, - * we try to ask the parent plugin for the name. In the worst case, we will return - * the value of the passed $type parameter. - * - * @param string $type the type of the plugin, e.g. mod or workshopform - * @return string - */ - public function plugintype_name($type) { - - if (get_string_manager()->string_exists('type_' . $type, 'core_plugin')) { - // for most plugin types, their names are defined in core_plugin lang file - return get_string('type_' . $type, 'core_plugin'); - - } else if ($parent = $this->get_parent_of_subplugin($type)) { - // if this is a subplugin, try to ask the parent plugin for the name - if (get_string_manager()->string_exists('subplugintype_' . $type, $parent)) { - return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type, $parent); - } else { - return $this->plugin_name($parent) . ' / ' . $type; - } - - } else { - return $type; - } - } - - /** - * Returns a localized name of a plugin type in plural form - * - * Most plugin types define their names in core_plugin lang file. In case of subplugins, - * we try to ask the parent plugin for the name. In the worst case, we will return - * the value of the passed $type parameter. - * - * @param string $type the type of the plugin, e.g. mod or workshopform - * @return string - */ - public function plugintype_name_plural($type) { - - if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) { - // for most plugin types, their names are defined in core_plugin lang file - return get_string('type_' . $type . '_plural', 'core_plugin'); - - } else if ($parent = $this->get_parent_of_subplugin($type)) { - // if this is a subplugin, try to ask the parent plugin for the name - if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) { - return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent); - } else { - return $this->plugin_name($parent) . ' / ' . $type; - } - - } else { - return $type; - } - } - - /** - * Returns information about the known plugin, or null - * - * @param string $component frankenstyle component name. - * @return plugininfo_base|null the corresponding plugin information. - */ - public function get_plugin_info($component) { - list($type, $name) = core_component::normalize_component($component); - $plugins = $this->get_plugins(); - if (isset($plugins[$type][$name])) { - return $plugins[$type][$name]; - } else { - return null; - } - } - - /** - * Check to see if the current version of the plugin seems to be a checkout of an external repository. - * - * @see available_update_deployer::plugin_external_source() - * @param string $component frankenstyle component name - * @return false|string - */ - public function plugin_external_source($component) { - - $plugininfo = $this->get_plugin_info($component); - - if (is_null($plugininfo)) { - return false; - } - - $pluginroot = $plugininfo->rootdir; - - if (is_dir($pluginroot.'/.git')) { - return 'git'; - } - - if (is_dir($pluginroot.'/CVS')) { - return 'cvs'; - } - - if (is_dir($pluginroot.'/.svn')) { - return 'svn'; - } - - return false; - } - - /** - * Get a list of any other plugins that require this one. - * @param string $component frankenstyle component name. - * @return array of frankensyle component names that require this one. - */ - public function other_plugins_that_require($component) { - $others = array(); - foreach ($this->get_plugins() as $type => $plugins) { - foreach ($plugins as $plugin) { - $required = $plugin->get_other_required_plugins(); - if (isset($required[$component])) { - $others[] = $plugin->component; - } - } - } - return $others; - } - - /** - * Check a dependencies list against the list of installed plugins. - * @param array $dependencies compenent name to required version or ANY_VERSION. - * @return bool true if all the dependencies are satisfied. - */ - public function are_dependencies_satisfied($dependencies) { - foreach ($dependencies as $component => $requiredversion) { - $otherplugin = $this->get_plugin_info($component); - if (is_null($otherplugin)) { - return false; - } - - if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) { - return false; - } - } - - return true; - } - - /** - * Checks all dependencies for all installed plugins - * - * This is used by install and upgrade. The array passed by reference as the second - * argument is populated with the list of plugins that have failed dependencies (note that - * a single plugin can appear multiple times in the $failedplugins). - * - * @param int $moodleversion the version from version.php. - * @param array $failedplugins to return the list of plugins with non-satisfied dependencies - * @return bool true if all the dependencies are satisfied for all plugins. - */ - public function all_plugins_ok($moodleversion, &$failedplugins = array()) { - - $return = true; - foreach ($this->get_plugins() as $type => $plugins) { - foreach ($plugins as $plugin) { - - if (!$plugin->is_core_dependency_satisfied($moodleversion)) { - $return = false; - $failedplugins[] = $plugin->component; - } - - if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) { - $return = false; - $failedplugins[] = $plugin->component; - } - } - } - - return $return; - } - - /** - * Is it possible to uninstall the given plugin? - * - * False is returned if the plugininfo subclass declares the uninstall should - * not be allowed via {@link plugininfo_base::is_uninstall_allowed()} or if the - * core vetoes it (e.g. becase the plugin or some of its subplugins is required - * by some other installed plugin). - * - * @param string $component full frankenstyle name, e.g. mod_foobar - * @return bool - */ - public function can_uninstall_plugin($component) { - - $pluginfo = $this->get_plugin_info($component); - - if (is_null($pluginfo)) { - return false; - } - - if (!$this->common_uninstall_check($pluginfo)) { - return false; - } - - // If it has subplugins, check they can be uninstalled too. - $subplugins = $this->get_subplugins_of_plugin($pluginfo->component); - foreach ($subplugins as $subpluginfo) { - if (!$this->common_uninstall_check($subpluginfo)) { - return false; - } - // Check if there are some other plugins requiring this subplugin - // (but the parent and siblings). - foreach ($this->other_plugins_that_require($subpluginfo->component) as $requiresme) { - $ismyparent = ($pluginfo->component === $requiresme); - $ismysibling = in_array($requiresme, array_keys($subplugins)); - if (!$ismyparent and !$ismysibling) { - return false; - } - } - } - - // Check if there are some other plugins requiring this plugin - // (but its subplugins). - foreach ($this->other_plugins_that_require($pluginfo->component) as $requiresme) { - $ismysubplugin = in_array($requiresme, array_keys($subplugins)); - if (!$ismysubplugin) { - return false; - } - } - - return true; - } - - /** - * Returns uninstall URL if exists. - * - * @param string $component - * @return moodle_url uninstall URL, null if uninstall not supported - */ - public function get_uninstall_url($component) { - if (!$this->can_uninstall_plugin($component)) { - return null; - } - - $pluginfo = $this->get_plugin_info($component); - - if (is_null($pluginfo)) { - return null; - } - - return $pluginfo->get_uninstall_url(); - } - - /** - * Uninstall the given plugin. - * - * Automatically cleans-up all remaining configuration data, log records, events, - * files from the file pool etc. - * - * In the future, the functionality of {@link uninstall_plugin()} function may be moved - * into this method and all the code should be refactored to use it. At the moment, we - * mimic this future behaviour by wrapping that function call. - * - * @param string $component - * @param progress_trace $progress traces the process - * @return bool true on success, false on errors/problems - */ - public function uninstall_plugin($component, progress_trace $progress) { - - $pluginfo = $this->get_plugin_info($component); - - if (is_null($pluginfo)) { - return false; - } - - // Give the pluginfo class a chance to execute some steps. - $result = $pluginfo->uninstall($progress); - if (!$result) { - return false; - } - - // Call the legacy core function to uninstall the plugin. - ob_start(); - uninstall_plugin($pluginfo->type, $pluginfo->name); - $progress->output(ob_get_clean()); - - return true; - } - - /** - * Checks if there are some plugins with a known available update - * - * @return bool true if there is at least one available update - */ - public function some_plugins_updatable() { - foreach ($this->get_plugins() as $type => $plugins) { - foreach ($plugins as $plugin) { - if ($plugin->available_updates()) { - return true; - } - } - } - - return false; - } - - /** - * Check to see if the given plugin folder can be removed by the web server process. - * - * @param string $component full frankenstyle component - * @return bool - */ - public function is_plugin_folder_removable($component) { - - $pluginfo = $this->get_plugin_info($component); - - if (is_null($pluginfo)) { - return false; - } - - // To be able to remove the plugin folder, its parent must be writable, too. - if (!is_writable(dirname($pluginfo->rootdir))) { - return false; - } - - // Check that the folder and all its content is writable (thence removable). - return $this->is_directory_removable($pluginfo->rootdir); - } - - /** - * Defines a list of all plugins that were originally shipped in the standard Moodle distribution, - * but are not anymore and are deleted during upgrades. - * - * The main purpose of this list is to hide missing plugins during upgrade. - * - * @param string $type plugin type - * @param string $name plugin name - * @return bool - */ - public static function is_deleted_standard_plugin($type, $name) { - - // Example of the array structure: - // $plugins = array( - // 'block' => array('admin', 'admin_tree'), - // 'mod' => array('assignment'), - // ); - // Do not include plugins that were removed during upgrades to versions that are - // not supported as source versions for upgrade any more. For example, at MOODLE_23_STABLE - // branch, listed should be no plugins that were removed at 1.9.x - 2.1.x versions as - // Moodle 2.3 supports upgrades from 2.2.x only. - $plugins = array( - 'qformat' => array('blackboard'), - 'enrol' => array('authorize'), - 'tool' => array('bloglevelupgrade'), - ); - - if (!isset($plugins[$type])) { - return false; - } - return in_array($name, $plugins[$type]); - } - - /** - * Defines a white list of all plugins shipped in the standard Moodle distribution - * - * @param string $type - * @return false|array array of standard plugins or false if the type is unknown - */ - public static function standard_plugins_list($type) { - - $standard_plugins = array( - - 'assignment' => array( - 'offline', 'online', 'upload', 'uploadsingle' - ), - - 'assignsubmission' => array( - 'comments', 'file', 'onlinetext' - ), - - 'assignfeedback' => array( - 'comments', 'file', 'offline', 'editpdf' - ), - - 'atto' => array( - 'bold', 'clear', 'html', 'image', 'indent', 'italic', 'link', - 'media', 'orderedlist', 'outdent', 'strike', 'title', - 'underline', 'unlink', 'unorderedlist' - ), - - 'auth' => array( - 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet', - 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius', - 'shibboleth', 'webservice' - ), - - 'block' => array( - 'activity_modules', 'admin_bookmarks', 'badges', 'blog_menu', - 'blog_recent', 'blog_tags', 'calendar_month', - 'calendar_upcoming', 'comments', 'community', - 'completionstatus', 'course_list', 'course_overview', - 'course_summary', 'feedback', 'glossary_random', 'html', - 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile', - 'navigation', 'news_items', 'online_users', 'participants', - 'private_files', 'quiz_results', 'recent_activity', - 'rss_client', 'search_forums', 'section_links', - 'selfcompletion', 'settings', 'site_main_menu', - 'social_activities', 'tag_flickr', 'tag_youtube', 'tags' - ), - - 'booktool' => array( - 'exportimscp', 'importhtml', 'print' - ), - - 'cachelock' => array( - 'file' - ), - - 'cachestore' => array( - 'file', 'memcache', 'memcached', 'mongodb', 'session', 'static' - ), - - 'calendartype' => array( - 'gregorian' - ), - - 'coursereport' => array( - //deprecated! - ), - - 'datafield' => array( - 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu', - 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url' - ), - - 'datapreset' => array( - 'imagegallery' - ), - - 'editor' => array( - 'textarea', 'tinymce', 'atto' - ), - - 'enrol' => array( - 'category', 'cohort', 'database', 'flatfile', - 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet', - 'paypal', 'self' - ), - - 'filter' => array( - 'activitynames', 'algebra', 'censor', 'emailprotect', - 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy', - 'urltolink', 'data', 'glossary' - ), - - 'format' => array( - 'singleactivity', 'social', 'topics', 'weeks' - ), - - 'gradeexport' => array( - 'ods', 'txt', 'xls', 'xml' - ), - - 'gradeimport' => array( - 'csv', 'xml' - ), - - 'gradereport' => array( - 'grader', 'outcomes', 'overview', 'user' - ), - - 'gradingform' => array( - 'rubric', 'guide' - ), - - 'local' => array( - ), - - 'message' => array( - 'email', 'jabber', 'popup' - ), - - 'mnetservice' => array( - 'enrol' - ), - - 'mod' => array( - 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder', - 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page', - 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop' - ), - - 'plagiarism' => array( - ), - - 'portfolio' => array( - 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa' - ), - - 'profilefield' => array( - 'checkbox', 'datetime', 'menu', 'text', 'textarea' - ), - - 'qbehaviour' => array( - 'adaptive', 'adaptivenopenalty', 'deferredcbm', - 'deferredfeedback', 'immediatecbm', 'immediatefeedback', - 'informationitem', 'interactive', 'interactivecountback', - 'manualgraded', 'missing' - ), - - 'qformat' => array( - 'aiken', 'blackboard_six', 'examview', 'gift', - 'learnwise', 'missingword', 'multianswer', 'webct', - 'xhtml', 'xml' - ), - - 'qtype' => array( - 'calculated', 'calculatedmulti', 'calculatedsimple', - 'description', 'essay', 'match', 'missingtype', 'multianswer', - 'multichoice', 'numerical', 'random', 'randomsamatch', - 'shortanswer', 'truefalse' - ), - - 'quiz' => array( - 'grading', 'overview', 'responses', 'statistics' - ), - - 'quizaccess' => array( - 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate', - 'password', 'safebrowser', 'securewindow', 'timelimit' - ), - - 'report' => array( - 'backups', 'completion', 'configlog', 'courseoverview', - 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats', 'performance' - ), - - 'repository' => array( - 'alfresco', 'areafiles', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem', - 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot', - 'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav', - 'wikimedia', 'youtube' - ), - - 'scormreport' => array( - 'basic', - 'interactions', - 'graphs', - 'objectives' - ), - - 'tinymce' => array( - 'ctrlhelp', 'dragmath', 'managefiles', 'moodleemoticon', 'moodleimage', - 'moodlemedia', 'moodlenolink', 'pdw', 'spellchecker', 'wrap' - ), - - 'theme' => array( - 'afterburner', 'anomaly', 'arialist', 'base', 'binarius', 'bootstrapbase', - 'boxxie', 'brick', 'canvas', 'clean', 'formal_white', 'formfactor', - 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble', - 'nonzero', 'overlay', 'serenity', 'sky_high', 'splash', - 'standard', 'standardold' - ), - - 'tool' => array( - 'assignmentupgrade', 'behat', 'capability', 'customlang', - 'dbtransfer', 'generator', 'health', 'innodb', 'installaddon', - 'langimport', 'multilangupgrade', 'phpunit', 'profiling', - 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', - 'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'xmldb' - ), - - 'webservice' => array( - 'amf', 'rest', 'soap', 'xmlrpc' - ), - - 'workshopallocation' => array( - 'manual', 'random', 'scheduled' - ), - - 'workshopeval' => array( - 'best' - ), - - 'workshopform' => array( - 'accumulative', 'comments', 'numerrors', 'rubric' - ) - ); - - if (isset($standard_plugins[$type])) { - return $standard_plugins[$type]; - } else { - return false; - } - } - - /** - * Reorders plugin types into a sequence to be displayed - * - * For technical reasons, plugin types returned by {@link core_component::get_plugin_types()} are - * in a certain order that does not need to fit the expected order for the display. - * Particularly, activity modules should be displayed first as they represent the - * real heart of Moodle. They should be followed by other plugin types that are - * used to build the courses (as that is what one expects from LMS). After that, - * other supportive plugin types follow. - * - * @param array $types associative array - * @return array same array with altered order of items - */ - protected function reorder_plugin_types(array $types) { - $fix = array( - 'mod' => $types['mod'], - 'block' => $types['block'], - 'qtype' => $types['qtype'], - 'qbehaviour' => $types['qbehaviour'], - 'qformat' => $types['qformat'], - 'filter' => $types['filter'], - 'enrol' => $types['enrol'], - ); - foreach ($types as $type => $path) { - if (!isset($fix[$type])) { - $fix[$type] = $path; - } - } - return $fix; - } - - /** - * Check if the given directory can be removed by the web server process. - * - * This recursively checks that the given directory and all its contents - * it writable. - * - * @param string $fullpath - * @return boolean - */ - protected function is_directory_removable($fullpath) { - - if (!is_writable($fullpath)) { - return false; - } - - if (is_dir($fullpath)) { - $handle = opendir($fullpath); - } else { - return false; - } - - $result = true; - - while ($filename = readdir($handle)) { - - if ($filename === '.' or $filename === '..') { - continue; - } - - $subfilepath = $fullpath.'/'.$filename; - - if (is_dir($subfilepath)) { - $result = $result && $this->is_directory_removable($subfilepath); - - } else { - $result = $result && is_writable($subfilepath); - } - } - - closedir($handle); - - return $result; - } - - /** - * Helper method that implements common uninstall prerequisities - * - * @param plugininfo_base $pluginfo - * @return bool - */ - protected function common_uninstall_check(plugininfo_base $pluginfo) { - - if (!$pluginfo->is_uninstall_allowed()) { - // The plugin's plugininfo class declares it should not be uninstalled. - return false; - } - - if ($pluginfo->get_status() === plugin_manager::PLUGIN_STATUS_NEW) { - // The plugin is not installed. It should be either installed or removed from the disk. - // Relying on this temporary state may be tricky. - return false; - } - - if (is_null($pluginfo->get_uninstall_url())) { - // Backwards compatibility. - debugging('plugininfo_base subclasses should use is_uninstall_allowed() instead of returning null in get_uninstall_url()', - DEBUG_DEVELOPER); - return false; - } - - return true; - } -} - - -/** - * General exception thrown by the {@link available_update_checker} class - */ -class available_update_checker_exception extends moodle_exception { - - /** - * @param string $errorcode exception description identifier - * @param mixed $debuginfo debugging data to display - */ - public function __construct($errorcode, $debuginfo=null) { - parent::__construct($errorcode, 'core_plugin', '', null, print_r($debuginfo, true)); - } -} - - -/** - * Singleton class that handles checking for available updates - */ -class available_update_checker { - - /** @var available_update_checker holds the singleton instance */ - protected static $singletoninstance; - /** @var null|int the timestamp of when the most recent response was fetched */ - protected $recentfetch = null; - /** @var null|array the recent response from the update notification provider */ - protected $recentresponse = null; - /** @var null|string the numerical version of the local Moodle code */ - protected $currentversion = null; - /** @var null|string the release info of the local Moodle code */ - protected $currentrelease = null; - /** @var null|string branch of the local Moodle code */ - protected $currentbranch = null; - /** @var array of (string)frankestyle => (string)version list of additional plugins deployed at this site */ - protected $currentplugins = array(); - - /** - * Direct initiation not allowed, use the factory method {@link self::instance()} - */ - protected function __construct() { - } - - /** - * Sorry, this is singleton - */ - protected function __clone() { - } - - /** - * Factory method for this class - * - * @return available_update_checker the singleton instance - */ - public static function instance() { - if (is_null(self::$singletoninstance)) { - self::$singletoninstance = new self(); - } - return self::$singletoninstance; - } - - /** - * Reset any caches - * @param bool $phpunitreset - */ - public static function reset_caches($phpunitreset = false) { - if ($phpunitreset) { - self::$singletoninstance = null; - } - } - - /** - * Returns the timestamp of the last execution of {@link fetch()} - * - * @return int|null null if it has never been executed or we don't known - */ - public function get_last_timefetched() { - - $this->restore_response(); - - if (!empty($this->recentfetch)) { - return $this->recentfetch; - - } else { - return null; - } - } - - /** - * Fetches the available update status from the remote site - * - * @throws available_update_checker_exception - */ - public function fetch() { - $response = $this->get_response(); - $this->validate_response($response); - $this->store_response($response); - } - - /** - * Returns the available update information for the given component - * - * This method returns null if the most recent response does not contain any information - * about it. The returned structure is an array of available updates for the given - * component. Each update info is an object with at least one property called - * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'. - * - * For the 'core' component, the method returns real updates only (those with higher version). - * For all other components, the list of all known remote updates is returned and the caller - * (usually the {@link plugin_manager}) is supposed to make the actual comparison of versions. - * - * @param string $component frankenstyle - * @param array $options with supported keys 'minmaturity' and/or 'notifybuilds' - * @return null|array null or array of available_update_info objects - */ - public function get_update_info($component, array $options = array()) { - - if (!isset($options['minmaturity'])) { - $options['minmaturity'] = 0; - } - - if (!isset($options['notifybuilds'])) { - $options['notifybuilds'] = false; - } - - if ($component == 'core') { - $this->load_current_environment(); - } - - $this->restore_response(); - - if (empty($this->recentresponse['updates'][$component])) { - return null; - } - - $updates = array(); - foreach ($this->recentresponse['updates'][$component] as $info) { - $update = new available_update_info($component, $info); - if (isset($update->maturity) and ($update->maturity < $options['minmaturity'])) { - continue; - } - if ($component == 'core') { - if ($update->version <= $this->currentversion) { - continue; - } - if (empty($options['notifybuilds']) and $this->is_same_release($update->release)) { - continue; - } - } - $updates[] = $update; - } - - if (empty($updates)) { - return null; - } - - return $updates; - } - - /** - * The method being run via cron.php - */ - public function cron() { - global $CFG; - - if (!$this->cron_autocheck_enabled()) { - $this->cron_mtrace('Automatic check for available updates not enabled, skipping.'); - return; - } - - $now = $this->cron_current_timestamp(); - - if ($this->cron_has_fresh_fetch($now)) { - $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.'); - return; - } - - if ($this->cron_has_outdated_fetch($now)) { - $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', ''); - $this->cron_execute(); - return; - } - - $offset = $this->cron_execution_offset(); - $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time - if ($now > $start + $offset) { - $this->cron_mtrace('Regular daily check for available updates ... ', ''); - $this->cron_execute(); - return; - } - } - - /// end of public API ////////////////////////////////////////////////////// - - /** - * Makes cURL request to get data from the remote site - * - * @return string raw request result - * @throws available_update_checker_exception - */ - protected function get_response() { - global $CFG; - require_once($CFG->libdir.'/filelib.php'); - - $curl = new curl(array('proxy' => true)); - $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params(), $this->prepare_request_options()); - $curlerrno = $curl->get_errno(); - if (!empty($curlerrno)) { - throw new available_update_checker_exception('err_response_curl', 'cURL error '.$curlerrno.': '.$curl->error); - } - $curlinfo = $curl->get_info(); - if ($curlinfo['http_code'] != 200) { - throw new available_update_checker_exception('err_response_http_code', $curlinfo['http_code']); - } - return $response; - } - - /** - * Makes sure the response is valid, has correct API format etc. - * - * @param string $response raw response as returned by the {@link self::get_response()} - * @throws available_update_checker_exception - */ - protected function validate_response($response) { - - $response = $this->decode_response($response); - - if (empty($response)) { - throw new available_update_checker_exception('err_response_empty'); - } - - if (empty($response['status']) or $response['status'] !== 'OK') { - throw new available_update_checker_exception('err_response_status', $response['status']); - } - - if (empty($response['apiver']) or $response['apiver'] !== '1.2') { - throw new available_update_checker_exception('err_response_format_version', $response['apiver']); - } - - if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) { - throw new available_update_checker_exception('err_response_target_version', $response['forbranch']); - } - } - - /** - * Decodes the raw string response from the update notifications provider - * - * @param string $response as returned by {@link self::get_response()} - * @return array decoded response structure - */ - protected function decode_response($response) { - return json_decode($response, true); - } - - /** - * Stores the valid fetched response for later usage - * - * This implementation uses the config_plugins table as the permanent storage. - * - * @param string $response raw valid data returned by {@link self::get_response()} - */ - protected function store_response($response) { - - set_config('recentfetch', time(), 'core_plugin'); - set_config('recentresponse', $response, 'core_plugin'); - - $this->restore_response(true); - } - - /** - * Loads the most recent raw response record we have fetched - * - * After this method is called, $this->recentresponse is set to an array. If the - * array is empty, then either no data have been fetched yet or the fetched data - * do not have expected format (and thence they are ignored and a debugging - * message is displayed). - * - * This implementation uses the config_plugins table as the permanent storage. - * - * @param bool $forcereload reload even if it was already loaded - */ - protected function restore_response($forcereload = false) { - - if (!$forcereload and !is_null($this->recentresponse)) { - // we already have it, nothing to do - return; - } - - $config = get_config('core_plugin'); - - if (!empty($config->recentresponse) and !empty($config->recentfetch)) { - try { - $this->validate_response($config->recentresponse); - $this->recentfetch = $config->recentfetch; - $this->recentresponse = $this->decode_response($config->recentresponse); - } catch (available_update_checker_exception $e) { - // The server response is not valid. Behave as if no data were fetched yet. - // This may happen when the most recent update info (cached locally) has been - // fetched with the previous branch of Moodle (like during an upgrade from 2.x - // to 2.y) or when the API of the response has changed. - $this->recentresponse = array(); - } - - } else { - $this->recentresponse = array(); - } - } - - /** - * Compares two raw {@link $recentresponse} records and returns the list of changed updates - * - * This method is used to populate potential update info to be sent to site admins. - * - * @param array $old - * @param array $new - * @throws available_update_checker_exception - * @return array parts of $new['updates'] that have changed - */ - protected function compare_responses(array $old, array $new) { - - if (empty($new)) { - return array(); - } - - if (!array_key_exists('updates', $new)) { - throw new available_update_checker_exception('err_response_format'); - } - - if (empty($old)) { - return $new['updates']; - } - - if (!array_key_exists('updates', $old)) { - throw new available_update_checker_exception('err_response_format'); - } - - $changes = array(); - - foreach ($new['updates'] as $newcomponent => $newcomponentupdates) { - if (empty($old['updates'][$newcomponent])) { - $changes[$newcomponent] = $newcomponentupdates; - continue; - } - foreach ($newcomponentupdates as $newcomponentupdate) { - $inold = false; - foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) { - if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) { - $inold = true; - } - } - if (!$inold) { - if (!isset($changes[$newcomponent])) { - $changes[$newcomponent] = array(); - } - $changes[$newcomponent][] = $newcomponentupdate; - } - } - } - - return $changes; - } - - /** - * Returns the URL to send update requests to - * - * During the development or testing, you can set $CFG->alternativeupdateproviderurl - * to a custom URL that will be used. Otherwise the standard URL will be returned. - * - * @return string URL - */ - protected function prepare_request_url() { - global $CFG; - - if (!empty($CFG->config_php_settings['alternativeupdateproviderurl'])) { - return $CFG->config_php_settings['alternativeupdateproviderurl']; - } else { - return 'https://download.moodle.org/api/1.2/updates.php'; - } - } - - /** - * Sets the properties currentversion, currentrelease, currentbranch and currentplugins - * - * @param bool $forcereload - */ - protected function load_current_environment($forcereload=false) { - global $CFG; - - if (!is_null($this->currentversion) and !$forcereload) { - // nothing to do - return; - } - - $version = null; - $release = null; - - require($CFG->dirroot.'/version.php'); - $this->currentversion = $version; - $this->currentrelease = $release; - $this->currentbranch = moodle_major_version(true); - - $pluginman = plugin_manager::instance(); - foreach ($pluginman->get_plugins() as $type => $plugins) { - foreach ($plugins as $plugin) { - if (!$plugin->is_standard()) { - $this->currentplugins[$plugin->component] = $plugin->versiondisk; - } - } - } - } - - /** - * Returns the list of HTTP params to be sent to the updates provider URL - * - * @return array of (string)param => (string)value - */ - protected function prepare_request_params() { - global $CFG; - - $this->load_current_environment(); - $this->restore_response(); - - $params = array(); - $params['format'] = 'json'; - - if (isset($this->recentresponse['ticket'])) { - $params['ticket'] = $this->recentresponse['ticket']; - } - - if (isset($this->currentversion)) { - $params['version'] = $this->currentversion; - } else { - throw new coding_exception('Main Moodle version must be already known here'); - } - - if (isset($this->currentbranch)) { - $params['branch'] = $this->currentbranch; - } else { - throw new coding_exception('Moodle release must be already known here'); - } - - $plugins = array(); - foreach ($this->currentplugins as $plugin => $version) { - $plugins[] = $plugin.'@'.$version; - } - if (!empty($plugins)) { - $params['plugins'] = implode(',', $plugins); - } - - return $params; - } - - /** - * Returns the list of cURL options to use when fetching available updates data - * - * @return array of (string)param => (string)value - */ - protected function prepare_request_options() { - global $CFG; - - $options = array( - 'CURLOPT_SSL_VERIFYHOST' => 2, // this is the default in {@link curl} class but just in case - 'CURLOPT_SSL_VERIFYPEER' => true, - ); - - return $options; - } - - /** - * Returns the current timestamp - * - * @return int the timestamp - */ - protected function cron_current_timestamp() { - return time(); - } - - /** - * Output cron debugging info - * - * @see mtrace() - * @param string $msg output message - * @param string $eol end of line - */ - protected function cron_mtrace($msg, $eol = PHP_EOL) { - mtrace($msg, $eol); - } - - /** - * Decide if the autocheck feature is disabled in the server setting - * - * @return bool true if autocheck enabled, false if disabled - */ - protected function cron_autocheck_enabled() { - global $CFG; - - if (empty($CFG->updateautocheck)) { - return false; - } else { - return true; - } - } - - /** - * Decide if the recently fetched data are still fresh enough - * - * @param int $now current timestamp - * @return bool true if no need to re-fetch, false otherwise - */ - protected function cron_has_fresh_fetch($now) { - $recent = $this->get_last_timefetched(); - - if (empty($recent)) { - return false; - } - - if ($now < $recent) { - $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!'); - return true; - } - - if ($now - $recent > 24 * HOURSECS) { - return false; - } - - return true; - } - - /** - * Decide if the fetch is outadated or even missing - * - * @param int $now current timestamp - * @return bool false if no need to re-fetch, true otherwise - */ - protected function cron_has_outdated_fetch($now) { - $recent = $this->get_last_timefetched(); - - if (empty($recent)) { - return true; - } - - if ($now < $recent) { - $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!'); - return false; - } - - if ($now - $recent > 48 * HOURSECS) { - return true; - } - - return false; - } - - /** - * Returns the cron execution offset for this site - * - * The main {@link self::cron()} is supposed to run every night in some random time - * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called - * execution offset, that is the amount of time after 01:00 AM. The offset value is - * initially generated randomly and then used consistently at the site. This way, the - * regular checks against the download.moodle.org server are spread in time. - * - * @return int the offset number of seconds from range 1 sec to 5 hours - */ - protected function cron_execution_offset() { - global $CFG; - - if (empty($CFG->updatecronoffset)) { - set_config('updatecronoffset', rand(1, 5 * HOURSECS)); - } - - return $CFG->updatecronoffset; - } - - /** - * Fetch available updates info and eventually send notification to site admins - */ - protected function cron_execute() { - - try { - $this->restore_response(); - $previous = $this->recentresponse; - $this->fetch(); - $this->restore_response(true); - $current = $this->recentresponse; - $changes = $this->compare_responses($previous, $current); - $notifications = $this->cron_notifications($changes); - $this->cron_notify($notifications); - $this->cron_mtrace('done'); - } catch (available_update_checker_exception $e) { - $this->cron_mtrace('FAILED!'); - } - } - - /** - * Given the list of changes in available updates, pick those to send to site admins - * - * @param array $changes as returned by {@link self::compare_responses()} - * @return array of available_update_info objects to send to site admins - */ - protected function cron_notifications(array $changes) { - global $CFG; - - $notifications = array(); - $pluginman = plugin_manager::instance(); - $plugins = $pluginman->get_plugins(true); - - foreach ($changes as $component => $componentchanges) { - if (empty($componentchanges)) { - continue; - } - $componentupdates = $this->get_update_info($component, - array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds)); - if (empty($componentupdates)) { - continue; - } - // notify only about those $componentchanges that are present in $componentupdates - // to respect the preferences - foreach ($componentchanges as $componentchange) { - foreach ($componentupdates as $componentupdate) { - if ($componentupdate->version == $componentchange['version']) { - if ($component == 'core') { - // In case of 'core', we already know that the $componentupdate - // is a real update with higher version ({@see self::get_update_info()}). - // We just perform additional check for the release property as there - // can be two Moodle releases having the same version (e.g. 2.4.0 and 2.5dev shortly - // after the release). We can do that because we have the release info - // always available for the core. - if ((string)$componentupdate->release === (string)$componentchange['release']) { - $notifications[] = $componentupdate; - } - } else { - // Use the plugin_manager to check if the detected $componentchange - // is a real update with higher version. That is, the $componentchange - // is present in the array of {@link available_update_info} objects - // returned by the plugin's available_updates() method. - list($plugintype, $pluginname) = core_component::normalize_component($component); - if (!empty($plugins[$plugintype][$pluginname])) { - $availableupdates = $plugins[$plugintype][$pluginname]->available_updates(); - if (!empty($availableupdates)) { - foreach ($availableupdates as $availableupdate) { - if ($availableupdate->version == $componentchange['version']) { - $notifications[] = $componentupdate; - } - } - } - } - } - } - } - } - } - - return $notifications; - } - - /** - * Sends the given notifications to site admins via messaging API - * - * @param array $notifications array of available_update_info objects to send - */ - protected function cron_notify(array $notifications) { - global $CFG; - - if (empty($notifications)) { - return; - } - - $admins = get_admins(); - - if (empty($admins)) { - return; - } - - $this->cron_mtrace('sending notifications ... ', ''); - - $text = get_string('updatenotifications', 'core_admin') . PHP_EOL; - $html = html_writer::tag('h1', get_string('updatenotifications', 'core_admin')) . PHP_EOL; - - $coreupdates = array(); - $pluginupdates = array(); - - foreach ($notifications as $notification) { - if ($notification->component == 'core') { - $coreupdates[] = $notification; - } else { - $pluginupdates[] = $notification; - } - } - - if (!empty($coreupdates)) { - $text .= PHP_EOL . get_string('updateavailable', 'core_admin') . PHP_EOL; - $html .= html_writer::tag('h2', get_string('updateavailable', 'core_admin')) . PHP_EOL; - $html .= html_writer::start_tag('ul') . PHP_EOL; - foreach ($coreupdates as $coreupdate) { - $html .= html_writer::start_tag('li'); - if (isset($coreupdate->release)) { - $text .= get_string('updateavailable_release', 'core_admin', $coreupdate->release); - $html .= html_writer::tag('strong', get_string('updateavailable_release', 'core_admin', $coreupdate->release)); - } - if (isset($coreupdate->version)) { - $text .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version); - $html .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version); - } - if (isset($coreupdate->maturity)) { - $text .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')'; - $html .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')'; - } - $text .= PHP_EOL; - $html .= html_writer::end_tag('li') . PHP_EOL; - } - $text .= PHP_EOL; - $html .= html_writer::end_tag('ul') . PHP_EOL; - - $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/index.php'); - $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL; - $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/index.php', $CFG->wwwroot.'/'.$CFG->admin.'/index.php')); - $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL; - } - - if (!empty($pluginupdates)) { - $text .= PHP_EOL . get_string('updateavailableforplugin', 'core_admin') . PHP_EOL; - $html .= html_writer::tag('h2', get_string('updateavailableforplugin', 'core_admin')) . PHP_EOL; - - $html .= html_writer::start_tag('ul') . PHP_EOL; - foreach ($pluginupdates as $pluginupdate) { - $html .= html_writer::start_tag('li'); - $text .= get_string('pluginname', $pluginupdate->component); - $html .= html_writer::tag('strong', get_string('pluginname', $pluginupdate->component)); - - $text .= ' ('.$pluginupdate->component.')'; - $html .= ' ('.$pluginupdate->component.')'; - - $text .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version); - $html .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version); - - $text .= PHP_EOL; - $html .= html_writer::end_tag('li') . PHP_EOL; - } - $text .= PHP_EOL; - $html .= html_writer::end_tag('ul') . PHP_EOL; - - $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php'); - $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL; - $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/plugins.php', $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php')); - $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL; - } - - $a = array('siteurl' => $CFG->wwwroot); - $text .= get_string('updatenotificationfooter', 'core_admin', $a) . PHP_EOL; - $a = array('siteurl' => html_writer::link($CFG->wwwroot, $CFG->wwwroot)); - $html .= html_writer::tag('footer', html_writer::tag('p', get_string('updatenotificationfooter', 'core_admin', $a), - array('style' => 'font-size:smaller; color:#333;'))); - - foreach ($admins as $admin) { - $message = new stdClass(); - $message->component = 'moodle'; - $message->name = 'availableupdate'; - $message->userfrom = get_admin(); - $message->userto = $admin; - $message->subject = get_string('updatenotificationsubject', 'core_admin', array('siteurl' => $CFG->wwwroot)); - $message->fullmessage = $text; - $message->fullmessageformat = FORMAT_PLAIN; - $message->fullmessagehtml = $html; - $message->smallmessage = get_string('updatenotifications', 'core_admin'); - $message->notification = 1; - message_send($message); - } - } - - /** - * Compare two release labels and decide if they are the same - * - * @param string $remote release info of the available update - * @param null|string $local release info of the local code, defaults to $release defined in version.php - * @return boolean true if the releases declare the same minor+major version - */ - protected function is_same_release($remote, $local=null) { - - if (is_null($local)) { - $this->load_current_environment(); - $local = $this->currentrelease; - } - - $pattern = '/^([0-9\.\+]+)([^(]*)/'; - - preg_match($pattern, $remote, $remotematches); - preg_match($pattern, $local, $localmatches); - - $remotematches[1] = str_replace('+', '', $remotematches[1]); - $localmatches[1] = str_replace('+', '', $localmatches[1]); - - if ($remotematches[1] === $localmatches[1] and rtrim($remotematches[2]) === rtrim($localmatches[2])) { - return true; - } else { - return false; - } - } -} - - -/** - * Defines the structure of objects returned by {@link available_update_checker::get_update_info()} - */ -class available_update_info { - - /** @var string frankenstyle component name */ - public $component; - /** @var int the available version of the component */ - public $version; - /** @var string|null optional release name */ - public $release = null; - /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */ - public $maturity = null; - /** @var string|null optional URL of a page with more info about the update */ - public $url = null; - /** @var string|null optional URL of a ZIP package that can be downloaded and installed */ - public $download = null; - /** @var string|null of self::download is set, then this must be the MD5 hash of the ZIP */ - public $downloadmd5 = null; - - /** - * Creates new instance of the class - * - * The $info array must provide at least the 'version' value and optionally all other - * values to populate the object's properties. - * - * @param string $name the frankenstyle component name - * @param array $info associative array with other properties - */ - public function __construct($name, array $info) { - $this->component = $name; - foreach ($info as $k => $v) { - if (property_exists('available_update_info', $k) and $k != 'component') { - $this->$k = $v; - } - } - } -} - - -/** - * Implements a communication bridge to the mdeploy.php utility - */ -class available_update_deployer { - - const HTTP_PARAM_PREFIX = 'updteautodpldata_'; // Hey, even Google has not heard of such a prefix! So it MUST be safe :-p - const HTTP_PARAM_CHECKER = 'datapackagesize'; // Name of the parameter that holds the number of items in the received data items - - /** @var available_update_deployer holds the singleton instance */ - protected static $singletoninstance; - /** @var moodle_url URL of a page that includes the deployer UI */ - protected $callerurl; - /** @var moodle_url URL to return after the deployment */ - protected $returnurl; - - /** - * Direct instantiation not allowed, use the factory method {@link self::instance()} - */ - protected function __construct() { - } - - /** - * Sorry, this is singleton - */ - protected function __clone() { - } - - /** - * Factory method for this class - * - * @return available_update_deployer the singleton instance - */ - public static function instance() { - if (is_null(self::$singletoninstance)) { - self::$singletoninstance = new self(); - } - return self::$singletoninstance; - } - - /** - * Reset caches used by this script - * - * @param bool $phpunitreset is this called as a part of PHPUnit reset? - */ - public static function reset_caches($phpunitreset = false) { - if ($phpunitreset) { - self::$singletoninstance = null; - } - } - - /** - * Is automatic deployment enabled? - * - * @return bool - */ - public function enabled() { - global $CFG; - - if (!empty($CFG->disableupdateautodeploy)) { - // The feature is prohibited via config.php - return false; - } - - return get_config('updateautodeploy'); - } - - /** - * Sets some base properties of the class to make it usable. - * - * @param moodle_url $callerurl the base URL of a script that will handle the class'es form data - * @param moodle_url $returnurl the final URL to return to when the deployment is finished - */ - public function initialize(moodle_url $callerurl, moodle_url $returnurl) { - - if (!$this->enabled()) { - throw new coding_exception('Unable to initialize the deployer, the feature is not enabled.'); - } - - $this->callerurl = $callerurl; - $this->returnurl = $returnurl; - } - - /** - * Has the deployer been initialized? - * - * Initialized deployer means that the following properties were set: - * callerurl, returnurl - * - * @return bool - */ - public function initialized() { - - if (!$this->enabled()) { - return false; - } - - if (empty($this->callerurl)) { - return false; - } - - if (empty($this->returnurl)) { - return false; - } - - return true; - } - - /** - * Returns a list of reasons why the deployment can not happen - * - * If the returned array is empty, the deployment seems to be possible. The returned - * structure is an associative array with keys representing individual impediments. - * Possible keys are: missingdownloadurl, missingdownloadmd5, notwritable. - * - * @param available_update_info $info - * @return array - */ - public function deployment_impediments(available_update_info $info) { - - $impediments = array(); - - if (empty($info->download)) { - $impediments['missingdownloadurl'] = true; - } - - if (empty($info->downloadmd5)) { - $impediments['missingdownloadmd5'] = true; - } - - if (!empty($info->download) and !$this->update_downloadable($info->download)) { - $impediments['notdownloadable'] = true; - } - - if (!$this->component_writable($info->component)) { - $impediments['notwritable'] = true; - } - - return $impediments; - } - - /** - * Check to see if the current version of the plugin seems to be a checkout of an external repository. - * - * @see plugin_manager::plugin_external_source() - * @param available_update_info $info - * @return false|string - */ - public function plugin_external_source(available_update_info $info) { - - $paths = core_component::get_plugin_types(); - list($plugintype, $pluginname) = core_component::normalize_component($info->component); - $pluginroot = $paths[$plugintype].'/'.$pluginname; - - if (is_dir($pluginroot.'/.git')) { - return 'git'; - } - - if (is_dir($pluginroot.'/CVS')) { - return 'cvs'; - } - - if (is_dir($pluginroot.'/.svn')) { - return 'svn'; - } - - return false; - } - - /** - * Prepares a renderable widget to confirm installation of an available update. - * - * @param available_update_info $info component version to deploy - * @return renderable - */ - public function make_confirm_widget(available_update_info $info) { - - if (!$this->initialized()) { - throw new coding_exception('Illegal method call - deployer not initialized.'); - } - - $params = $this->data_to_params(array( - 'updateinfo' => (array)$info, // see http://www.php.net/manual/en/language.types.array.php#language.types.array.casting - )); - - $widget = new single_button( - new moodle_url($this->callerurl, $params), - get_string('updateavailableinstall', 'core_admin'), - 'post' - ); - - return $widget; - } - - /** - * Prepares a renderable widget to execute installation of an available update. - * - * @param available_update_info $info component version to deploy - * @param moodle_url $returnurl URL to return after the installation execution - * @return renderable - */ - public function make_execution_widget(available_update_info $info, moodle_url $returnurl = null) { - global $CFG; - - if (!$this->initialized()) { - throw new coding_exception('Illegal method call - deployer not initialized.'); - } - - $pluginrootpaths = core_component::get_plugin_types(); - - list($plugintype, $pluginname) = core_component::normalize_component($info->component); - - if (empty($pluginrootpaths[$plugintype])) { - throw new coding_exception('Unknown plugin type root location', $plugintype); - } - - list($passfile, $password) = $this->prepare_authorization(); - - if (is_null($returnurl)) { - $returnurl = new moodle_url('/admin'); - } else { - $returnurl = $returnurl; - } - - $params = array( - 'upgrade' => true, - 'type' => $plugintype, - 'name' => $pluginname, - 'typeroot' => $pluginrootpaths[$plugintype], - 'package' => $info->download, - 'md5' => $info->downloadmd5, - 'dataroot' => $CFG->dataroot, - 'dirroot' => $CFG->dirroot, - 'passfile' => $passfile, - 'password' => $password, - 'returnurl' => $returnurl->out(false), - ); - - if (!empty($CFG->proxyhost)) { - // MDL-36973 - Beware - we should call just !is_proxybypass() here. But currently, our - // cURL wrapper class does not do it. So, to have consistent behaviour, we pass proxy - // setting regardless the $CFG->proxybypass setting. Once the {@link curl} class is - // fixed, the condition should be amended. - if (true or !is_proxybypass($info->download)) { - if (empty($CFG->proxyport)) { - $params['proxy'] = $CFG->proxyhost; - } else { - $params['proxy'] = $CFG->proxyhost.':'.$CFG->proxyport; - } - - if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) { - $params['proxyuserpwd'] = $CFG->proxyuser.':'.$CFG->proxypassword; - } - - if (!empty($CFG->proxytype)) { - $params['proxytype'] = $CFG->proxytype; - } - } - } - - $widget = new single_button( - new moodle_url('/mdeploy.php', $params), - get_string('updateavailableinstall', 'core_admin'), - 'post' - ); - - return $widget; - } - - /** - * Returns array of data objects passed to this tool. - * - * @return array - */ - public function submitted_data() { - - $data = $this->params_to_data($_POST); - - if (empty($data) or empty($data[self::HTTP_PARAM_CHECKER])) { - return false; - } - - if (!empty($data['updateinfo']) and is_object($data['updateinfo'])) { - $updateinfo = $data['updateinfo']; - if (!empty($updateinfo->component) and !empty($updateinfo->version)) { - $data['updateinfo'] = new available_update_info($updateinfo->component, (array)$updateinfo); - } - } - - if (!empty($data['callerurl'])) { - $data['callerurl'] = new moodle_url($data['callerurl']); - } - - if (!empty($data['returnurl'])) { - $data['returnurl'] = new moodle_url($data['returnurl']); - } - - return $data; - } - - /** - * Handles magic getters and setters for protected properties. - * - * @param string $name method name, e.g. set_returnurl() - * @param array $arguments arguments to be passed to the array - */ - public function __call($name, array $arguments = array()) { - - if (substr($name, 0, 4) === 'set_') { - $property = substr($name, 4); - if (empty($property)) { - throw new coding_exception('Invalid property name (empty)'); - } - if (empty($arguments)) { - $arguments = array(true); // Default value for flag-like properties. - } - // Make sure it is a protected property. - $isprotected = false; - $reflection = new ReflectionObject($this); - foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $reflectionproperty) { - if ($reflectionproperty->getName() === $property) { - $isprotected = true; - break; - } - } - if (!$isprotected) { - throw new coding_exception('Unable to set property - it does not exist or it is not protected'); - } - $value = reset($arguments); - $this->$property = $value; - return; - } - - if (substr($name, 0, 4) === 'get_') { - $property = substr($name, 4); - if (empty($property)) { - throw new coding_exception('Invalid property name (empty)'); - } - if (!empty($arguments)) { - throw new coding_exception('No parameter expected'); - } - // Make sure it is a protected property. - $isprotected = false; - $reflection = new ReflectionObject($this); - foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $reflectionproperty) { - if ($reflectionproperty->getName() === $property) { - $isprotected = true; - break; - } - } - if (!$isprotected) { - throw new coding_exception('Unable to get property - it does not exist or it is not protected'); - } - return $this->$property; - } - } - - /** - * Generates a random token and stores it in a file in moodledata directory. - * - * @return array of the (string)filename and (string)password in this order - */ - public function prepare_authorization() { - global $CFG; - - make_upload_directory('mdeploy/auth/'); - - $attempts = 0; - $success = false; - - while (!$success and $attempts < 5) { - $attempts++; - - $passfile = $this->generate_passfile(); - $password = $this->generate_password(); - $now = time(); - - $filepath = $CFG->dataroot.'/mdeploy/auth/'.$passfile; - - if (!file_exists($filepath)) { - $success = file_put_contents($filepath, $password . PHP_EOL . $now . PHP_EOL, LOCK_EX); - chmod($filepath, $CFG->filepermissions); - } - } - - if ($success) { - return array($passfile, $password); - - } else { - throw new moodle_exception('unable_prepare_authorization', 'core_plugin'); - } - } - - // End of external API - - /** - * Prepares an array of HTTP parameters that can be passed to another page. - * - * @param array|object $data associative array or an object holding the data, data JSON-able - * @return array suitable as a param for moodle_url - */ - protected function data_to_params($data) { - - // Append some our own data - if (!empty($this->callerurl)) { - $data['callerurl'] = $this->callerurl->out(false); - } - if (!empty($this->returnurl)) { - $data['returnurl'] = $this->returnurl->out(false); - } - - // Finally append the count of items in the package. - $data[self::HTTP_PARAM_CHECKER] = count($data); - - // Generate params - $params = array(); - foreach ($data as $name => $value) { - $transname = self::HTTP_PARAM_PREFIX.$name; - $transvalue = json_encode($value); - $params[$transname] = $transvalue; - } - - return $params; - } - - /** - * Converts HTTP parameters passed to the script into native PHP data - * - * @param array $params such as $_REQUEST or $_POST - * @return array data passed for this class - */ - protected function params_to_data(array $params) { - - if (empty($params)) { - return array(); - } - - $data = array(); - foreach ($params as $name => $value) { - if (strpos($name, self::HTTP_PARAM_PREFIX) === 0) { - $realname = substr($name, strlen(self::HTTP_PARAM_PREFIX)); - $realvalue = json_decode($value); - $data[$realname] = $realvalue; - } - } - - return $data; - } - - /** - * Returns a random string to be used as a filename of the password storage. - * - * @return string - */ - protected function generate_passfile() { - return clean_param(uniqid('mdeploy_', true), PARAM_FILE); - } - - /** - * Returns a random string to be used as the authorization token - * - * @return string - */ - protected function generate_password() { - return complex_random_string(); - } - - /** - * Checks if the given component's directory is writable - * - * For the purpose of the deployment, the web server process has to have - * write access to all files in the component's directory (recursively) and for the - * directory itself. - * - * @see worker::move_directory_source_precheck() - * @param string $component normalized component name - * @return boolean - */ - protected function component_writable($component) { - - list($plugintype, $pluginname) = core_component::normalize_component($component); - - $directory = core_component::get_plugin_directory($plugintype, $pluginname); - - if (is_null($directory)) { - throw new coding_exception('Unknown component location', $component); - } - - return $this->directory_writable($directory); - } - - /** - * Checks if the mdeploy.php will be able to fetch the ZIP from the given URL - * - * This is mainly supposed to check if the transmission over HTTPS would - * work. That is, if the CA certificates are present at the server. - * - * @param string $downloadurl the URL of the ZIP package to download - * @return bool - */ - protected function update_downloadable($downloadurl) { - global $CFG; - - $curloptions = array( - 'CURLOPT_SSL_VERIFYHOST' => 2, // this is the default in {@link curl} class but just in case - 'CURLOPT_SSL_VERIFYPEER' => true, - ); - - $curl = new curl(array('proxy' => true)); - $result = $curl->head($downloadurl, $curloptions); - $errno = $curl->get_errno(); - if (empty($errno)) { - return true; - } else { - return false; - } - } - - /** - * Checks if the directory and all its contents (recursively) is writable - * - * @param string $path full path to a directory - * @return boolean - */ - private function directory_writable($path) { - - if (!is_writable($path)) { - return false; - } - - if (is_dir($path)) { - $handle = opendir($path); - } else { - return false; - } - - $result = true; - - while ($filename = readdir($handle)) { - $filepath = $path.'/'.$filename; - - if ($filename === '.' or $filename === '..') { - continue; - } - - if (is_dir($filepath)) { - $result = $result && $this->directory_writable($filepath); - - } else { - $result = $result && is_writable($filepath); - } - } - - closedir($handle); - - return $result; - } -} - - -/** - * Factory class producing required subclasses of {@link plugininfo_base} - */ -class plugininfo_default_factory { - - /** - * Makes a new instance of the plugininfo class - * - * @param string $type the plugin type, eg. 'mod' - * @param string $typerootdir full path to the location of all the plugins of this type - * @param string $name the plugin name, eg. 'workshop' - * @param string $namerootdir full path to the location of the plugin - * @param string $typeclass the name of class that holds the info about the plugin - * @return plugininfo_base the instance of $typeclass - */ - public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) { - $plugin = new $typeclass(); - $plugin->type = $type; - $plugin->typerootdir = $typerootdir; - $plugin->name = $name; - $plugin->rootdir = $namerootdir; - - $plugin->init_display_name(); - $plugin->load_disk_version(); - $plugin->load_db_version(); - $plugin->init_is_standard(); - - return $plugin; - } -} - - -/** - * Base class providing access to the information about a plugin - * - * @property-read string component the component name, type_name - */ -abstract class plugininfo_base { - - /** @var string the plugintype name, eg. mod, auth or workshopform */ - public $type; - /** @var string full path to the location of all the plugins of this type */ - public $typerootdir; - /** @var string the plugin name, eg. assignment, ldap */ - public $name; - /** @var string the localized plugin name */ - public $displayname; - /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */ - public $source; - /** @var string fullpath to the location of this plugin */ - public $rootdir; - /** @var int|string the version of the plugin's source code */ - public $versiondisk; - /** @var int|string the version of the installed plugin */ - public $versiondb; - /** @var int|float|string required version of Moodle core */ - public $versionrequires; - /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */ - public $dependencies; - /** @var int number of instances of the plugin - not supported yet */ - public $instances; - /** @var int order of the plugin among other plugins of the same type - not supported yet */ - public $sortorder; - /** @var array|null array of {@link available_update_info} for this plugin */ - public $availableupdates; - - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - return null; - } - - /** - * Gathers and returns the information about all plugins of the given type, - * either on disk or previously installed. - * - * @param string $type the name of the plugintype, eg. mod, auth or workshopform - * @param string $typerootdir full path to the location of the plugin dir - * @param string $typeclass the name of the actually called class - * @return array of plugintype classes, indexed by the plugin name - */ - public static function get_plugins($type, $typerootdir, $typeclass) { - // Get the information about plugins at the disk. - $plugins = core_component::get_plugin_list($type); - $return = array(); - foreach ($plugins as $pluginname => $pluginrootdir) { - $return[$pluginname] = plugininfo_default_factory::make($type, $typerootdir, - $pluginname, $pluginrootdir, $typeclass); - } - - // Fetch missing incorrectly uninstalled plugins. - $manager = plugin_manager::instance(); - $plugins = $manager->get_installed_plugins($type); - - foreach ($plugins as $name => $version) { - if (isset($return[$name])) { - continue; - } - $plugin = new $typeclass(); - $plugin->type = $type; - $plugin->typerootdir = $typerootdir; - $plugin->name = $name; - $plugin->rootdir = null; - $plugin->displayname = $name; - $plugin->versiondb = $version; - $plugin->init_is_standard(); - - $return[$name] = $plugin; - } - - return $return; - } - - /** - * Is this plugin already installed and updated? - * @return bool true if plugin installed and upgraded. - */ - public function is_updated() { - if (!$this->rootdir) { - return false; - } - if ($this->versiondb === null and $this->versiondisk === null) { - // There is no version.php or version info inside, - // for now let's pretend it is ok. - // TODO: return false once we require version in each plugin. - return true; - } - - return ((float)$this->versiondb === (float)$this->versiondisk); - } - - /** - * Sets {@link $displayname} property to a localized name of the plugin - */ - public function init_display_name() { - if (!get_string_manager()->string_exists('pluginname', $this->component)) { - $this->displayname = '[pluginname,' . $this->component . ']'; - } else { - $this->displayname = get_string('pluginname', $this->component); - } - } - - /** - * Magic method getter, redirects to read only values. - * - * @param string $name - * @return mixed - */ - public function __get($name) { - switch ($name) { - case 'component': return $this->type . '_' . $this->name; - - default: - debugging('Invalid plugin property accessed! '.$name); - return null; - } - } - - /** - * Return the full path name of a file within the plugin. - * - * No check is made to see if the file exists. - * - * @param string $relativepath e.g. 'version.php'. - * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'. - */ - public function full_path($relativepath) { - if (empty($this->rootdir)) { - return ''; - } - return $this->rootdir . '/' . $relativepath; - } - - /** - * Sets {@link $versiondisk} property to a numerical value representing the - * version of the plugin's source code. - * - * If the value is null after calling this method, either the plugin - * does not use versioning (typically does not have any database - * data) or is missing from disk. - */ - public function load_disk_version() { - $versions = plugin_manager::instance()->get_present_plugins($this->type); - - $this->versiondisk = null; - $this->versionrequires = null; - $this->dependencies = array(); - - if (!isset($versions[$this->name])) { - return; - } - - $plugin = $versions[$this->name]; - - if (isset($plugin->version)) { - $this->versiondisk = $plugin->version; - } - if (isset($plugin->requires)) { - $this->versionrequires = $plugin->requires; - } - if (isset($plugin->dependencies)) { - $this->dependencies = $plugin->dependencies; - } - } - - /** - * Get the list of other plugins that this plugin requires to be installed. - * - * @return array with keys the frankenstyle plugin name, and values either - * a version string (like '2011101700') or the constant ANY_VERSION. - */ - public function get_other_required_plugins() { - if (is_null($this->dependencies)) { - $this->load_disk_version(); - } - return $this->dependencies; - } - - /** - * Is this is a subplugin? - * - * @return boolean - */ - public function is_subplugin() { - return ($this->get_parent_plugin() !== false); - } - - /** - * If I am a subplugin, return the name of my parent plugin. - * - * @return string|bool false if not a subplugin, name of the parent otherwise - */ - public function get_parent_plugin() { - return $this->get_plugin_manager()->get_parent_of_subplugin($this->type); - } - - /** - * Sets {@link $versiondb} property to a numerical value representing the - * currently installed version of the plugin. - * - * If the value is null after calling this method, either the plugin - * does not use versioning (typically does not have any database - * data) or has not been installed yet. - */ - public function load_db_version() { - $versions = plugin_manager::instance()->get_installed_plugins($this->type); - - if (isset($versions[$this->name])) { - $this->versiondb = $versions[$this->name]; - } else { - $this->versiondb = null; - } - } - - /** - * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx - * constants. - * - * If the property's value is null after calling this method, then - * the type of the plugin has not been recognized and you should throw - * an exception. - */ - public function init_is_standard() { - - $standard = plugin_manager::standard_plugins_list($this->type); - - if ($standard !== false) { - $standard = array_flip($standard); - if (isset($standard[$this->name])) { - $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; - } else if (!is_null($this->versiondb) and is_null($this->versiondisk) - and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) { - $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted - } else { - $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION; - } - } - } - - /** - * Returns true if the plugin is shipped with the official distribution - * of the current Moodle version, false otherwise. - * - * @return bool - */ - public function is_standard() { - return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD; - } - - /** - * Returns true if the the given Moodle version is enough to run this plugin - * - * @param string|int|double $moodleversion - * @return bool - */ - public function is_core_dependency_satisfied($moodleversion) { - - if (empty($this->versionrequires)) { - return true; - - } else { - return (double)$this->versionrequires <= (double)$moodleversion; - } - } - - /** - * Returns the status of the plugin - * - * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants - */ - public function get_status() { - - if (is_null($this->versiondb) and is_null($this->versiondisk)) { - return plugin_manager::PLUGIN_STATUS_NODB; - - } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) { - return plugin_manager::PLUGIN_STATUS_NEW; - - } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) { - if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) { - return plugin_manager::PLUGIN_STATUS_DELETE; - } else { - return plugin_manager::PLUGIN_STATUS_MISSING; - } - - } else if ((float)$this->versiondb === (float)$this->versiondisk) { - // Note: the float comparison should work fine here - // because there are no arithmetic operations with the numbers. - return plugin_manager::PLUGIN_STATUS_UPTODATE; - - } else if ($this->versiondb < $this->versiondisk) { - return plugin_manager::PLUGIN_STATUS_UPGRADE; - - } else if ($this->versiondb > $this->versiondisk) { - return plugin_manager::PLUGIN_STATUS_DOWNGRADE; - - } else { - // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-) - throw new coding_exception('Unable to determine plugin state, check the plugin versions'); - } - } - - /** - * Returns the information about plugin availability - * - * True means that the plugin is enabled. False means that the plugin is - * disabled. Null means that the information is not available, or the - * plugin does not support configurable availability or the availability - * can not be changed. - * - * @return null|bool - */ - public function is_enabled() { - if (!$this->rootdir) { - // Plugin missing. - return false; - } - - $enabled = plugin_manager::instance()->get_enabled_plugins($this->type); - - if (!is_array($enabled)) { - return null; - } - - return isset($enabled[$this->name]); - } - - /** - * Populates the property {@link $availableupdates} with the information provided by - * available update checker - * - * @param available_update_checker $provider the class providing the available update info - */ - public function check_available_updates(available_update_checker $provider) { - global $CFG; - - if (isset($CFG->updateminmaturity)) { - $minmaturity = $CFG->updateminmaturity; - } else { - // this can happen during the very first upgrade to 2.3 - $minmaturity = MATURITY_STABLE; - } - - $this->availableupdates = $provider->get_update_info($this->component, - array('minmaturity' => $minmaturity)); - } - - /** - * If there are updates for this plugin available, returns them. - * - * Returns array of {@link available_update_info} objects, if some update - * is available. Returns null if there is no update available or if the update - * availability is unknown. - * - * @return array|null - */ - public function available_updates() { - - if (empty($this->availableupdates) or !is_array($this->availableupdates)) { - return null; - } - - $updates = array(); - - foreach ($this->availableupdates as $availableupdate) { - if ($availableupdate->version > $this->versiondisk) { - $updates[] = $availableupdate; - } - } - - if (empty($updates)) { - return null; - } - - return $updates; - } - - /** - * Returns the node name used in admin settings menu for this plugin settings (if applicable) - * - * @return null|string node name or null if plugin does not create settings node (default) - */ - public function get_settings_section_name() { - return null; - } - - /** - * Returns the URL of the plugin settings screen - * - * Null value means that the plugin either does not have the settings screen - * or its location is not available via this library. - * - * @return null|moodle_url - */ - public function get_settings_url() { - $section = $this->get_settings_section_name(); - if ($section === null) { - return null; - } - $settings = admin_get_root()->locate($section); - if ($settings && $settings instanceof admin_settingpage) { - return new moodle_url('/admin/settings.php', array('section' => $section)); - } else if ($settings && $settings instanceof admin_externalpage) { - return new moodle_url($settings->url); - } else { - return null; - } - } - - /** - * Loads plugin settings to the settings tree - * - * This function usually includes settings.php file in plugins folder. - * Alternatively it can create a link to some settings page (instance of admin_externalpage) - * - * @param part_of_admin_tree $adminroot - * @param string $parentnodename - * @param bool $hassiteconfig whether the current user has moodle/site:config capability - */ - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - } - - /** - * Should there be a way to uninstall the plugin via the administration UI - * - * By default, uninstallation is allowed for all non-standard add-ons. Subclasses - * may want to override this to allow uninstallation of all plugins (simply by - * returning true unconditionally). Subplugins follow their parent plugin's - * decision by default. - * - * Note that even if true is returned, the core may still prohibit the uninstallation, - * e.g. in case there are other plugins that depend on this one. - * - * @return bool - */ - public function is_uninstall_allowed() { - - if ($this->is_subplugin()) { - return $this->get_plugin_manager()->get_plugin_info($this->get_parent_plugin())->is_uninstall_allowed(); - } - - if ($this->is_standard()) { - return false; - } - - return true; - } - - /** - * Optional extra warning before uninstallation, for example number of uses in courses. - * - * @return string - */ - public function get_uninstall_extra_warning() { - return ''; - } - - /** - * Returns the URL of the screen where this plugin can be uninstalled - * - * Visiting that URL must be safe, that is a manual confirmation is needed - * for actual uninstallation of the plugin. By default, URL to a common - * uninstalling tool is returned. - * - * @return moodle_url - */ - public function get_uninstall_url() { - return $this->get_default_uninstall_url(); - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - // Override when extending class, - // do not forget to call parent::pre_uninstall_cleanup() at the end. - } - - /** - * Returns relative directory of the plugin with heading '/' - * - * @return string - */ - public function get_dir() { - global $CFG; - - return substr($this->rootdir, strlen($CFG->dirroot)); - } - - /** - * Hook method to implement certain steps when uninstalling the plugin. - * - * This hook is called by {@link plugin_manager::uninstall_plugin()} so - * it is basically usable only for those plugin types that use the default - * uninstall tool provided by {@link self::get_default_uninstall_url()}. - * - * @param progress_trace $progress traces the process - * @return bool true on success, false on failure - */ - public function uninstall(progress_trace $progress) { - return true; - } - - /** - * Returns URL to a script that handles common plugin uninstall procedure. - * - * This URL is suitable for plugins that do not have their own UI - * for uninstalling. - * - * @return moodle_url - */ - protected final function get_default_uninstall_url() { - return new moodle_url('/admin/plugins.php', array( - 'sesskey' => sesskey(), - 'uninstall' => $this->component, - 'confirm' => 0, - )); - } - - /** - * Provides access to the plugin_manager singleton. - * - * @return plugin_manager - */ - protected function get_plugin_manager() { - return plugin_manager::instance(); - } -} - - -/** - * General class for all plugin types that do not have their own class - */ -class plugininfo_general extends plugininfo_base { -} - - -/** - * Class for page side blocks - */ -class plugininfo_block extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB; - - return $DB->get_records_menu('block', array('visible'=>1), 'name ASC', 'name, name AS val'); - } - - /** - * Magic method getter, redirects to read only values. - * - * For block plugins pretends the object has 'visible' property for compatibility - * with plugins developed for Moodle version below 2.4 - * - * @param string $name - * @return mixed - */ - public function __get($name) { - if ($name === 'visible') { - debugging('This is now an instance of plugininfo_block, please use $block->is_enabled() instead of $block->visible', DEBUG_DEVELOPER); - return ($this->is_enabled() !== false); - } - return parent::__get($name); - } - - public function init_display_name() { - - if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) { - $this->displayname = get_string('pluginname', 'block_' . $this->name); - - } else if (($block = block_instance($this->name)) !== false) { - $this->displayname = $block->get_title(); - - } else { - parent::init_display_name(); - } - } - - public function get_settings_section_name() { - return 'blocksetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $block = $this; // also can be used inside settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $section = $this->get_settings_section_name(); - - if (!$hassiteconfig || (($blockinstance = block_instance($this->name)) === false)) { - return; - } - - $settings = null; - if ($blockinstance->has_config()) { - if (file_exists($this->full_path('settings.php'))) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - return true; - } - - /** - * Warnign with number of block instances. - * - * @return string - */ - public function get_uninstall_extra_warning() { - global $DB; - - if (!$count = $DB->count_records('block_instances', array('blockname'=>$this->name))) { - return ''; - } - - return '

'.get_string('uninstallextraconfirmblock', 'core_plugin', array('instances'=>$count)).'

'; - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - global $DB, $CFG; - - if ($block = $DB->get_record('block', array('name'=>$this->name))) { - // Inform block it's about to be deleted - if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) { - $blockobject = block_instance($block->name); - if ($blockobject) { - $blockobject->before_delete(); //only if we can create instance, block might have been already removed - } - } - - // First delete instances and related contexts - $instances = $DB->get_records('block_instances', array('blockname' => $block->name)); - foreach($instances as $instance) { - blocks_delete_instance($instance); - } - - // Delete block - $DB->delete_records('block', array('id'=>$block->id)); - } - - parent::uninstall_cleanup(); - } -} - - -/** - * Class for text filters - */ -class plugininfo_filter extends plugininfo_base { - - public function init_display_name() { - if (!get_string_manager()->string_exists('filtername', $this->component)) { - $this->displayname = '[filtername,' . $this->component . ']'; - } else { - $this->displayname = get_string('filtername', $this->component); - } - } - - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB, $CFG; - require_once("$CFG->libdir/filterlib.php"); - - $enabled = array(); - $filters = $DB->get_records_select('filter_active', "active <> :disabled", array('disabled'=>TEXTFILTER_DISABLED), 'filter ASC', 'id, filter'); - foreach ($filters as $filter) { - $enabled[$filter->filter] = $filter->filter; - } - - return $enabled; - } - - public function get_settings_section_name() { - return 'filtersetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $filter = $this; // also can be used inside settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $settings = null; - if ($hassiteconfig && file_exists($this->full_path('filtersettings.php'))) { - $section = $this->get_settings_section_name(); - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('filtersettings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - return true; - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - global $DB; - - $DB->delete_records('filter_active', array('filter' => $this->name)); - $DB->delete_records('filter_config', array('filter' => $this->name)); - - parent::uninstall_cleanup(); - } -} - - -/** - * Class for activity modules - */ -class plugininfo_mod extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB; - return $DB->get_records_menu('modules', array('visible'=>1), 'name ASC', 'name, name AS val'); - } - - /** - * Magic method getter, redirects to read only values. - * - * For module plugins we pretend the object has 'visible' property for compatibility - * with plugins developed for Moodle version below 2.4 - * - * @param string $name - * @return mixed - */ - public function __get($name) { - if ($name === 'visible') { - debugging('This is now an instance of plugininfo_mod, please use $module->is_enabled() instead of $module->visible', DEBUG_DEVELOPER); - return ($this->is_enabled() !== false); - } - return parent::__get($name); - } - - public function init_display_name() { - if (get_string_manager()->string_exists('pluginname', $this->component)) { - $this->displayname = get_string('pluginname', $this->component); - } else { - $this->displayname = get_string('modulename', $this->component); - } - } - - public function get_settings_section_name() { - return 'modsetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $module = $this; // also can be used inside settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $section = $this->get_settings_section_name(); - - $settings = null; - if ($hassiteconfig && file_exists($this->full_path('settings.php'))) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - /** - * Allow all activity modules but Forum to be uninstalled. - - * This exception for the Forum has been hard-coded in Moodle since ages, - * we may want to re-think it one day. - */ - public function is_uninstall_allowed() { - if ($this->name === 'forum') { - return false; - } else { - return true; - } - } - - /** - * Return warning with number of activities and number of affected courses. - * - * @return string - */ - public function get_uninstall_extra_warning() { - global $DB; - - if (!$module = $DB->get_record('modules', array('name'=>$this->name))) { - return ''; - } - - if (!$count = $DB->count_records('course_modules', array('module'=>$module->id))) { - return ''; - } - - $sql = "SELECT COUNT('x') - FROM ( - SELECT course - FROM {course_modules} - WHERE module = :mid - GROUP BY course - ) c"; - $courses = $DB->count_records_sql($sql, array('mid'=>$module->id)); - - return '

'.get_string('uninstallextraconfirmmod', 'core_plugin', array('instances'=>$count, 'courses'=>$courses)).'

'; - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - global $DB, $CFG; - - if (!$module = $DB->get_record('modules', array('name' => $this->name))) { - parent::uninstall_cleanup(); - return; - } - - // Delete all the relevant instances from all course sections. - if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) { - foreach ($coursemods as $coursemod) { - // Do not verify results, there is not much we can do anyway. - delete_mod_from_section($coursemod->id, $coursemod->section); - } - } - - // Increment course.cacherev for courses that used this module. - // This will force cache rebuilding on the next request. - increment_revision_number('course', 'cacherev', - "id IN (SELECT DISTINCT course - FROM {course_modules} - WHERE module=?)", - array($module->id)); - - // Delete all the course module records. - $DB->delete_records('course_modules', array('module' => $module->id)); - - // Delete module contexts. - if ($coursemods) { - foreach ($coursemods as $coursemod) { - context_helper::delete_instance(CONTEXT_MODULE, $coursemod->id); - } - } - - // Delete the module entry itself. - $DB->delete_records('modules', array('name' => $module->name)); - - // Cleanup the gradebook. - require_once($CFG->libdir.'/gradelib.php'); - grade_uninstalled_module($module->name); - - // Do not look for legacy $module->name . '_uninstall any more, - // they should have migrated to db/uninstall.php by now. - - parent::uninstall_cleanup(); - } -} - - -/** - * Class for question behaviours. - */ -class plugininfo_qbehaviour extends plugininfo_base { - - public function is_uninstall_allowed() { - return true; - } - - public function get_uninstall_url() { - return new moodle_url('/admin/qbehaviours.php', - array('delete' => $this->name, 'sesskey' => sesskey())); - } -} - - -/** - * Class for question types - */ -class plugininfo_qtype extends plugininfo_base { - - public function is_uninstall_allowed() { - return true; - } - - public function get_uninstall_url() { - return new moodle_url('/admin/qtypes.php', - array('delete' => $this->name, 'sesskey' => sesskey())); - } - - public function get_settings_section_name() { - return 'qtypesetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $qtype = $this; // also can be used inside settings.php - - if (!$this->rootdir) { - // Most probably somebody deleted dir without proper uninstall. - return; - } - $section = $this->get_settings_section_name(); - - $settings = null; - $systemcontext = context_system::instance(); - if (($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) && - file_exists($this->full_path('settings.php'))) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/question:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } -} - - -/** - * Class for authentication plugins - */ -class plugininfo_auth extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $CFG; - - // These two are always enabled and can't be disabled. - $enabled = array('nologin'=>'nologin', 'manual'=>'manual'); - foreach (explode(',', $CFG->auth) as $auth) { - $enabled[$auth] = $auth; - } - - return $enabled; - } - - public function get_settings_section_name() { - return 'authsetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $auth = $this; // also to be used inside settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $section = $this->get_settings_section_name(); - - $settings = null; - if ($hassiteconfig) { - if (file_exists($this->full_path('settings.php'))) { - // TODO: finish implementation of common settings - locking, etc. - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } else { - $settingsurl = new moodle_url('/admin/auth_config.php', array('auth' => $this->name)); - $settings = new admin_externalpage($section, $this->displayname, - $settingsurl, 'moodle/site:config', $this->is_enabled() === false); - } - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } -} - - -/** - * Class for enrolment plugins - */ -class plugininfo_enrol extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $CFG; - - $enabled = array(); - foreach (explode(',', $CFG->enrol_plugins_enabled) as $enrol) { - $enabled[$enrol] = $enrol; - } - - return $enabled; - } - - public function get_settings_section_name() { - if (file_exists($this->full_path('settings.php'))) { - return 'enrolsettings' . $this->name; - } else { - return null; - } - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { - return; - } - $section = $this->get_settings_section_name(); - - $ADMIN = $adminroot; // may be used in settings.php - $enrol = $this; // also can be used inside settings.php - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - - include($this->full_path('settings.php')); // This may also set $settings to null! - - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - if ($this->name === 'manual') { - return false; - } - return true; - } - - /** - * Return warning with number of activities and number of affected courses. - * - * @return string - */ - public function get_uninstall_extra_warning() { - global $DB, $OUTPUT; - - $sql = "SELECT COUNT('x') - FROM {user_enrolments} ue - JOIN {enrol} e ON e.id = ue.enrolid - WHERE e.enrol = :plugin"; - $count = $DB->count_records_sql($sql, array('plugin'=>$this->name)); - - if (!$count) { - return ''; - } - - $migrateurl = new moodle_url('/admin/enrol.php', array('action'=>'migrate', 'enrol'=>$this->name, 'sesskey'=>sesskey())); - $migrate = new single_button($migrateurl, get_string('migratetomanual', 'core_enrol')); - $button = $OUTPUT->render($migrate); - - $result = '

'.get_string('uninstallextraconfirmenrol', 'core_plugin', array('enrolments'=>$count)).'

'; - $result .= $button; - - return $result; - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - global $DB, $CFG; - - // NOTE: this is a bit brute force way - it will not trigger events and hooks properly. - - // Nuke all role assignments. - role_unassign_all(array('component'=>'enrol_'.$this->name)); - - // Purge participants. - $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($this->name)); - - // Purge enrol instances. - $DB->delete_records('enrol', array('enrol'=>$this->name)); - - // Tweak enrol settings. - if (!empty($CFG->enrol_plugins_enabled)) { - $enabledenrols = explode(',', $CFG->enrol_plugins_enabled); - $enabledenrols = array_unique($enabledenrols); - $enabledenrols = array_flip($enabledenrols); - unset($enabledenrols[$this->name]); - $enabledenrols = array_flip($enabledenrols); - if (is_array($enabledenrols)) { - set_config('enrol_plugins_enabled', implode(',', $enabledenrols)); - } - } - - parent::uninstall_cleanup(); - } -} - - -/** - * Class for messaging processors - */ -class plugininfo_message extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB; - return $DB->get_records_menu('message_processors', array('enabled'=>1), 'name ASC', 'name, name AS val'); - } - - public function get_settings_section_name() { - return 'messagesetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - if (!$hassiteconfig) { - return; - } - $section = $this->get_settings_section_name(); - - $settings = null; - $processors = get_message_processors(); - if (isset($processors[$this->name])) { - $processor = $processors[$this->name]; - if ($processor->available && $processor->hassettings) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - return true; - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - global $CFG; - - require_once($CFG->libdir.'/messagelib.php'); - message_processor_uninstall($this->name); - - parent::uninstall_cleanup(); - } -} - - -/** - * Class for repositories - */ -class plugininfo_repository extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB; - return $DB->get_records_menu('repository', array('visible'=>1), 'type ASC', 'type, type AS val'); - } - - public function get_settings_section_name() { - return 'repositorysettings'.$this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - if (!$this->rootdir) { - // Plugin missing. - return; - } - - if ($hassiteconfig && $this->is_enabled()) { - // completely no access to repository setting when it is not enabled - $sectionname = $this->get_settings_section_name(); - $settingsurl = new moodle_url('/admin/repository.php', - array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name)); - $settings = new admin_externalpage($sectionname, $this->displayname, - $settingsurl, 'moodle/site:config', false); - $adminroot->add($parentnodename, $settings); - } - } -} - - -/** - * Class for portfolios - */ -class plugininfo_portfolio extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB; - - $enabled = array(); - $rs = $DB->get_recordset('portfolio_instance', array('visible'=>1), 'plugin ASC', 'plugin'); - foreach ($rs as $repository) { - $enabled[$repository->plugin] = $repository->plugin; - } - - return $enabled; - } -} - - -/** - * Class for themes - */ -class plugininfo_theme extends plugininfo_base { - - public function is_enabled() { - global $CFG; - - if ((!empty($CFG->theme) and $CFG->theme === $this->name) or - (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) { - return true; - } else { - return parent::is_enabled(); - } - } -} - - -/** - * Class representing an MNet service - */ -class plugininfo_mnetservice extends plugininfo_base { - - public function is_enabled() { - global $CFG; - - if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') { - return false; - } else { - return parent::is_enabled(); - } - } -} - - -/** - * Class for admin tool plugins - */ -class plugininfo_tool extends plugininfo_base { - - public function is_uninstall_allowed() { - return true; - } -} - - -/** - * Class for admin tool plugins - */ -class plugininfo_report extends plugininfo_base { - - public function is_uninstall_allowed() { - return true; - } -} - - -/** - * Class for local plugins - */ -class plugininfo_local extends plugininfo_base { - - public function is_uninstall_allowed() { - return true; + return core_plugin_manager::instance(); } } /** - * Class for HTML editors + * @deprecated since 2.6 - use \core\plugininfo\base instead. */ -class plugininfo_editor extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $CFG; - - if (empty($CFG->texteditors)) { - return array('tinymce'=>'tinymce', 'textarea'=>'textarea'); - } - - $enabled = array(); - foreach (explode(',', $CFG->texteditors) as $editor) { - $enabled[$editor] = $editor; - } - - return $enabled; - } - - public function get_settings_section_name() { - return 'editorsettings' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $editor = $this; // also can be used inside settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $section = $this->get_settings_section_name(); - - $settings = null; - if ($hassiteconfig && file_exists($this->full_path('settings.php'))) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - /** - * Basic textarea editor can not be uninstalled. - */ - public function is_uninstall_allowed() { - if ($this->name === 'textarea') { - return false; - } else { - return true; - } - } +class plugininfo_base extends \core\plugininfo\base { + // BC only. } -/** - * Class for plagiarism plugins - */ -class plugininfo_plagiarism extends plugininfo_base { - - public function get_settings_section_name() { - return 'plagiarism'. $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - if (!$this->rootdir) { - // Plugin missing. - return; - } - - // plagiarism plugin just redirect to settings.php in the plugins directory - if ($hassiteconfig && file_exists($this->full_path('settings.php'))) { - $section = $this->get_settings_section_name(); - $settingsurl = new moodle_url($this->get_dir().'/settings.php'); - $settings = new admin_externalpage($section, $this->displayname, - $settingsurl, 'moodle/site:config', $this->is_enabled() === false); - $adminroot->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - return true; - } -} - -/** - * Class for webservice protocols - */ -class plugininfo_webservice extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $CFG; - - if (empty($CFG->enablewebservices) or empty($CFG->webserviceprotocols)) { - return array(); - } - - $enabled = array(); - foreach (explode(',', $CFG->webserviceprotocols) as $protocol) { - $enabled[$protocol] = $protocol; - } - - return $enabled; - } - - public function get_settings_section_name() { - return 'webservicesetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // may be used in settings.php - $webservice = $this; // also can be used inside settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $section = $this->get_settings_section_name(); - - $settings = null; - if ($hassiteconfig && file_exists($this->full_path('settings.php'))) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - return false; - } -} - -/** - * Class for course formats - */ -class plugininfo_format extends plugininfo_base { - /** - * Finds all enabled plugins, the result may include missing plugins. - * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown - */ - public static function get_enabled_plugins() { - global $DB; - - $plugins = plugin_manager::instance()->get_installed_plugins('format'); - if (!$plugins) { - return array(); - } - $installed = array(); - foreach ($plugins as $plugin => $version) { - $installed[] = 'format_'.$plugin; - } - - list($installed, $params) = $DB->get_in_or_equal($installed, SQL_PARAMS_NAMED); - $disabled = $DB->get_recordset_select('config_plugins', "plugin $installed AND name = 'disabled'", $params, 'plugin ASC'); - foreach ($disabled as $conf) { - if (empty($conf->value)) { - continue; - } - list($type, $name) = explode('_', $conf->plugin, 2); - unset($plugins[$name]); - } - - $enabled = array(); - foreach ($plugins as $plugin => $version) { - $enabled[$plugin] = $plugin; - } - - return $enabled; - } - - /** - * Gathers and returns the information about all plugins of the given type - * - * @param string $type the name of the plugintype, eg. mod, auth or workshopform - * @param string $typerootdir full path to the location of the plugin dir - * @param string $typeclass the name of the actually called class - * @return array of plugintype classes, indexed by the plugin name - */ - public static function get_plugins($type, $typerootdir, $typeclass) { - global $CFG; - $formats = parent::get_plugins($type, $typerootdir, $typeclass); - require_once($CFG->dirroot.'/course/lib.php'); - $order = get_sorted_course_formats(); - $sortedformats = array(); - foreach ($order as $formatname) { - $sortedformats[$formatname] = $formats[$formatname]; - } - return $sortedformats; - } - - public function get_settings_section_name() { - return 'formatsetting' . $this->name; - } - - public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { - global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them - $ADMIN = $adminroot; // also may be used in settings.php - - if (!$this->rootdir) { - // Plugin missing. - return; - } - - $section = $this->get_settings_section_name(); - - $settings = null; - if ($hassiteconfig && file_exists($this->full_path('settings.php'))) { - $settings = new admin_settingpage($section, $this->displayname, - 'moodle/site:config', $this->is_enabled() === false); - include($this->full_path('settings.php')); // this may also set $settings to null - } - if ($settings) { - $ADMIN->add($parentnodename, $settings); - } - } - - public function is_uninstall_allowed() { - if ($this->name !== get_config('moodlecourse', 'format') && $this->name !== 'site') { - return true; - } else { - return false; - } - } - - public function get_uninstall_extra_warning() { - global $DB; - - $coursecount = $DB->count_records('course', array('format' => $this->name)); - - if (!$coursecount) { - return ''; - } - - $defaultformat = $this->get_plugin_manager()->plugin_name('format_'.get_config('moodlecourse', 'format')); - $message = get_string( - 'formatuninstallwithcourses', 'core_admin', - (object)array('count' => $coursecount, 'format' => $this->displayname, - 'defaultformat' => $defaultformat)); - - return $message; - } - - /** - * Pre-uninstall hook. - * - * This is intended for disabling of plugin, some DB table purging, etc. - * - * NOTE: to be called from uninstall_plugin() only. - * @private - */ - public function uninstall_cleanup() { - global $DB; - - if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $this->name) { - $courses = $DB->get_records('course', array('format' => $this->name), 'id'); - $data = (object)array('id' => null, 'format' => $defaultformat); - foreach ($courses as $record) { - $data->id = $record->id; - update_course($data); - } - } - - $DB->delete_records('course_format_options', array('format' => $this->name)); - - parent::uninstall_cleanup(); - } -} diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 0090081cc28..d9f9f9a76c5 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -323,6 +323,42 @@ class core_component_testcase extends advanced_testcase { } } + public function test_get_subtype_parent() { + global $CFG; + + $this->assertNull(core_component::get_subtype_parent('mod')); + + // Any plugin with more subtypes is ok here. + $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.php"); + $this->assertSame('mod_assign', core_component::get_subtype_parent('assignsubmission')); + $this->assertSame('mod_assign', core_component::get_subtype_parent('assignfeedback')); + $this->assertNull(core_component::get_subtype_parent('assignxxxxx')); + } + + public function test_get_subplugins() { + global $CFG; + + // Any plugin with more subtypes is ok here. + $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.php"); + + $subplugins = core_component::get_subplugins('mod_assign'); + $this->assertSame(array('assignsubmission', 'assignfeedback'), array_keys($subplugins)); + + $subs = core_component::get_plugin_list('assignsubmission'); + $feeds = core_component::get_plugin_list('assignfeedback'); + + $this->assertSame(array_keys($subs), $subplugins['assignsubmission']); + $this->assertSame(array_keys($feeds), $subplugins['assignfeedback']); + + // Any plugin without subtypes is ok here. + $this->assertFileExists("$CFG->dirroot/mod/choice"); + $this->assertFileNotExists("$CFG->dirroot/mod/choice/db/subplugins.php"); + + $this->assertNull(core_component::get_subplugins('mod_choice')); + + $this->assertNull(core_component::get_subplugins('xxxx_yyyy')); + } + public function test_get_plugin_types_with_subplugins() { global $CFG; diff --git a/lib/tests/plugin_manager_test.php b/lib/tests/plugin_manager_test.php index 4a8f3788354..f91f722bd0a 100644 --- a/lib/tests/plugin_manager_test.php +++ b/lib/tests/plugin_manager_test.php @@ -25,30 +25,26 @@ defined('MOODLE_INTERNAL') || die(); -global $CFG; -require_once($CFG->libdir.'/pluginlib.php'); - - /** * Tests of the basic API of the plugin manager. */ class core_plugin_manager_testcase extends advanced_testcase { public function test_instance() { - $pluginman = plugin_manager::instance(); - $this->assertInstanceOf('plugin_manager', $pluginman); - $pluginman2 = plugin_manager::instance(); + $pluginman = core_plugin_manager::instance(); + $this->assertInstanceOf('core_plugin_manager', $pluginman); + $pluginman2 = core_plugin_manager::instance(); $this->assertSame($pluginman, $pluginman2); } public function test_reset_caches() { // Make sure there are no warnings or errors. - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } public function test_get_plugin_types() { // Make sure there are no warnings or errors. - $types = plugin_manager::instance()->get_plugin_types(); + $types = core_plugin_manager::instance()->get_plugin_types(); $this->assertInternalType('array', $types); foreach ($types as $type => $fulldir) { $this->assertFileExists($fulldir); @@ -56,9 +52,9 @@ class core_plugin_manager_testcase extends advanced_testcase { } public function test_get_installed_plugins() { - $types = plugin_manager::instance()->get_plugin_types(); + $types = core_plugin_manager::instance()->get_plugin_types(); foreach ($types as $type => $fulldir) { - $installed = plugin_manager::instance()->get_installed_plugins($type); + $installed = core_plugin_manager::instance()->get_installed_plugins($type); foreach ($installed as $plugin => $version) { $this->assertRegExp('/^[a-z]+[a-z0-9_]*$/', $plugin); $this->assertTrue(is_numeric($version), 'All plugins should have a version, plugin '.$type.'_'.$plugin.' does not have version info.'); @@ -67,9 +63,9 @@ class core_plugin_manager_testcase extends advanced_testcase { } public function test_get_enabled_plugins() { - $types = plugin_manager::instance()->get_plugin_types(); + $types = core_plugin_manager::instance()->get_plugin_types(); foreach ($types as $type => $fulldir) { - $enabled = plugin_manager::instance()->get_enabled_plugins($type); + $enabled = core_plugin_manager::instance()->get_enabled_plugins($type); if (is_array($enabled)) { foreach ($enabled as $key => $val) { $this->assertRegExp('/^[a-z]+[a-z0-9_]*$/', $key); @@ -82,9 +78,9 @@ class core_plugin_manager_testcase extends advanced_testcase { } public function test_get_present_plugins() { - $types = plugin_manager::instance()->get_plugin_types(); + $types = core_plugin_manager::instance()->get_plugin_types(); foreach ($types as $type => $fulldir) { - $present = plugin_manager::instance()->get_present_plugins($type); + $present = core_plugin_manager::instance()->get_present_plugins($type); if (is_array($present)) { foreach ($present as $plugin => $version) { $this->assertRegExp('/^[a-z]+[a-z0-9_]*$/', $plugin, 'All plugins are supposed to have version.php file.'); @@ -99,18 +95,18 @@ class core_plugin_manager_testcase extends advanced_testcase { } public function test_get_plugins() { - $plugininfos = plugin_manager::instance()->get_plugins(); + $plugininfos = core_plugin_manager::instance()->get_plugins(); foreach ($plugininfos as $type => $infos) { foreach ($infos as $name => $info) { - $this->assertInstanceOf('plugininfo_base', $info); + $this->assertInstanceOf('\core\plugininfo\base', $info); } } } public function test_get_plugins_of_type() { - $plugininfos = plugin_manager::instance()->get_plugins(); + $plugininfos = core_plugin_manager::instance()->get_plugins(); foreach ($plugininfos as $type => $infos) { - $this->assertSame($infos, plugin_manager::instance()->get_plugins_of_type($type)); + $this->assertSame($infos, core_plugin_manager::instance()->get_plugins_of_type($type)); } } @@ -120,15 +116,15 @@ class core_plugin_manager_testcase extends advanced_testcase { // Any standard plugin with subplugins is suitable. $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.'); - $subplugins = plugin_manager::instance()->get_subplugins_of_plugin('editor_tinymce'); + $subplugins = core_plugin_manager::instance()->get_subplugins_of_plugin('editor_tinymce'); foreach ($subplugins as $component => $info) { - $this->assertInstanceOf('plugininfo_base', $info); + $this->assertInstanceOf('\core\plugininfo\base', $info); } } public function test_get_subplugins() { // Tested already indirectly from test_get_subplugins_of_plugin(). - $subplugins = plugin_manager::instance()->get_subplugins(); + $subplugins = core_plugin_manager::instance()->get_subplugins(); $this->assertInternalType('array', $subplugins); } @@ -138,7 +134,7 @@ class core_plugin_manager_testcase extends advanced_testcase { // Any standard plugin with subplugins is suitable. $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.'); - $parent = plugin_manager::instance()->get_parent_of_subplugin('tinymce'); + $parent = core_plugin_manager::instance()->get_parent_of_subplugin('tinymce'); $this->assertSame('editor_tinymce', $parent); } @@ -148,17 +144,17 @@ class core_plugin_manager_testcase extends advanced_testcase { // Any standard plugin is suitable. $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.'); - $name = plugin_manager::instance()->plugin_name('editor_tinymce'); + $name = core_plugin_manager::instance()->plugin_name('editor_tinymce'); $this->assertSame(get_string('pluginname', 'editor_tinymce'), $name); } public function test_plugintype_name() { - $name = plugin_manager::instance()->plugintype_name('editor'); + $name = core_plugin_manager::instance()->plugintype_name('editor'); $this->assertSame(get_string('type_editor', 'core_plugin'), $name); } public function test_plugintype_name_plural() { - $name = plugin_manager::instance()->plugintype_name_plural('editor'); + $name = core_plugin_manager::instance()->plugintype_name_plural('editor'); $this->assertSame(get_string('type_editor_plural', 'core_plugin'), $name); } @@ -168,8 +164,8 @@ class core_plugin_manager_testcase extends advanced_testcase { // Any standard plugin is suitable. $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.'); - $info = plugin_manager::instance()->get_plugin_info('editor_tinymce'); - $this->assertInstanceOf('plugininfo_editor', $info); + $info = core_plugin_manager::instance()->get_plugin_info('editor_tinymce'); + $this->assertInstanceOf('\core\plugininfo\editor', $info); } public function test_can_uninstall_plugin() { @@ -179,8 +175,8 @@ class core_plugin_manager_testcase extends advanced_testcase { $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/assignmentupgrade", 'assign upgrade tool is not present'); $this->assertFileExists("$CFG->dirroot/mod/assign", 'assign module is not present'); - $this->assertFalse(plugin_manager::instance()->can_uninstall_plugin('mod_assign')); - $this->assertTrue(plugin_manager::instance()->can_uninstall_plugin('tool_assignmentupgrade')); + $this->assertFalse(core_plugin_manager::instance()->can_uninstall_plugin('mod_assign')); + $this->assertTrue(core_plugin_manager::instance()->can_uninstall_plugin('tool_assignmentupgrade')); } public function test_plugin_states() { @@ -207,24 +203,24 @@ class core_plugin_manager_testcase extends advanced_testcase { // Deleted present. set_config('version', 2013091300, 'enrol_authorize'); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); - $plugininfos = plugin_manager::instance()->get_plugins(); + $plugininfos = core_plugin_manager::instance()->get_plugins(); foreach ($plugininfos as $type => $infos) { foreach ($infos as $name => $info) { /** @var plugininfo_base $info */ if ($info->component === 'mod_assign') { - $this->assertSame(plugin_manager::PLUGIN_STATUS_UPGRADE, $info->get_status(), 'Invalid '.$info->component.' state'); + $this->assertSame(core_plugin_manager::PLUGIN_STATUS_UPGRADE, $info->get_status(), 'Invalid '.$info->component.' state'); } else if ($info->component === 'mod_forum') { - $this->assertSame(plugin_manager::PLUGIN_STATUS_DOWNGRADE, $info->get_status(), 'Invalid '.$info->component.' state'); + $this->assertSame(core_plugin_manager::PLUGIN_STATUS_DOWNGRADE, $info->get_status(), 'Invalid '.$info->component.' state'); } else if ($info->component === 'tool_phpunit') { - $this->assertSame(plugin_manager::PLUGIN_STATUS_NEW, $info->get_status(), 'Invalid '.$info->component.' state'); + $this->assertSame(core_plugin_manager::PLUGIN_STATUS_NEW, $info->get_status(), 'Invalid '.$info->component.' state'); } else if ($info->component === 'mod_xxxxxxx') { - $this->assertSame(plugin_manager::PLUGIN_STATUS_MISSING, $info->get_status(), 'Invalid '.$info->component.' state'); + $this->assertSame(core_plugin_manager::PLUGIN_STATUS_MISSING, $info->get_status(), 'Invalid '.$info->component.' state'); } else if ($info->component === 'enrol_authorize') { - $this->assertSame(plugin_manager::PLUGIN_STATUS_DELETE, $info->get_status(), 'Invalid '.$info->component.' state'); + $this->assertSame(core_plugin_manager::PLUGIN_STATUS_DELETE, $info->get_status(), 'Invalid '.$info->component.' state'); } else { - $this->assertSame(plugin_manager::PLUGIN_STATUS_UPTODATE, $info->get_status(), 'Invalid '.$info->component.' state'); + $this->assertSame(core_plugin_manager::PLUGIN_STATUS_UPTODATE, $info->get_status(), 'Invalid '.$info->component.' state'); } } } diff --git a/lib/tests/available_update_checker_test.php b/lib/tests/update_checker_test.php similarity index 97% rename from lib/tests/available_update_checker_test.php rename to lib/tests/update_checker_test.php index b1d9f27bb29..95877b7e5a2 100644 --- a/lib/tests/available_update_checker_test.php +++ b/lib/tests/update_checker_test.php @@ -26,18 +26,17 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; -require_once($CFG->libdir.'/pluginlib.php'); -require_once(__DIR__.'/available_update_deployer_test.php'); +require_once(__DIR__.'/update_deployer_test.php'); /** * Tests of the basic API of the available update checker. */ -class core_available_update_checker_testcase extends advanced_testcase { +class core_update_checker_testcase extends advanced_testcase { public function test_core_available_update() { $provider = testable_available_update_checker::instance(); - $this->assertInstanceOf('available_update_checker', $provider); + $this->assertInstanceOf('\core\update\checker', $provider); $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array()); $updates = $provider->get_update_info('core'); @@ -261,7 +260,7 @@ class core_available_update_checker_testcase extends advanced_testcase { $broken = array( 'status' => 'ERROR' // No 'updates' key here. ); - $this->setExpectedException('available_update_checker_exception'); + $this->setExpectedException('\core\update\checker_exception'); $cmp = $provider->compare_responses($broken, $broken); } diff --git a/lib/tests/available_update_deployer_test.php b/lib/tests/update_deployer_test.php similarity index 91% rename from lib/tests/available_update_deployer_test.php rename to lib/tests/update_deployer_test.php index 23e75c52d8c..6b8f977b438 100644 --- a/lib/tests/available_update_deployer_test.php +++ b/lib/tests/update_deployer_test.php @@ -25,14 +25,11 @@ defined('MOODLE_INTERNAL') || die(); -global $CFG; -require_once($CFG->libdir.'/pluginlib.php'); - /** - * Test cases for {@link available_update_deployer} class. + * Test cases for {@link \core\update\deployer} class. */ -class core_available_update_deployer_testcase extends advanced_testcase { +class core_update_deployer_testcase extends advanced_testcase { public function test_magic_setters() { $deployer = testable_available_update_deployer::instance(); @@ -58,9 +55,9 @@ class core_available_update_deployer_testcase extends advanced_testcase { /** - * Modified version of {@link available_update_checker} suitable for testing. + * Modified version of {@link \core\update\checker} suitable for testing. */ -class testable_available_update_checker extends available_update_checker { +class testable_available_update_checker extends \core\update\checker { /** @var replaces the default DB table storage for the fetched response */ protected $fakeresponsestorage; @@ -197,13 +194,13 @@ class testable_available_update_checker extends available_update_checker { /** - * Exception used to detect {@link available_update_checker::cron_execute()} calls. + * Exception used to detect {@link \core\update\checker::cron_execute()} calls. */ class testable_available_update_checker_cron_executed extends Exception { } /** - * Modified {@link available_update_deployer} suitable for testing purposes. + * Modified {@link \core\update\deployer} suitable for testing purposes. */ -class testable_available_update_deployer extends available_update_deployer { +class testable_available_update_deployer extends \core\update\deployer { } diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 4643fc1ca43..8d5d9357853 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -36,7 +36,7 @@ information provided here is intended especially for developers. Use core_user::get_noreply_user() and core_user::get_support_user() to get noreply and support user's respectively. Real users can be used as noreply/support users by setting $CFG->noreplyuserid and $CFG->supportuserid * New function readfile_allow_large() in filelib.php for use when very large files may need sending to user. -* Use plugin_manager::reset_caches() when changing visibility of plugins. +* Use core_plugin_manager::reset_caches() when changing visibility of plugins. * Implement new method get_enabled_plugins() method in subplugin info classes. * Each plugin should include version information in version.php. * Module and block tables do not contain version column any more, use get_config('xx_yy', 'version') instead. @@ -221,9 +221,9 @@ Event triggering and event handlers: * Additional (optional) param $onlyactive has been added to get_enrolled_users, count_enrolled_users functions to get information for only active (excluding suspended enrolments) users. Included two helper functions extract_suspended_users, get_suspended_userids to extract suspended user information. -* The plugin_manager class now provides two new helper methods for getting information +* The core_plugin_manager class now provides two new helper methods for getting information about known plugins: get_plugins_of_type() and get_subplugins_of_plugin(). -* The get_uninstall_url() method of all subclasses of plugininfo_base class is now expected +* The get_uninstall_url() method of all subclasses of \core\plugininfo\base class is now expected to always return moodle_url. Subclasses can use the new method is_uninstall_allowed() to control the availability of the 'Uninstall' link at the Plugins overview page (previously they would do it by get_uninstall_url() returning null). By default, URL to a new general plugin diff --git a/message/lib.php b/message/lib.php index c3fa84a1ebb..b69565e06b9 100644 --- a/message/lib.php +++ b/message/lib.php @@ -2350,8 +2350,8 @@ function get_message_processors($ready = false, $reset = false) { */ function get_message_providers() { global $CFG, $DB; - require_once($CFG->libdir . '/pluginlib.php'); - $pluginman = plugin_manager::instance(); + + $pluginman = core_plugin_manager::instance(); $providers = $DB->get_records('message_providers', null, 'name'); @@ -2359,7 +2359,7 @@ function get_message_providers() { foreach ($providers as $providerid => $provider) { $plugin = $pluginman->get_plugin_info($provider->component); if ($plugin) { - if ($plugin->get_status() === plugin_manager::PLUGIN_STATUS_MISSING) { + if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) { unset($providers[$providerid]); // Plugins does not exist continue; } diff --git a/message/renderer.php b/message/renderer.php index 4a7be2e260d..82e8e68f0a9 100644 --- a/message/renderer.php +++ b/message/renderer.php @@ -46,7 +46,7 @@ class core_message_renderer extends plugin_renderer_base { global $CFG; // Display the current workflows $table = new html_table(); - $table->attributes['class'] = 'generaltable'; + $table->attributes['class'] = 'admintable generaltable'; $table->data = array(); $table->head = array( get_string('name'), @@ -81,7 +81,7 @@ class core_message_renderer extends plugin_renderer_base { ) )); } else { - $name->attributes['class'] = 'dimmed_text'; + $row->attributes['class'] = 'dimmed_text'; $url = new moodle_url('/admin/message.php', array('enable' => $processor->id, 'sesskey' => sesskey())); $enable->text = html_writer::link($url, html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/show'), diff --git a/mod/assign/adminlib.php b/mod/assign/adminlib.php index 7fffceef4c6..9461138e21e 100644 --- a/mod/assign/adminlib.php +++ b/mod/assign/adminlib.php @@ -139,16 +139,26 @@ class assign_plugin_manager { * Util function for writing an action icon link * * @param string $action URL parameter to include in the link - * @param string $plugintype URL parameter to include in the link + * @param string $plugin URL parameter to include in the link * @param string $icon The key to the icon to use (e.g. 't/up') * @param string $alt The string description of the link used as the title and alt text * @return string The icon/link */ - private function format_icon_link($action, $plugintype, $icon, $alt) { + private function format_icon_link($action, $plugin, $icon, $alt) { global $OUTPUT; - return $OUTPUT->action_icon(new moodle_url($this->pageurl, - array('action' => $action, 'plugin'=> $plugintype, 'sesskey' => sesskey())), + $url = $this->pageurl; + + if ($action === 'delete') { + $url = core_plugin_manager::instance()->get_uninstall_url($this->subtype.'_'.$plugin, 'manage'); + if (!$url) { + return ' '; + } + return html_writer::link($url, get_string('uninstallplugin', 'core_admin')); + } + + return $OUTPUT->action_icon(new moodle_url($url, + array('action' => $action, 'plugin'=> $plugin, 'sesskey' => sesskey())), new pix_icon($icon, $alt, 'moodle', array('title' => $alt)), null, array('title' => $alt)) . ' '; } @@ -167,12 +177,12 @@ class assign_plugin_manager { $table = new flexible_table($this->subtype . 'pluginsadminttable'); $table->define_baseurl($this->pageurl); $table->define_columns(array('pluginname', 'version', 'hideshow', 'order', - 'delete', 'settings')); + 'settings', 'uninstall')); $table->define_headers(array(get_string($this->subtype . 'pluginname', 'assign'), get_string('version'), get_string('hideshow', 'assign'), - get_string('order'), get_string('delete'), get_string('settings'))); + get_string('order'), get_string('settings'), get_string('uninstallplugin', 'core_admin'))); $table->set_attribute('id', $this->subtype . 'plugins'); - $table->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide'); + $table->set_attribute('class', 'admintable generaltable'); $table->setup(); $plugins = $this->get_sorted_plugins_list(); @@ -180,6 +190,7 @@ class assign_plugin_manager { foreach ($plugins as $idx => $plugin) { $row = array(); + $class = ''; $row[] = get_string('pluginname', $this->subtype . '_' . $plugin); $row[] = get_config($this->subtype . '_' . $plugin, 'version'); @@ -190,6 +201,7 @@ class assign_plugin_manager { $row[] = $this->format_icon_link('hide', $plugin, 't/hide', get_string('disable')); } else { $row[] = $this->format_icon_link('show', $plugin, 't/show', get_string('enable')); + $class = 'dimmed_text'; } $movelinks = ''; @@ -203,11 +215,6 @@ class assign_plugin_manager { } $row[] = $movelinks; - if ($row[1] != '') { - $row[] = $this->format_icon_link('delete', $plugin, 't/delete', get_string('delete')); - } else { - $row[] = ' '; - } $exists = file_exists($CFG->dirroot . '/mod/assign/' . $shortsubtype . '/' . $plugin . '/settings.php'); if ($row[1] != '' && $exists) { $row[] = html_writer::link(new moodle_url('/admin/settings.php', @@ -215,7 +222,10 @@ class assign_plugin_manager { } else { $row[] = ' '; } - $table->add_data($row); + + $row[] = $this->format_icon_link('delete', $plugin, 't/delete', get_string('uninstallplugin', 'core_admin')); + + $table->add_data($row, $class); } $table->finish_output(); @@ -257,88 +267,6 @@ class assign_plugin_manager { require_capability('moodle/site:config', $systemcontext); } - /** - * Delete the database and files associated with this plugin. - * - * @param string $plugin - The type of the plugin to delete - * @return string the name of the next page to display - */ - public function delete_plugin($plugin) { - global $CFG, $DB, $OUTPUT; - $confirm = optional_param('confirm', null, PARAM_BOOL); - - if ($confirm) { - // Delete any configuration records. - if (!unset_all_config_for_plugin($this->subtype . '_' . $plugin)) { - $this->error = $OUTPUT->notification(get_string('errordeletingconfig', 'admin', $this->subtype . '_' . $plugin)); - } - - // Should be covered by the previous function - but just in case. - unset_config('disabled', $this->subtype . '_' . $plugin); - unset_config('sortorder', $this->subtype . '_' . $plugin); - - // Delete the plugin specific config settings. - $DB->delete_records('assign_plugin_config', array('plugin'=>$plugin, 'subtype'=>$this->subtype)); - - // Then the tables themselves. - $shortsubtype = substr($this->subtype, strlen('assign')); - $installxml = $CFG->dirroot . '/mod/assign/' . $shortsubtype . '/' . $plugin . '/db/install.xml'; - drop_plugin_tables($this->subtype . '_' . $plugin, - $installxml, - false); - - // Remove event handlers and dequeue pending events. - events_uninstall($this->subtype . '_' . $plugin); - - // The page to display. - return 'plugindeleted'; - } else { - // The page to display. - return 'confirmdelete'; - } - - } - - /** - * Show the page that gives the details of the plugin that was just deleted. - * - * @param string $plugin - The plugin that was just deleted - * @return None - */ - private function view_plugin_deleted($plugin) { - global $OUTPUT; - $this->view_header(); - $pluginname = get_string('pluginname', $this->subtype . '_' . $plugin); - echo $OUTPUT->heading(get_string('deletingplugin', 'assign', $pluginname)); - echo $this->error; - $messageparams = array('name'=>$pluginname, - 'directory'=>('/mod/assign/' . $this->subtype . '/'.$plugin)); - echo $OUTPUT->notification(get_string('plugindeletefiles', 'moodle', $messageparams)); - echo $OUTPUT->continue_button($this->pageurl); - $this->view_footer(); - } - - /** - * Show the page that asks the user to confirm they want to delete a plugin. - * - * @param string $plugin - The plugin that will be deleted - * @return None - */ - private function view_confirm_delete($plugin) { - global $OUTPUT; - $this->view_header(); - $pluginname = get_string('pluginname', $this->subtype . '_' . $plugin); - echo $OUTPUT->heading(get_string('deletepluginareyousure', 'assign', $pluginname)); - $urlparams = array('action' => 'delete', 'plugin'=>$plugin, 'confirm' => 1); - $confirmurl = new moodle_url($this->pageurl, $urlparams); - echo $OUTPUT->confirm(get_string('deletepluginareyousuremessage', 'assign', $pluginname), - $confirmurl, - $this->pageurl); - $this->view_footer(); - } - - - /** * Hide this plugin. * @@ -347,6 +275,7 @@ class assign_plugin_manager { */ public function hide_plugin($plugin) { set_config('disabled', 1, $this->subtype . '_' . $plugin); + core_plugin_manager::reset_caches(); return 'view'; } @@ -405,6 +334,7 @@ class assign_plugin_manager { */ public function show_plugin($plugin) { set_config('disabled', 0, $this->subtype . '_' . $plugin); + core_plugin_manager::reset_caches(); return 'view'; } @@ -424,9 +354,7 @@ class assign_plugin_manager { $this->check_permissions(); // Process. - if ($action == 'delete' && $plugin != null) { - $action = $this->delete_plugin($plugin); - } else if ($action == 'hide' && $plugin != null) { + if ($action == 'hide' && $plugin != null) { $action = $this->hide_plugin($plugin); } else if ($action == 'show' && $plugin != null) { $action = $this->show_plugin($plugin); @@ -437,51 +365,8 @@ class assign_plugin_manager { } // View. - if ($action == 'confirmdelete' && $plugin != null) { - $this->view_confirm_delete($plugin); - } else if ($action == 'plugindeleted' && $plugin != null) { - $this->view_plugin_deleted($plugin); - } else if ($action == 'view') { + if ($action == 'view') { $this->view_plugins_table(); } } - - /** - * This function adds plugin pages to the navigation menu. - * - * @static - * @param string $subtype - The type of plugin (submission or feedback) - * @param part_of_admin_tree $admin - The handle to the admin menu - * @param admin_settingpage $settings - The handle to current node in the navigation tree - * @param stdClass|plugininfo_mod $module - The handle to the current module - * @return None - */ - public static function add_admin_assign_plugin_settings($subtype, - part_of_admin_tree $admin, - admin_settingpage $settings, - $module) { - global $CFG; - - $plugins = core_component::get_plugin_list_with_file($subtype, 'settings.php', false); - $pluginsbyname = array(); - foreach ($plugins as $plugin => $plugindir) { - $pluginname = get_string('pluginname', $subtype . '_'.$plugin); - $pluginsbyname[$pluginname] = $plugin; - } - ksort($pluginsbyname); - - foreach ($pluginsbyname as $pluginname => $plugin) { - $settings = new admin_settingpage($subtype . '_' . $plugin, - $pluginname, - 'moodle/site:config', - $module->is_enabled() === false); - if ($admin->fulltree) { - $shortsubtype = substr($subtype, strlen('assign')); - include($CFG->dirroot . "/mod/assign/$shortsubtype/$plugin/settings.php"); - } - - $admin->add($subtype . 'plugins', $settings); - } - - } } diff --git a/mod/assign/classes/plugininfo/assignfeedback.php b/mod/assign/classes/plugininfo/assignfeedback.php new file mode 100644 index 00000000000..61923cd7e14 --- /dev/null +++ b/mod/assign/classes/plugininfo/assignfeedback.php @@ -0,0 +1,128 @@ +. + +/** + * Assign feedback subplugin info class. + * + * @package mod_assign + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\plugininfo; + +use core\plugininfo\base, core_plugin_manager, moodle_url; + +defined('MOODLE_INTERNAL') || die(); + + +class assignfeedback extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + + $plugins = core_plugin_manager::instance()->get_installed_plugins('assignfeedback'); + if (!$plugins) { + return array(); + } + $installed = array(); + foreach ($plugins as $plugin => $version) { + $installed[] = 'assignfeedback_'.$plugin; + } + + list($installed, $params) = $DB->get_in_or_equal($installed, SQL_PARAMS_NAMED); + $disabled = $DB->get_records_select('config_plugins', "plugin $installed AND name = 'disabled'", $params, 'plugin ASC'); + foreach ($disabled as $conf) { + if (empty($conf->value)) { + continue; + } + list($type, $name) = explode('_', $conf->plugin, 2); + unset($plugins[$name]); + } + + $enabled = array(); + foreach ($plugins as $plugin => $version) { + $enabled[$plugin] = $plugin; + } + + return $enabled; + } + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/mod/assign/adminmanageplugins.php', array('subtype'=>'assignfeedback')); + } + + /** + * Pre-uninstall hook. + * @private + */ + public function uninstall_cleanup() { + global $DB; + + $DB->delete_records('assign_plugin_config', array('plugin'=>$this->name, 'subtype'=>'assignfeedback')); + + parent::uninstall_cleanup(); + } + + public function get_settings_section_name() { + return $this->type . '_' . $this->name; + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + + if ($adminroot->fulltree) { + $shortsubtype = substr($this->type, strlen('assign')); + include($this->full_path('settings.php')); + } + + $adminroot->add($this->type . 'plugins', $settings); + } +} diff --git a/mod/assign/classes/plugininfo/assignsubmission.php b/mod/assign/classes/plugininfo/assignsubmission.php new file mode 100644 index 00000000000..a6370a42ce0 --- /dev/null +++ b/mod/assign/classes/plugininfo/assignsubmission.php @@ -0,0 +1,128 @@ +. + +/** + * Assign submission subplugin info class. + * + * @package mod_assign + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\plugininfo; + +use core\plugininfo\base, core_plugin_manager, moodle_url; + +defined('MOODLE_INTERNAL') || die(); + + +class assignsubmission extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $DB; + + $plugins = core_plugin_manager::instance()->get_installed_plugins('assignsubmission'); + if (!$plugins) { + return array(); + } + $installed = array(); + foreach ($plugins as $plugin => $version) { + $installed[] = 'assignsubmission_'.$plugin; + } + + list($installed, $params) = $DB->get_in_or_equal($installed, SQL_PARAMS_NAMED); + $disabled = $DB->get_records_select('config_plugins', "plugin $installed AND name = 'disabled'", $params, 'plugin ASC'); + foreach ($disabled as $conf) { + if (empty($conf->value)) { + continue; + } + list($type, $name) = explode('_', $conf->plugin, 2); + unset($plugins[$name]); + } + + $enabled = array(); + foreach ($plugins as $plugin => $version) { + $enabled[$plugin] = $plugin; + } + + return $enabled; + } + + public function is_uninstall_allowed() { + return true; + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/mod/assign/adminmanageplugins.php', array('subtype'=>'assignsubmission')); + } + + /** + * Pre-uninstall hook. + * @private + */ + public function uninstall_cleanup() { + global $DB; + + $DB->delete_records('assign_plugin_config', array('plugin'=>$this->name, 'subtype'=>'assignsubmission')); + + parent::uninstall_cleanup(); + } + + public function get_settings_section_name() { + return $this->type . '_' . $this->name; + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + + if ($adminroot->fulltree) { + $shortsubtype = substr($this->type, strlen('assign')); + include($this->full_path('settings.php')); + } + + $adminroot->add($this->type . 'plugins', $settings); + } +} diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php index 02b486668d9..fe845a5d2b9 100644 --- a/mod/assign/lang/en/assign.php +++ b/mod/assign/lang/en/assign.php @@ -122,9 +122,6 @@ $string['defaultsettings'] = 'Default assignment settings'; $string['defaultsettings_help'] = 'These settings define the defaults for all new assignments.'; $string['defaultteam'] = 'Default group'; $string['deleteallsubmissions'] = 'Delete all submissions'; -$string['deletepluginareyousure'] = 'Delete assignment plugin {$a}: are you sure?'; -$string['deletepluginareyousuremessage'] = 'You are about to completely delete the assignment plugin {$a}. This will completely delete everything in the database associated with this assignment plugin. Are you SURE you want to continue?'; -$string['deletingplugin'] = 'Deleting plugin {$a}.'; $string['description'] = 'Description'; $string['downloadall'] = 'Download all submissions'; $string['download all submissions'] = 'Download all submissions in a zip file.'; diff --git a/mod/assign/settings.php b/mod/assign/settings.php index 038d31e4706..04575f40bec 100644 --- a/mod/assign/settings.php +++ b/mod/assign/settings.php @@ -26,17 +26,9 @@ defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot . '/mod/assign/adminlib.php'); -$ADMIN->add('modules', new admin_category('assignmentplugins', - new lang_string('assignmentplugins', 'assign'), $module->is_enabled() === false)); -$ADMIN->add('assignmentplugins', new admin_category('assignsubmissionplugins', - new lang_string('submissionplugins', 'assign'), $module->is_enabled() === false)); -$ADMIN->add('assignsubmissionplugins', new assign_admin_page_manage_assign_plugins('assignsubmission')); -$ADMIN->add('assignmentplugins', new admin_category('assignfeedbackplugins', - new lang_string('feedbackplugins', 'assign'), $module->is_enabled() === false)); -$ADMIN->add('assignfeedbackplugins', new assign_admin_page_manage_assign_plugins('assignfeedback')); +$ADMIN->add('modsettings', new admin_category('modassignfolder', new lang_string('pluginname', 'mod_assign'), $module->is_enabled() === false)); -assign_plugin_manager::add_admin_assign_plugin_settings('assignsubmission', $ADMIN, $settings, $module); -assign_plugin_manager::add_admin_assign_plugin_settings('assignfeedback', $ADMIN, $settings, $module); +$settings = new admin_settingpage($section, get_string('settings', 'mod_assign'), 'moodle/site:config', $module->is_enabled() === false); if ($ADMIN->fulltree) { $menu = array(); @@ -251,3 +243,24 @@ if ($ADMIN->fulltree) { $setting->set_locked_flag_options(admin_setting_flag::ENABLED, false); $settings->add($setting); } + +$ADMIN->add('modassignfolder', $settings); +// Tell core we already added the settings structure. +$settings = null; + +$ADMIN->add('modassignfolder', new admin_category('assignsubmissionplugins', + new lang_string('submissionplugins', 'assign'), !$module->is_enabled())); +$ADMIN->add('assignsubmissionplugins', new assign_admin_page_manage_assign_plugins('assignsubmission')); +$ADMIN->add('modassignfolder', new admin_category('assignfeedbackplugins', + new lang_string('feedbackplugins', 'assign'), !$module->is_enabled())); +$ADMIN->add('assignfeedbackplugins', new assign_admin_page_manage_assign_plugins('assignfeedback')); + +foreach (core_plugin_manager::instance()->get_plugins_of_type('assignsubmission') as $plugin) { + /** @var \mod_assign\plugininfo\assignsubmission $plugin */ + $plugin->load_settings($ADMIN, 'assignsubmissionplugins', $hassiteconfig); +} + +foreach (core_plugin_manager::instance()->get_plugins_of_type('assignfeedback') as $plugin) { + /** @var \mod_assign\plugininfo\assignfeedback $plugin */ + $plugin->load_settings($ADMIN, 'assignfeedbackplugins', $hassiteconfig); +} diff --git a/mod/assignment/classes/plugininfo/assignment.php b/mod/assignment/classes/plugininfo/assignment.php new file mode 100644 index 00000000000..e76cc0add36 --- /dev/null +++ b/mod/assignment/classes/plugininfo/assignment.php @@ -0,0 +1,35 @@ +. + +/** + * Subplugin info class. + * + * @package mod_assignment + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assignment\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class assignment extends base { + public function is_uninstall_allowed() { + return false; + } +} diff --git a/mod/book/classes/plugininfo/booktool.php b/mod/book/classes/plugininfo/booktool.php new file mode 100644 index 00000000000..e137f37a9a8 --- /dev/null +++ b/mod/book/classes/plugininfo/booktool.php @@ -0,0 +1,35 @@ +. + +/** + * Subplugin info class. + * + * @package mod_book + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_book\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class booktool extends base { + public function is_uninstall_allowed() { + return true; + } +} diff --git a/mod/data/classes/plugininfo/datafield.php b/mod/data/classes/plugininfo/datafield.php new file mode 100644 index 00000000000..1bf6fb685df --- /dev/null +++ b/mod/data/classes/plugininfo/datafield.php @@ -0,0 +1,36 @@ +. + +/** + * Subplugin info class. + * + * @package mod_data + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_data\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class datafield extends base { + public function is_uninstall_allowed() { + global $DB; + return !$DB->record_exists('data_fields', array('type'=>$this->name)); + } +} diff --git a/mod/data/classes/plugininfo/datapreset.php b/mod/data/classes/plugininfo/datapreset.php new file mode 100644 index 00000000000..37804e75fec --- /dev/null +++ b/mod/data/classes/plugininfo/datapreset.php @@ -0,0 +1,35 @@ +. + +/** + * Subplugin info class. + * + * @package mod_data + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_data\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class datapreset extends base { + public function is_uninstall_allowed() { + return true; + } +} diff --git a/mod/quiz/adminlib.php b/mod/quiz/classes/plugininfo/quiz.php similarity index 85% rename from mod/quiz/adminlib.php rename to mod/quiz/classes/plugininfo/quiz.php index 16ff793bb4d..e52dfafbca6 100644 --- a/mod/quiz/adminlib.php +++ b/mod/quiz/classes/plugininfo/quiz.php @@ -15,24 +15,20 @@ // along with Moodle. If not, see . /** - * Quiz admin stuff. + * Subplugin info class. * * @package mod_quiz * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace mod_quiz\plugininfo; + +use core\plugininfo\base; defined('MOODLE_INTERNAL') || die(); -/** - * Quiz admin stuff. - * - * @package mod_quiz - * @copyright 2013 Petr Skoda {@link http://skodak.org} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class plugininfo_quiz extends plugininfo_base { +class quiz extends base { public function is_uninstall_allowed() { return true; } diff --git a/mod/quiz/classes/plugininfo/quizaccess.php b/mod/quiz/classes/plugininfo/quizaccess.php new file mode 100644 index 00000000000..3a7758c9a37 --- /dev/null +++ b/mod/quiz/classes/plugininfo/quizaccess.php @@ -0,0 +1,35 @@ +. + +/** + * Subplugin info class. + * + * @package mod_quiz + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_quiz\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class quizaccess extends base { + public function is_uninstall_allowed() { + return false; + } +} diff --git a/mod/scorm/classes/plugininfo/scormreport.php b/mod/scorm/classes/plugininfo/scormreport.php new file mode 100644 index 00000000000..671ffbe7373 --- /dev/null +++ b/mod/scorm/classes/plugininfo/scormreport.php @@ -0,0 +1,35 @@ +. + +/** + * Subplugin info class. + * + * @package mod_scorm + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_scorm\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class scormreport extends base { + public function is_uninstall_allowed() { + return true; + } +} diff --git a/mod/workshop/classes/plugininfo/workshopallocation.php b/mod/workshop/classes/plugininfo/workshopallocation.php new file mode 100644 index 00000000000..87524db3a19 --- /dev/null +++ b/mod/workshop/classes/plugininfo/workshopallocation.php @@ -0,0 +1,38 @@ +. + +/** + * Subplugin info class. + * + * @package mod_workshop + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_workshop\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class workshopallocation extends base { + public function is_uninstall_allowed() { + if ($this->is_standard()) { + return false; + } + return true; + } +} diff --git a/mod/workshop/classes/plugininfo/workshopeval.php b/mod/workshop/classes/plugininfo/workshopeval.php new file mode 100644 index 00000000000..5c44afba920 --- /dev/null +++ b/mod/workshop/classes/plugininfo/workshopeval.php @@ -0,0 +1,38 @@ +. + +/** + * Subplugin info class. + * + * @package mod_workshop + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_workshop\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class workshopeval extends base { + public function is_uninstall_allowed() { + if ($this->is_standard()) { + return false; + } + return true; + } +} diff --git a/mod/workshop/classes/plugininfo/workshopform.php b/mod/workshop/classes/plugininfo/workshopform.php new file mode 100644 index 00000000000..b7bcf89f53f --- /dev/null +++ b/mod/workshop/classes/plugininfo/workshopform.php @@ -0,0 +1,38 @@ +. + +/** + * Subplugin info class. + * + * @package mod_workshop + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_workshop\plugininfo; + +use core\plugininfo\base; + +defined('MOODLE_INTERNAL') || die(); + + +class workshopform extends base { + public function is_uninstall_allowed() { + if ($this->is_standard()) { + return false; + } + return true; + } +} diff --git a/user/portfolio.php b/user/portfolio.php index e8b0b391371..6d09c75e600 100644 --- a/user/portfolio.php +++ b/user/portfolio.php @@ -29,7 +29,6 @@ if (empty($CFG->enableportfolios)) { print_error('disabled', 'portfolio'); } -require_once($CFG->libdir . '/pluginlib.php'); require_once($CFG->libdir . '/portfoliolib.php'); require_once($CFG->libdir . '/portfolio/forms.php'); @@ -82,7 +81,7 @@ if (!empty($config)) { $success = $instance->set_user_config($fromform, $USER->id); //$success = $success && $instance->save(); if ($success) { - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); redirect($baseurl, get_string('instancesaved', 'portfolio'), 3); } else { print_error('instancenotsaved', 'portfolio', $baseurl); @@ -99,7 +98,7 @@ if (!empty($config)) { } else if (!empty($hide)) { $instance = portfolio_instance($hide); $instance->set_user_config(array('visible' => !$instance->get_user_config('visible', $USER->id)), $USER->id); - plugin_manager::reset_caches(); + core_plugin_manager::reset_caches(); } if ($display) {