"sub": false,
"supernew": false,
"maxerr": 500,
- "maxlen": 150,
+ "maxlen": 180,
"passfail": false,
"latedef": true
}
define('IGNORE_COMPONENT_CACHE', true);
// Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.3.3") < 0) {
+if (version_compare(phpversion(), "5.4.4") < 0) {
$phpversion = phpversion();
// do NOT localise - lang strings would not work here and we CAN NOT move it after installib
- fwrite(STDERR, "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).\n");
+ fwrite(STDERR, "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n");
fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
exit(1);
}
";
// Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.3.3") < 0) {
+if (version_compare(phpversion(), "5.4.4") < 0) {
$phpversion = phpversion();
// do NOT localise - lang strings would not work here and we CAN NOT move it after installib
- fwrite(STDERR, "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).\n");
+ fwrite(STDERR, "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n");
fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
exit(1);
}
echo $OUTPUT->header();
// This may take a long time.
- set_time_limit(0);
+ core_php_time_limit::raise();
// Disable plugin to prevent concurrent cron execution.
unset($enabled[$enrol]);
</PHP_SETTING>
</PHP_SETTINGS>
</MOODLE>
+ <MOODLE version="2.7" requires="2.2">
+ <UNICODE level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unicoderequired" />
+ </FEEDBACK>
+ </UNICODE>
+ <DATABASE level="required">
+ <VENDOR name="mariadb" version="5.5.31" />
+ <VENDOR name="mysql" version="5.5.31" />
+ <VENDOR name="postgres" version="9.1" />
+ <VENDOR name="mssql" version="10.0" />
+ <VENDOR name="oracle" version="10.2" />
+ </DATABASE>
+ <PHP version="5.4.4" level="required">
+ </PHP>
+ <PCREUNICODE level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="pcreunicodewarning" />
+ </FEEDBACK>
+ </PCREUNICODE>
+ <PHP_EXTENSIONS>
+ <PHP_EXTENSION name="iconv" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="iconvrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="mbstring" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="mbstringrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="curl" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="curlrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="openssl" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="opensslrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="tokenizer" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="tokenizerrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="xmlrpc" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="xmlrpcrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="soap" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="soaprecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="ctype" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="ctyperequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="zip" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="ziprequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="zlib" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="gd" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="gdrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="simplexml" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="simplexmlrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="spl" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="splrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="pcre" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="dom" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="xml" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="intl" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="intlrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="json" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="hash" level="required"/>
+ </PHP_EXTENSIONS>
+ <PHP_SETTINGS>
+ <PHP_SETTING name="memory_limit" value="96M" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="settingmemorylimit" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ <PHP_SETTING name="file_uploads" value="1" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="settingfileuploads" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ <PHP_SETTING name="opcache.enable" value="1" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="opcacherecommended" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ </PHP_SETTINGS>
+ </MOODLE>
</COMPATIBILITY_MATRIX>
}
// Check that PHP is of a sufficient version as soon as possible
-if (version_compare(phpversion(), '5.3.3') < 0) {
+if (version_compare(phpversion(), '5.4.4') < 0) {
$phpversion = phpversion();
// do NOT localise - lang strings would not work here and we CAN NOT move it to later place
- echo "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).<br />";
+ echo "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).<br />";
echo "Please upgrade your server software or install older Moodle version.";
die();
}
define('NO_OUTPUT_BUFFERING', true);
-if (empty($_GET['cache']) and empty($_POST['cache']) and empty($_GET['sesskey']) and empty($_POST['sesskey'])) {
+if ((isset($_GET['cache']) and $_GET['cache'] === '0')
+ or (isset($_POST['cache']) and $_POST['cache'] === '0')
+ or (!isset($_POST['cache']) and !isset($_GET['cache']) and empty($_GET['sesskey']) and empty($_POST['sesskey']))) {
// Prevent caching at all cost when visiting this page directly,
// we redirect to self once we known no upgrades are necessary.
// Note: $_GET and $_POST are used here intentionally because our param cleaning is not loaded yet.
// Set up PAGE.
$url = new moodle_url('/admin/index.php');
-if ($cache) {
- $url->param('cache', 1);
-}
+$url->param('cache', $cache);
$PAGE->set_url($url);
unset($url);
$PAGE->set_pagelayout('maintenance');
$PAGE->set_popup_notification_allowed(false);
+ /** @var core_admin_renderer $output */
+ $output = $PAGE->get_renderer('core', 'admin');
+
if (upgrade_stale_php_files_present()) {
$PAGE->set_title($stradministration);
$PAGE->set_cacheable(false);
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
echo $output->upgrade_stale_php_files_page();
die();
}
$PAGE->set_heading($strdatabasechecking);
$PAGE->set_cacheable(false);
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
echo $output->upgrade_confirm_page($a->newversion, $maturity, $testsite);
die();
$PAGE->set_heading($strcurrentrelease);
$PAGE->set_cacheable(false);
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
echo $output->upgrade_environment_page($release, $envstatus, $environment_results);
die();
$PAGE->set_heading($strplugincheck);
$PAGE->set_cacheable(false);
- $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1));
-
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
-
- // check plugin dependencies first
- $failed = array();
- if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
- echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
- die();
- }
- unset($failed);
+ $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
if ($fetchupdates) {
- // no sesskey support guaranteed here
- if (empty($CFG->disableupdatenotifications)) {
- \core\update\checker::instance()->fetch();
+ // No sesskey support guaranteed here, because sessions might not work yet.
+ $updateschecker = \core\update\checker::instance();
+ if ($updateschecker->enabled()) {
+ $updateschecker->fetch();
}
redirect($reloadurl);
}
$deploydata = $deployer->submitted_data();
if (!empty($deploydata)) {
+ // No sesskey support guaranteed here, because sessions might not work yet.
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
die();
}
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)));
+ new moodle_url('/admin/index.php', array('confirmupgrade'=>1, 'confirmrelease'=>1, 'confirmplugincheck'=>1, 'cache'=>0)));
die();
} else {
- // Launch main upgrade
+ // Always verify plugin dependencies!
+ $failed = array();
+ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+ $PAGE->set_pagelayout('maintenance');
+ $PAGE->set_popup_notification_allowed(false);
+ $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
+ echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
+ die();
+ }
+ unset($failed);
+
+ // Launch main upgrade.
upgrade_core($version, true);
}
} else if ($version < $CFG->version) {
if (!$cache and moodle_needs_upgrading()) {
if (!$PAGE->headerprinted) {
// means core upgrade or installation was not already done
+
+ /** @var core_admin_renderer $output */
+ $output = $PAGE->get_renderer('core', 'admin');
+
if (!$confirmplugins) {
$strplugincheck = get_string('plugincheck');
$PAGE->set_cacheable(false);
if ($fetchupdates) {
- // no sesskey support guaranteed here
- \core\update\checker::instance()->fetch();
+ require_sesskey();
+ $updateschecker = \core\update\checker::instance();
+ if ($updateschecker->enabled()) {
+ $updateschecker->fetch();
+ }
redirect($PAGE->url);
}
- $output = $PAGE->get_renderer('core', 'admin');
-
$deployer = \core\update\deployer::instance();
if ($deployer->enabled()) {
$deployer->initialize($PAGE->url, $PAGE->url);
$deploydata = $deployer->submitted_data();
if (!empty($deploydata)) {
+ require_sesskey();
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
die();
}
}
- // check plugin dependencies first
- $failed = array();
- 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!
+ // Show plugins info.
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)));
+ new moodle_url('/admin/index.php', array('confirmplugincheck'=>1, 'cache'=>0)));
+ die();
+ }
+
+ // Make sure plugin dependencies are always checked.
+ $failed = array();
+ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+ $PAGE->set_pagelayout('maintenance');
+ $PAGE->set_popup_notification_allowed(false);
+ $reloadurl = new moodle_url('/admin/index.php', array('cache' => 0));
+ echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
die();
}
+ unset($failed);
}
+
// install/upgrade all plugins and other parts
upgrade_noncore(true);
}
upgrade_finished('upgradesettings.php');
}
+if (has_capability('moodle/site:config', context_system::instance())) {
+ if ($fetchupdates) {
+ require_sesskey();
+ $updateschecker = \core\update\checker::instance();
+ if ($updateschecker->enabled()) {
+ $updateschecker->fetch();
+ }
+ redirect(new moodle_url('/admin/index.php', array('cache' => 0)));
+ }
+}
+
// Now we can be sure everything was upgraded and caches work fine,
// redirect if necessary to make sure caching is enabled.
if (!$cache) {
admin_externalpage_setup('adminnotifications');
-if ($fetchupdates) {
- require_sesskey();
- $updateschecker->fetch();
- redirect(new moodle_url('/admin/index.php'));
-}
-
$output = $PAGE->get_renderer('core', 'admin');
echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
$cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb,
admin_externalpage_setup('ssoaccesscontrol');
-echo $OUTPUT->header();
-
if (!extension_loaded('openssl')) {
print_error('requiresopenssl', 'mnet');
}
if (mnet_update_sso_access_control($idrec->username, $idrec->mnet_host_id, $accessctrl)) {
if ($accessctrl == 'allow') {
- redirect('access_control.php', get_string('ssl_acl_allow','mnet', array('uset'=>$idrec->username, 'host'=>$mnethosts[$idrec->mnet_host_id])));
- } elseif ($accessctrl == 'deny') {
- redirect('access_control.php', get_string('ssl_acl_deny','mnet', array('user'=>$idrec->username, 'host'=>$mnethosts[$idrec->mnet_host_id])));
+ redirect('access_control.php', get_string('ssl_acl_allow','mnet', array('user' => $idrec->username,
+ 'host' => $mnethosts[$idrec->mnet_host_id])));
+ } else if ($accessctrl == 'deny') {
+ redirect('access_control.php', get_string('ssl_acl_deny','mnet', array('user' => $idrec->username,
+ 'host' => $mnethosts[$idrec->mnet_host_id])));
}
}
break;
exit;
}
+echo $OUTPUT->header();
+
// Explain
echo $OUTPUT->box(get_string('ssoacldescr','mnet'));
// Are the needed bits enabled?
public function upgrade_confirm_page($strnewversion, $maturity, $testsite) {
$output = '';
- $continueurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1));
+ $continueurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'cache' => 0));
$continue = new single_button($continueurl, get_string('continue'), 'get');
$cancelurl = new moodle_url('/admin/index.php');
$output .= $this->environment_check_table($envstatus, $environment_results);
if (!$envstatus) {
- $output .= $this->upgrade_reload(new moodle_url('/admin/index.php'), array('confirmupgrade' => 1));
+ $output .= $this->upgrade_reload(new moodle_url('/admin/index.php'), array('confirmupgrade' => 1, 'cache' => 0));
} else {
$output .= $this->notification(get_string('environmentok', 'admin'), 'notifysuccess');
$output .= $this->box(get_string('langpackwillbeupdated', 'admin'), 'generalbox', 'notice');
}
- $output .= $this->continue_button(new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1)));
+ $output .= $this->continue_button(new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0)));
}
$output .= $this->footer();
}
$updateinfo .= $this->container_start('checkforupdates');
- $fetchurl = new moodle_url('/admin/index.php', array('fetchupdates' => 1, 'sesskey' => sesskey(), 'cache' => 1));
+ $fetchurl = new moodle_url('/admin/index.php', array('fetchupdates' => 1, 'sesskey' => sesskey(), 'cache' => 0));
$updateinfo .= $this->single_button($fetchurl, get_string('checkforupdates', 'core_plugin'));
if ($fetch) {
$updateinfo .= $this->container(get_string('checkforupdateslast', 'core_plugin',
$out .= $this->output->heading(get_string('nonehighlighted', 'core_plugin'));
if (empty($options['full'])) {
$out .= html_writer::link(new moodle_url('/admin/index.php',
- array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1)),
+ array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1, 'cache' => 0)),
get_string('nonehighlightedinfo', 'core_plugin'));
}
$out .= $this->output->container_end();
$out .= $this->output->heading(get_string('somehighlighted', 'core_plugin', $sumofhighlighted));
if (empty($options['full'])) {
$out .= html_writer::link(new moodle_url('/admin/index.php',
- array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1)),
+ array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1, 'cache' => 0)),
get_string('somehighlightedinfo', 'core_plugin'));
} else {
$out .= html_writer::link(new moodle_url('/admin/index.php',
- array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 0)),
+ array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 0, 'cache' => 0)),
get_string('somehighlightedonly', 'core_plugin'));
}
$out .= $this->output->container_end();
get_string('displayname', 'core_plugin'),
get_string('source', 'core_plugin'),
get_string('version', 'core_plugin'),
+ get_string('release', 'core_plugin'),
get_string('availability', 'core_plugin'),
get_string('actions', 'core_plugin'),
get_string('notes','core_plugin'),
);
- $table->headspan = array(1, 1, 1, 1, 2, 1);
+ $table->headspan = array(1, 1, 1, 1, 1, 2, 1);
$table->colclasses = array(
- 'pluginname', 'source', 'version', 'availability', 'settings', 'uninstall', 'notes'
+ 'pluginname', 'source', 'version', 'release', 'availability', 'settings', 'uninstall', 'notes'
);
foreach ($plugininfo as $type => $plugins) {
}
$version = new html_table_cell($plugin->versiondb);
+ $release = new html_table_cell($plugin->release);
$isenabled = $plugin->is_enabled();
if (is_null($isenabled)) {
$notes = new html_table_cell($requiredby.$updateinfo);
$row->cells = array(
- $pluginname, $source, $version, $availability, $settings, $uninstall, $notes
+ $pluginname, $source, $version, $release, $availability, $settings, $uninstall, $notes
);
$table->data[] = $row;
}
// This file defines settingpages and externalpages under the "appearance" category
-if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
+$capabilities = array(
+ 'moodle/my:configsyspages',
+ 'moodle/tag:manage'
+);
+
+if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // speedup for non-admins, add all caps used on this page
$ADMIN->add('appearance', new admin_category('themes', new lang_string('themes')));
// "themesettings" settingpage
}
- // calendar
+ // Calendar settings.
$temp = new admin_settingpage('calendar', new lang_string('calendarsettings','admin'));
+
+ $temp->add(new admin_setting_configselect('calendartype', new lang_string('calendartype', 'admin'),
+ new lang_string('calendartype_desc', 'admin'), 'gregorian', \core_calendar\type_factory::get_list_of_calendar_types()));
$temp->add(new admin_setting_special_adminseesall());
//this is hacky because we do not want to include the stuff from calendar/lib.php
$temp->add(new admin_setting_configselect('calendar_site_timeformat', new lang_string('pref_timeformat', 'calendar'),
$temp->add(new admin_setting_configcheckbox('doctonewwindow', new lang_string('doctonewwindow', 'admin'), new lang_string('configdoctonewwindow', 'admin'), 0));
$ADMIN->add('appearance', $temp);
- $temp = new admin_externalpage('mypage', new lang_string('mypage', 'admin'), $CFG->wwwroot . '/my/indexsys.php');
+ $temp = new admin_externalpage('mypage', new lang_string('mypage', 'admin'), $CFG->wwwroot . '/my/indexsys.php',
+ 'moodle/my:configsyspages');
$ADMIN->add('appearance', $temp);
- $temp = new admin_externalpage('profilepage', new lang_string('myprofile', 'admin'), $CFG->wwwroot . '/user/profilesys.php');
+ $temp = new admin_externalpage('profilepage', new lang_string('myprofile', 'admin'), $CFG->wwwroot . '/user/profilesys.php',
+ 'moodle/my:configsyspages');
$ADMIN->add('appearance', $temp);
// coursecontact is the person responsible for course - usually manages enrolments, receives notification, etc.
$ADMIN->add('appearance', $temp);
// link to tag management interface
- $ADMIN->add('appearance', new admin_externalpage('managetags', new lang_string('managetags', 'tag'), "$CFG->wwwroot/tag/manage.php"));
+ $ADMIN->add('appearance', new admin_externalpage('managetags', new lang_string('managetags', 'tag'), $CFG->wwwroot.'/tag/manage.php', 'moodle/tag:manage'));
$temp = new admin_settingpage('additionalhtml', new lang_string('additionalhtml', 'admin'));
$temp->add(new admin_setting_heading('additionalhtml_heading', new lang_string('additionalhtml_heading', 'admin'), new lang_string('additionalhtml_desc', 'admin')));
// Add common settings page
$temp = new admin_settingpage('managerepositoriescommon', new lang_string('commonrepositorysettings', 'repository'));
- $temp->add(new admin_setting_configtext('repositorycacheexpire', new lang_string('cacheexpire', 'repository'), new lang_string('configcacheexpire', 'repository'), 120));
+ $temp->add(new admin_setting_configtext('repositorycacheexpire', new lang_string('cacheexpire', 'repository'), new lang_string('configcacheexpire', 'repository'), 120, PARAM_INT));
+ $temp->add(new admin_setting_configtext('repositorygetfiletimeout', new lang_string('getfiletimeout', 'repository'), new lang_string('configgetfiletimeout', 'repository'), 30, PARAM_INT));
+ $temp->add(new admin_setting_configtext('repositorysyncfiletimeout', new lang_string('syncfiletimeout', 'repository'), new lang_string('configsyncfiletimeout', 'repository'), 1, PARAM_INT));
+ $temp->add(new admin_setting_configtext('repositorysyncimagetimeout', new lang_string('syncimagetimeout', 'repository'), new lang_string('configsyncimagetimeout', 'repository'), 3, PARAM_INT));
$temp->add(new admin_setting_configcheckbox('repositoryallowexternallinks', new lang_string('allowexternallinks', 'repository'), new lang_string('configallowexternallinks', 'repository'), 1));
$temp->add(new admin_setting_configcheckbox('legacyfilesinnewcourses', new lang_string('legacyfilesinnewcourses', 'admin'), new lang_string('legacyfilesinnewcourses_help', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('legacyfilesaddallowed', new lang_string('legacyfilesaddallowed', 'admin'), new lang_string('legacyfilesaddallowed_help', 'admin'), 1));
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR => 'HTTP_CLIENT, REMOTE_ADDR',
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR');
$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'), new lang_string('configgetremoteaddrconf', 'admin'), 0, $options));
+
$temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'), new lang_string('webproxyinfo', 'admin')));
$temp->add(new admin_setting_configtext('proxyhost', new lang_string('proxyhost', 'admin'), new lang_string('configproxyhost', 'admin'), '', PARAM_HOST));
$temp->add(new admin_setting_configtext('proxyport', new lang_string('proxyport', 'admin'), new lang_string('configproxyport', 'admin'), 0, PARAM_INT));
$temp->add(new admin_setting_configselect('extramemorylimit', new lang_string('extramemorylimit', 'admin'),
new lang_string('configextramemorylimit', 'admin'), '512M',
$memoryoptions));
+$temp->add(new admin_setting_configtext('maxtimelimit', new lang_string('maxtimelimit', 'admin'),
+ new lang_string('maxtimelimit_desc', 'admin'), 0, PARAM_INT));
$temp->add(new admin_setting_configtext('curlcache', new lang_string('curlcache', 'admin'),
new lang_string('configcurlcache', 'admin'), 120, PARAM_INT));
// We expect admin block to be visible, otherwise go to homepage.
if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
$this->getSession()->visit($this->locate_path('/'));
- $this->wait(self::TIMEOUT, '(document.readyState === "complete")');
+ $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
// Search by label.
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
$submitsearch->press();
- $this->wait(self::TIMEOUT, '(document.readyState === "complete")');
+ $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
// Admin settings does not use the same DOM structure than other moodle forms
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
$this->getSession()->wait($timeout, $javascript);
}
}
-
- /**
- * Goes to notification page ensuring site admin navigation is loaded.
- *
- * @Given /^I go to notifications page$/
- * @return Given[]
- */
- public function i_go_to_notifications_page() {
- if ($this->running_javascript()) {
- return array(
- new Given('I expand "' . get_string('administrationsite') . '" node'),
- new Given('I follow "' . get_string('notifications') . '"')
- );
- } else {
- return array(
- new Given('I follow "' . get_string('administrationsite') . '"')
- );
- }
- }
}
And I should not see "C_shortname Course fullname"
Scenario: Courses list with extended course names
- Given I go to notifications page
+ Given I expand "Site administration" node
And I click on "Courses" "link" in the "//div[@id='settingsnav']/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' type_setting ')][contains(., 'Appearance')]" "xpath_element"
And I check "Display extended course names"
When I press "Save changes"
And I expand "Users" node
And I expand "Accounts" node
And I follow "Upload users"
- When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filepicker
+ When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I should see "Tom"
And I expand "Users" node
And I follow "Groups"
And I select "Section 1 (1)" from "groups"
- And I wait "4" seconds
And the "members" select box should contain "Tom Jones"
// Getting $CFG data.
require_once(__DIR__ . '/../../../../config.php');
-// CFG->behat_prefix must be set and with value different than CFG->prefix and phpunit_prefix.
-if (empty($CFG->behat_prefix) ||
- ($CFG->behat_prefix == $CFG->prefix) ||
- (!empty($CFG->phpunit_prefix) && $CFG->behat_prefix == $CFG->phpunit_prefix)) {
- behat_error(BEHAT_EXITCODE_CONFIG,
- 'Define $CFG->behat_prefix in config.php with a value different than $CFG->prefix and $CFG->phpunit_prefix');
-}
-
-// CFG->behat_dataroot must be set and with value different than CFG->dataroot and phpunit_dataroot.
-if (empty($CFG->behat_dataroot) ||
- ($CFG->behat_dataroot == $CFG->dataroot) ||
- (!empty($CFG->phpunit_dataroot) && $CFG->behat_dataroot == $CFG->phpunit_dataroot)) {
- behat_error(BEHAT_EXITCODE_CONFIG,
- 'Define $CFG->behat_dataroot in config.php with a value different than $CFG->dataroot and $CFG->phpunit_dataroot');
-}
+// When we use the utilities we don't know how the site
+// will be accessed, so if neither $CFG->behat_switchcompletely or
+// $CFG->behat_wwwroot are set we must think that the site will
+// be accessed using the built-in server which is set by default
+// to localhost:8000. We need to do this to prevent uses of the production
+// wwwroot when the site is being installed / dropped...
+$CFG->behat_wwwroot = behat_get_wwwroot();
+
+// Checking the integrity of the provided $CFG->behat_* vars
+// to prevent conflicts with production and phpunit environments.
+behat_check_config_vars();
// Create behat_dataroot if it doesn't exists.
if (!file_exists($CFG->behat_dataroot)) {
| Course 1 | C1 | 0 |
And I log in as "admin"
And I follow "Course 1"
- When I click on "Move this to the dock" "button" in the "Administration" "block"
+ When I dock "Administration" block
Then I should not see "Question bank" in the "region-pre" "region"
And I click on "//div[@id='dock']/descendant::h2[normalize-space(.)='Administration']" "xpath_element"
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And I log in as "admin"
- When I click on "Move this to the dock" "button" in the "Administration" "block"
+ When I dock "Administration" block
Then I should not see "Turn editing on" in the "region-pre" "region"
-@tool @tool_behat
+@tool @tool_behat @_only_local
Feature: Set up contextual data for tests
In order to write tests quickly
As a developer
| fullname | shortname |
| Course 1 | C1 |
And the following "activities" exists:
- | activity | name | intro | course | idnumber |
- | assign | Test assignment name | Test assignment description | C1 | assign1 |
- | data | Test database name | Test database description | C1 | data1 |
+ | activity | name | intro | course | idnumber |
+ | assign | Test assignment name | Test assignment description | C1 | assign1 |
+ | assignment | Test assignment22 name | Test assignment22 description | C1 | assignment1 |
+ | book | Test book name | Test book description | C1 | book1 |
+ | chat | Test chat name | Test chat description | C1 | chat1 |
+ | choice | Test choice name | Test choice description | C1 | choice1 |
+ | data | Test database name | Test database description | C1 | data1 |
+ | feedback | Test feedback name | Test feedback description | C1 | feedback1 |
+ | folder | Test folder name | Test folder description | C1 | folder1 |
+ | forum | Test forum name | Test forum description | C1 | forum1 |
+ | glossary | Test glossary name | Test glossary description | C1 | glossary1 |
+ | imscp | Test imscp name | Test imscp description | C1 | imscp1 |
+ | label | Test label name | Test label description | C1 | label1 |
+ | lesson | Test lesson name | Test lesson description | C1 | lesson1 |
+ | lti | Test lti name | Test lti description | C1 | lti1 |
+ | page | Test page name | Test page description | C1 | page1 |
+ | quiz | Test quiz name | Test quiz description | C1 | quiz1 |
+ | resource | Test resource name | Test resource description | C1 | resource1 |
+ | scorm | Test scorm name | Test scorm description | C1 | scorm1 |
+ | survey | Test survey name | Test survey description | C1 | survey1 |
+ | url | Test url name | Test url description | C1 | url1 |
+ | wiki | Test wiki name | Test wiki description | C1 | wiki1 |
+ | workshop | Test workshop name | Test workshop description | C1 | workshop1 |
When I log in as "admin"
And I follow "Course 1"
Then I should see "Test assignment name"
+ # Assignment 2.2 module type is disabled by default
+ # And I should see "Test assignment22 name"
+ And I should see "Test book name"
+ And I should see "Test chat name"
+ And I should see "Test choice name"
And I should see "Test database name"
+ # Feedback module type is disabled by default
+ # And I should see "Test feedback name"
+ And I should see "Test folder name"
+ And I should see "Test forum name"
+ And I should see "Test glossary name"
+ And I should see "Test imscp name"
+ # We don't see label name, we see only description:
+ And I should see "Test label description"
+ And I should see "Test lesson name"
+ And I should see "Test lti name"
+ And I should see "Test page name"
+ And I should see "Test quiz name"
+ And I should see "Test resource name"
+ And I should see "Test scorm name"
+ And I should see "Test survey name"
+ And I should see "Test url name"
+ And I should see "Test wiki name"
+ And I should see "Test workshop name"
And I follow "Test assignment name"
And I should see "Test assignment description"
Then the "groups" select box should contain "Group 1 (1)"
And the "groups" select box should contain "Group 2 (1)"
And I select "Group 1 (1)" from "groups"
- And I wait "5" seconds
And the "members" select box should contain "Student 1"
And I select "Group 2 (1)" from "groups"
- And I wait "5" seconds
And the "members" select box should contain "Student 2"
$this->markTestSkipped('Behat not installed.');
}
+ // It is possible that it has no value.
+ if (empty($CFG->behat_wwwroot)) {
+ $CFG->behat_wwwroot = behat_get_wwwroot();
+ }
+
// To avoid user value at config.php level.
unset($CFG->behat_config);
// Lists.
$this->assertContains('- feature1', $contents);
$this->assertContains('- feature3', $contents);
+
+ unset($CFG->behat_wwwroot);
}
}
$progressbar->create(); // prints the HTML code of the progress bar
// we may need a bit of extra execution time and memory here
- @set_time_limit(HOURSECS);
+ core_php_time_limit::raise(HOURSECS);
raise_memory_limit(MEMORY_EXTRA);
tool_customlang_utils::checkout($lng, $progressbar);
* @return does not return, calls die()
*/
function tool_dbtransfer_export_xml_database($description, $mdb) {
- @set_time_limit(0);
+ core_php_time_limit::raise();
\core\session\manager::write_close(); // Release session.
* @return void
*/
function tool_dbtransfer_transfer_database(moodle_database $sourcedb, moodle_database $targetdb, progress_trace $feedback = null) {
- @set_time_limit(0);
+ core_php_time_limit::raise();
\core\session\manager::write_close(); // Release session.
// Update time limit so PHP doesn't time out.
if (!CLI_SCRIPT) {
- set_time_limit(120);
+ core_php_time_limit::raise(120);
}
}
echo $OUTPUT->notification('Please be patient and wait for this to complete...', 'notifysuccess');
- set_time_limit(0);
+ core_php_time_limit::raise();
foreach ($rs as $table) {
$DB->set_debug(true);
$notice_error = array();
if (($mode == INSTALLATION_OF_SELECTED_LANG) and confirm_sesskey() and !empty($pack)) {
- set_time_limit(0);
+ core_php_time_limit::raise();
make_temp_directory('');
make_upload_directory('lang');
}
if ($mode == UPDATE_ALL_LANG) {
- set_time_limit(0);
+ core_php_time_limit::raise();
$installer = new lang_installer();
/// Turn off time limits, sometimes upgrades can be slow.
-@set_time_limit(0);
+core_php_time_limit::raise();
echo '<strong>Progress:</strong>';
$i = 0;
error('Not available on production sites, sorry.');
}
-set_time_limit(60*30);
+core_php_time_limit::raise(60*30);
$oldcwd = getcwd();
$code = 0;
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Site wide search-replace form.
+ *
+ * @package tool_replace
+ * @copyright 2013 Petr Skoda {@link http://skodak.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once("$CFG->libdir/formslib.php");
+
+/**
+ * Site wide search-replace form.
+ */
+class tool_replace_form extends moodleform {
+ function definition() {
+ global $CFG, $DB;
+
+ $mform = $this->_form;
+
+ $mform->addElement('header', 'searchhdr', get_string('pluginname', 'tool_replace'));
+ $mform->setExpanded('searchhdr', true);
+
+ $mform->addElement('text', 'search', get_string('searchwholedb', 'tool_replace'), 'size="50"');
+ $mform->setType('search', PARAM_RAW);
+ $mform->addElement('static', 'searchst', '', get_string('searchwholedbhelp', 'tool_replace'));
+ $mform->addRule('search', get_string('required'), 'required', null, 'client');
+
+ $mform->addElement('text', 'replace', get_string('replacewith', 'tool_replace'), 'size="50"', PARAM_RAW);
+ $mform->addElement('static', 'replacest', '', get_string('replacewithhelp', 'tool_replace'));
+ $mform->setType('replace', PARAM_RAW);
+ $mform->addElement('checkbox', 'shorten', get_string('shortenoversized', 'tool_replace'));
+ $mform->addRule('replace', get_string('required'), 'required', null, 'client');
+
+ $mform->addElement('header', 'confirmhdr', get_string('confirm'));
+ $mform->setExpanded('confirmhdr', true);
+ $mform->addElement('checkbox', 'sure', get_string('disclaimer', 'tool_replace'));
+ $mform->addRule('sure', get_string('required'), 'required', null, 'client');
+
+ $this->add_action_buttons(false, get_string('doit', 'tool_replace'));
+ }
+
+ function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+
+ if (empty($data['shorten']) and core_text::strlen($data['search']) < core_text::strlen($data['replace'])) {
+ $errors['shorten'] = get_string('required');
+ }
+
+ return $errors;
+ }
+}
/**
* Search and replace strings throughout all texts in the whole database
*
- * @package tool
- * @subpackage replace
+ * @package tool_replace
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
admin_externalpage_setup('toolreplace');
-$search = optional_param('search', '', PARAM_RAW);
-$replace = optional_param('replace', '', PARAM_RAW);
-$sure = optional_param('sure', 0, PARAM_BOOL);
-
-###################################################################
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('pageheader', 'tool_replace'));
-if ($DB->get_dbfamily() !== 'mysql' and $DB->get_dbfamily() !== 'postgres') {
- //TODO: add $DB->text_replace() to DML drivers
+if (!$DB->replace_all_text_supported()) {
echo $OUTPUT->notification(get_string('notimplemented', 'tool_replace'));
echo $OUTPUT->footer();
die;
}
-if (!data_submitted() or !$search or !$replace or !confirm_sesskey() or !$sure) { /// Print a form
- echo $OUTPUT->notification(get_string('notsupported', 'tool_replace'));
- echo $OUTPUT->notification(get_string('excludedtables', 'tool_replace'));
+echo $OUTPUT->box_start();
+echo $OUTPUT->notification(get_string('notsupported', 'tool_replace'));
+echo $OUTPUT->notification(get_string('excludedtables', 'tool_replace'));
+echo $OUTPUT->box_end();
+
+$form = new tool_replace_form();
- echo $OUTPUT->box_start();
- echo '<div class="mdl-align">';
- echo '<form action="index.php" method="post"><div>';
- echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
- echo '<div><label for="search">'.get_string('searchwholedb', 'tool_replace').
- ' </label><input id="search" type="text" name="search" size="40" /> ('.
- get_string('searchwholedbhelp', 'tool_replace').')</div>';
- echo '<div><label for="replace">'.get_string('replacewith', 'tool_replace').
- ' </label><input type="text" id="replace" name="replace" size="40" /> ('.
- get_string('replacewithhelp', 'tool_replace').')</div>';
- echo '<div><label for="sure">'.get_string('disclaimer', 'tool_replace').' </label><input type="checkbox" id="sure" name="sure" value="1" /></div>';
- echo '<div class="buttons"><input type="submit" class="singlebutton" value="Yes, do it now" /></div>';
- echo '</div></form>';
- echo '</div>';
- echo $OUTPUT->box_end();
+if (!$data = $form->get_data()) {
+ $form->display();
echo $OUTPUT->footer();
- die;
+ die();
}
+// Scroll to the end when finished.
+$PAGE->requires->js_init_code("window.scrollTo(0, 5000000);");
+
echo $OUTPUT->box_start();
-db_replace($search, $replace);
+db_replace($data->search, $data->replace);
echo $OUTPUT->box_end();
-/// Rebuild course cache which might be incorrect now
-echo $OUTPUT->notification(get_string('notifyrebuilding', 'tool_replace'), 'notifysuccess');
-rebuild_course_cache();
-echo $OUTPUT->notification(get_string('notifyfinished', 'tool_replace'), 'notifysuccess');
+// Course caches are now rebuilt on the fly.
echo $OUTPUT->continue_button(new moodle_url('/admin/index.php'));
echo $OUTPUT->footer();
-
-
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-$string['disclaimer'] = 'I understand the risks of this operation:';
+$string['cannotfit'] = 'The replacement is longer than original and shortening is not allow, cannot continue.';
+$string['disclaimer'] = 'I understand the risks of this operation';
+$string['doit'] = 'Yes, do it!';
$string['excludedtables'] = 'Several tables are not updated as part of the text replacement. This include configuration, log, events, and session tables.';
$string['pageheader'] = 'Search and replace text throughout the whole database';
$string['notifyfinished'] = '...finished';
$string['notifyrebuilding'] = 'Rebuilding course cache...';
-$string['notimplemented'] = 'Sorry, this feature is implemented only for MySQL and PostgreSQL databases.';
+$string['notimplemented'] = 'Sorry, this feature is not implemented in your database driver.';
$string['notsupported'] ='This script is not supported, always make complete backup before proceeding!<br />This operation can not be reverted!';
$string['pluginname'] = 'DB search and replace';
-$string['replacewith'] = 'Replace with this string:';
+$string['replacewith'] = 'Replace with this string';
$string['replacewithhelp'] = 'usually new server URL';
-$string['searchwholedb'] = 'Search whole database for:';
+$string['searchwholedb'] = 'Search whole database for';
$string['searchwholedbhelp'] = 'usually previous server URL';
+$string['shortenoversized'] = 'Shorten result if necessary';
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2013110500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2013110501; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2013110500; // Requires this Moodle version
$plugin->component = 'tool_replace'; // Full name of the plugin (used for diagnostics)
$errors = 0;
// We will most certainly need extra time and memory to process big files.
- @set_time_limit(0);
+ core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Loop over the CSV lines.
$tracker->start();
// We might need extra time and memory depending on the number of rows to preview.
- @set_time_limit(0);
+ core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Loop over the CSV lines.
@javascript
Scenario: Creation of unexisting courses
- Given I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filepicker
+ Given I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filemanager
And I click on "Preview" "button"
When I click on "Upload courses" "button"
Then I should see "The course exists and update is not allowed"
@javascript
Scenario: Creation of existing courses
- Given I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filepicker
+ Given I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filemanager
And I select "Create all, increment shortname if needed" from "Upload mode"
And I click on "Preview" "button"
When I click on "Upload courses" "button"
@javascript
Scenario: Updating a course fullname
- Given I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filepicker
+ Given I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filemanager
And I select "Only update existing courses" from "Upload mode"
And I select "Update with CSV data only" from "Update mode"
And I click on "Preview" "button"
}
}
$this->assertTrue($found);
-
- // Restore the time limit to prevent warning.
- set_time_limit(0);
}
public function test_restore_file() {
}
}
$this->assertTrue($found);
-
- // Restore the time limit to prevent warning.
- set_time_limit(0);
}
public function test_restore_invalid_file() {
$this->assertEquals($dir, $dir2);
$CFG->keeptempdirectoriesonbackup = $oldcfg;
-
- // Restore the time limit to prevent warning.
- set_time_limit(0);
}
public function test_get_role_ids() {
}
}
$this->assertTrue($found);
-
- // Restore the time limit to prevent warning.
- set_time_limit(0);
}
public function test_restore_restore_file() {
}
}
$this->assertTrue($found);
-
- // Restore the time limit to prevent warning.
- set_time_limit(0);
}
public function test_shortname_template() {
$iid = optional_param('iid', '', PARAM_INT);
$previewrows = optional_param('previewrows', 10, PARAM_INT);
-@set_time_limit(60*60); // 1 hour should be enough
+core_php_time_limit::raise(60*60); // 1 hour should be enough
raise_memory_limit(MEMORY_HUGE);
require_login();
continue;
}
if (!property_exists($user, $column) or !property_exists($existinguser, $column)) {
- // this should never happen
- debugging("Could not find $column on the user objects", DEBUG_DEVELOPER);
continue;
}
if ($updatetype == UU_UPDATE_MISSING) {
// Large files are likely to take their time and memory. Let PHP know
// that we'll take longer, and that the process should be recycled soon
// to free up memory.
- @set_time_limit(0);
+ core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Create a unique temporary directory, to process the zip file
if ($usertotal < 500) {
list($sort, $params) = users_order_by_sql('u');
- //user searchable selector - get all users (admin and guest included)
- //user must be confirmed, not deleted, not suspended, not guest
- $sql = "SELECT u.id, u.firstname, u.lastname
- FROM {user} u
- WHERE u.deleted = 0 AND u.confirmed = 1 AND u.suspended = 0 AND u.id != :siteguestid
- ORDER BY $sort";
+ // User searchable selector - return users who are confirmed, not deleted, not suspended and not a guest.
+ $sql = 'SELECT u.id, ' . get_all_user_name_fields(true, 'u') . '
+ FROM {user} u
+ WHERE u.deleted = 0
+ AND u.confirmed = 1
+ AND u.suspended = 0
+ AND u.id != :siteguestid
+ ORDER BY ' . $sort;
$params['siteguestid'] = $CFG->siteguest;
$users = $DB->get_records_sql($sql, $params);
$options = array();
if (empty($user->lang)) {
$user->lang = $CFG->lang;
}
+ if (empty($user->calendartype)) {
+ $user->calendartype = $CFG->calendartype;
+ }
$user->timecreated = time();
$user->timemodified = $user->timecreated;
if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype), 'id,username,auth')) {
* @return moodle_url
*/
function change_password_url() {
- if ($this->is_internal()) {
+ if ($this->is_internal() || empty($this->config->changepasswordurl)) {
// Standard form.
return null;
} else {
require_once($CFG->dirroot.'/user/lib.php');
$user->password = hash_internal_user_password($user->password);
+ if (empty($user->calendartype)) {
+ $user->calendartype = $CFG->calendartype;
+ }
$user->id = user_create_user($user, false);
$host = '{'.$host.":{$this->config->port}/imap/tls}";
break;
+ case 'imapnosslcert':
+ $host = '{'.$host.":{$this->config->port}/imap/novalidate-cert}";
+ break;
+
default:
$host = '{'.$host.":{$this->config->port}/imap}";
}
* @return moodle_url
*/
function change_password_url() {
- return new moodle_url($this->config->changepasswordurl);
+ if (!empty($this->config->changepasswordurl)) {
+ return new moodle_url($this->config->changepasswordurl);
+ } else {
+ return null;
+ }
}
/**
<td>
<?php
- $imaptypes = array('imap', 'imapssl', 'imapcert', 'imaptls');
+ $imaptypes = array('imap', 'imapssl', 'imapcert', 'imapnosslcert', 'imaptls');
foreach ($imaptypes as $imaptype) {
$imapoptions[$imaptype] = $imaptype;
}
if (empty($user->lang)) {
$user->lang = $CFG->lang;
}
+ if (empty($user->calendartype)) {
+ $user->calendartype = $CFG->calendartype;
+ }
$id = user_create_user($user, false);
echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
*/
function change_password_url() {
if (empty($this->config->stdchangepassword)) {
- return new moodle_url($this->config->changepasswordurl);
+ if (!empty($this->config->changepasswordurl)) {
+ return new moodle_url($this->config->changepasswordurl);
+ } else {
+ return null;
+ }
} else {
return null;
}
* @return moodle_url
*/
function change_password_url() {
- return new moodle_url($this->config->changepasswordurl);
+ if (!empty($this->config->changepasswordurl)) {
+ return new moodle_url($this->config->changepasswordurl);
+ } else {
+ return null;
+ }
}
/**
This files describes API changes in /auth/* - plugins,
information provided here is intended especially for developers.
+=== 2.7 ===
+
+* If you are returning a url in method change_password_url() from config, please make sure it is set before trying to use it.
+
=== 2.6 ===
* can_be_manually_set() - This function was introduced in the base class and returns false by default. If overriden by
/**
* Returns true if this authentication plugin is 'internal'.
*
+ * Webserice auth doesn't use password fields, it uses only tokens.
+ *
* @return bool
*/
function is_internal() {
- return true;
+ return false;
}
/**
// Carry out actual backup.
$backup->execute();
+ // Backup controller gets saved/loaded so the logger object changes and we
+ // have to retrieve it.
+ $logger = $backup->get_controller()->get_logger();
+ while (!is_a($logger, 'core_backup_html_logger')) {
+ $logger = $logger->get_next();
+ }
+
// Get HTML from logger.
$loghtml = $logger->get_html();
*/
public function execute_plan() {
// Basic/initial prevention against time/memory limits
- set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+ core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
raise_memory_limit(MEMORY_EXTRA);
// If this is not a course backup, inform the plan we are not
// including all the activities for sure. This will affect any
public function execute_plan() {
// Basic/initial prevention against time/memory limits
- set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+ core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
raise_memory_limit(MEMORY_EXTRA);
// If this is not a course restore, inform the plan we are not
// including all the activities for sure. This will affect any
throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status);
}
// Basic/initial prevention against time/memory limits
- set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+ core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
raise_memory_limit(MEMORY_EXTRA);
$this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter);
if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed
require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php');
// Basic/initial prevention against time/memory limits
- set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+ core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
raise_memory_limit(MEMORY_EXTRA);
$this->progress->start_progress('Backup format conversion');
backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
} catch (file_exception $e) {
$this->add_result(array('missing_files_in_pool' => true));
- $this->add_log('missing file in pool: ' . $e->debuginfo, backup::LOG_WARNING);
+
+ // Build helpful log message with all information necessary to identify
+ // file location.
+ $context = context::instance_by_id($values->contextid, IGNORE_MISSING);
+ $contextname = '';
+ if ($context) {
+ $contextname = ' \'' . $context->get_context_name() . '\'';
+ }
+ $message = 'Missing file in pool: ' . $values->filepath . $values->filename .
+ ' (context ' . $values->contextid . $contextname . ', component ' .
+ $values->component . ', filearea ' . $values->filearea . ', itemid ' .
+ $values->itemid . ') [' . $e->debuginfo . ']';
+ $this->add_log($message, backup::LOG_WARNING);
}
}
}
if ($includesfiles) {
// The file is not found in the backup.
if (!file_exists($backuppath)) {
- $result = new stdClass();
- $result->code = 'file_missing_in_backup';
- $result->message = sprintf('missing file %s%s in backup', $file->filepath, $file->filename);
- $result->level = backup::LOG_WARNING;
- $results[] = $result;
+ $results[] = self::get_missing_file_result($file);
continue;
}
$fs->create_file_from_storedfile($file_record, $foundfile->id);
} else {
// A matching existing file record was not found in the database.
- $result = new stdClass();
- $result->code = 'file_missing_in_backup';
- $result->message = sprintf('missing file %s%s in backup', $file->filepath, $file->filename);
- $result->level = backup::LOG_WARNING;
- $results[] = $result;
+ $results[] = self::get_missing_file_result($file);
continue;
}
}
return $results;
}
+ /**
+ * Returns suitable entry to include in log when there is a missing file.
+ *
+ * @param stdClass $file File definition
+ * @return stdClass Log entry
+ */
+ protected static function get_missing_file_result($file) {
+ $result = new stdClass();
+ $result->code = 'file_missing_in_backup';
+ $result->message = 'Missing file in backup: ' . $file->filepath . $file->filename .
+ ' (old context ' . $file->contextid . ', component ' . $file->component .
+ ', filearea ' . $file->filearea . ', old itemid ' . $file->itemid . ')';
+ $result->level = backup::LOG_WARNING;
+ return $result;
+ }
+
/**
* Given one restoreid, create in DB all the users present
* in backup_ids having newitemid = 0, as far as
cron_trace_time_and_memory();
// This could take a while!
- @set_time_limit(0);
+ core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
$nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup($admin->timezone, $now);
// Remove the test dir and any content
@remove_dir(dirname($file));
-
- // Clear the time limit, otherwise PHPUnit complains.
- set_time_limit(0);
}
/**
const INDETERMINATE = -1;
/**
+ * This value is set rather high to ensure there are no regressions from
+ * previous behaviour. For testing, it may be useful to set the
+ * frontendservertimeout config option to a lower value, such as 180
+ * seconds (default for some commercial products).
+ *
* @var int The number of seconds that can pass without progress() calls.
*/
- const TIME_LIMIT_WITHOUT_PROGRESS = 120;
+ const TIME_LIMIT_WITHOUT_PROGRESS = 3600;
/**
* @var int Time of last progress call.
// Update progress.
$this->count++;
$this->lastprogresstime = $now;
- set_time_limit(self::TIME_LIMIT_WITHOUT_PROGRESS);
+
+ // Update time limit before next progress display.
+ core_php_time_limit::raise(self::TIME_LIMIT_WITHOUT_PROGRESS);
$this->update_progress();
}
// Make some progress and check that the time limit gets added.
$progress->step_time();
+ core_php_time_limit::get_and_clear_unit_test_data();
$progress->progress(2);
$this->assertTrue($progress->was_update_called());
- $this->assertEquals(120, ini_get('max_execution_time'));
+ $this->assertEquals(array(core_backup_progress::TIME_LIMIT_WITHOUT_PROGRESS),
+ core_php_time_limit::get_and_clear_unit_test_data());
// Check the new value.
$this->assert_min_max(0.2, 0.2, $progress);
// There was 1 progress call.
$this->assertEquals(1, $progress->get_progress_count());
-
- // Clear the time limit, otherwise phpunit complains.
- set_time_limit(0);
}
/**
$this->assert_min_max(0.8, 0.8, $progress);
$progress->end_progress();
$this->assertFalse($progress->is_in_progress_section());
-
- set_time_limit(0);
}
/**
$this->assert_min_max(0.4, 1.0, $progress);
$progress->end_progress();
$this->assert_min_max(1.0, 1.0, $progress);
-
- set_time_limit(0);
}
/**
$this->assert_min_max(0.01, 0.01, $progress);
$progress->end_progress();
$this->assert_min_max(0.01, 0.01, $progress);
-
- // Clear the time limit, otherwise phpunit complains.
- set_time_limit(0);
}
/**
$this->assert_min_max(0.02, 0.02, $progress);
$progress->end_progress();
$this->assert_min_max(0.02, 0.02, $progress);
-
- set_time_limit(0);
}
/**
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~would exceed max~', $e->getMessage()));
}
-
- // Clear the time limit, otherwise phpunit complains.
- set_time_limit(0);
}
/**
*/
function __construct(base_ui_stage $uistage, $action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) {
$this->uistage = $uistage;
+ // Add a class to the attributes to prevent the default collapsible behaviour.
+ if (!$attributes) {
+ $attributes = array();
+ }
+ $attributes['class'] = 'unresponsive';
+ if (!isset($attributes['enctype'])) {
+ $attributes['enctype'] = 'application/x-www-form-urlencoded'; // Enforce compatibility with our max_input_vars hack.
+ }
parent::__construct($action, $customdata, $method, $target, $attributes, $editable);
}
/**
$config->yesLabel = get_string('confirmcancelyes', 'backup');
$config->noLabel = get_string('confirmcancelno', 'backup');
$config->closeButtonTitle = get_string('close', 'editor');
- $PAGE->requires->yui_module('moodle-backup-confirmcancel', 'M.core_backup.watch_cancel_buttons', array($config));
+ $PAGE->requires->yui_module('moodle-backup-confirmcancel', 'M.core_backup.confirmcancel.watch_cancel_buttons', array($config));
// Get list of module types on course.
$modinfo = get_fast_modinfo($COURSE);
$modnames = $modinfo->get_used_module_names(true);
- $PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.select_all_init',
+ $PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.backupselectall',
array($modnames));
$PAGE->requires->strings_for_js(array('select', 'all', 'none'), 'moodle');
$PAGE->requires->strings_for_js(array('showtypes', 'hidetypes'), 'backup');
$html .= html_writer::start_tag('form', array(
'action' => $url->out_omit_querystring(),
'class' => 'backup-restore',
+ 'enctype' => 'application/x-www-form-urlencoded', // Enforce compatibility with our max_input_vars hack.
'method' => 'post'));
foreach ($url->params() as $name => $value) {
$html .= html_writer::empty_tag('input', array(
And I press "Continue"
And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
And "//div[contains(concat(' ', normalize-space(@class), ' '), ' fitem ')][contains(., 'Include calendar events')]/descendant::img" "xpath_element" should exists
- And I check "Include course logs"
+ And "Include course logs" "checkbox" should exists
And I press "Next"
@javascript
// Go to homepage.
$this->getSession()->visit($this->locate_path('/'));
+ $this->wait();
// Click the course link.
$this->find_link($backupcourse)->click();
+ $this->wait();
// Click the backup link.
$this->find_link(get_string('backup'))->click();
+ $this->wait();
// Initial settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('backupstage1action', 'backup'))->press();
+ $this->wait();
// Schema settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('backupstage2action', 'backup'))->press();
+ $this->wait();
// Confirmation and review, backup filename can also be specified.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('backupstage4action', 'backup'))->press();
// Waiting for it to finish.
- $this->wait(10);
+ $this->wait(self::EXTENDED_TIMEOUT);
// Last backup continue button.
$this->find_button(get_string('backupstage16action', 'backup'))->press();
// Go to homepage.
$this->getSession()->visit($this->locate_path('/'));
+ $this->wait();
// Click the course link.
$this->find_link($tocourse)->click();
+ $this->wait();
// Click the import link.
$this->find_link(get_string('import'))->click();
+ $this->wait();
// Select the course.
$exception = new ExpectationException('"' . $fromcourse . '" course not found in the list of courses to import from', $this->getSession());
$radionode->click();
$this->find_button(get_string('continue'))->press();
+ $this->wait();
// Initial settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('importbackupstage1action', 'backup'))->press();
+ $this->wait();
// Schema settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('importbackupstage2action', 'backup'))->press();
+ $this->wait();
// Run it.
$this->find_button(get_string('importbackupstage4action', 'backup'))->press();
- $this->wait();
+ $this->wait(self::EXTENDED_TIMEOUT);
// Continue and redirect to 'to' course.
$this->find_button(get_string('continue'))->press();
// Settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('restorestage4action', 'backup'))->press();
+ $this->wait();
// Schema.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('restorestage8action', 'backup'))->press();
+ $this->wait();
// Review, no options here.
$this->find_button(get_string('restorestage16action', 'backup'))->press();
- $this->wait(10);
+ $this->wait();
// Last restore continue button, redirected to restore course after this.
$this->find_button(get_string('restorestage32action', 'backup'))->press();
+
+ // Long wait when waiting for the restore to finish.
+ $this->wait(self::EXTENDED_TIMEOUT);
}
/**
return;
}
+ // Wait for the page to be loaded and the JS ready.
+ $this->wait();
+
// If we find any of the provided options in the current form we should set the value.
$datahash = $options->getRowsHash();
foreach ($datahash as $locator => $value) {
try {
+ // Using $this->find* to enforce stability over speed.
$fieldnode = $this->find_field($locator);
$field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
$field->set_value($value);
}
/**
- * Waits until the DOM is ready.
+ * Waits until the DOM and the page Javascript code is ready.
*
- * @param int To override the default timeout
+ * @param int $timeout The number of seconds that we wait.
* @return void
*/
protected function wait($timeout = false) {
if (!$timeout) {
$timeout = self::TIMEOUT;
}
- $this->getSession()->wait($timeout, '(document.readyState === "complete")');
+
+ $this->getSession()->wait($timeout * 1000, self::PAGE_READY_JS);
}
}
And I add a "Database" to section "1" and I fill the form with:
| Name | Test database name |
| Description | Test database description |
- And I open "Test database name" actions menu
- When I click on "Duplicate" "link" in the "Test database name" activity
+ And I duplicate "Test database name" activity
+ And I wait until section "1" is available
And I open "Test database name" actions menu
And I click on "Edit settings" "link" in the "Test database name" activity
And I fill the moodle form with:
+++ /dev/null
-YUI.add('moodle-backup-confirmcancel', function(Y) {
-
-// Namespace for the backup
-M.core_backup = M.core_backup || {};
-/**
- * Adds confirmation dialogues to the cancel buttons on the page.
- *
- * @param {object} config
- */
-M.core_backup.watch_cancel_buttons = function(config) {
- Y.all('.confirmcancel').each(function(){
- this._confirmationListener = this._confirmationListener || this.on('click', function(e){
- // Prevent the default event (sumbit) from firing
- e.preventDefault();
- // Create the confirm box
- var confirm = new M.core.confirm(config);
- // If the user clicks yes
- confirm.on('complete-yes', function(e){
- // Detach the listener for the confirm box so it doesn't fire again.
- this._confirmationListener.detach();
- // Simulate the original cancel button click
- this.simulate('click');
- }, this);
- // Show the confirm box
- confirm.show();
- }, this);
- });
-}
-
-}, '@VERSION@', {'requires':['base','node','node-event-simulate','moodle-core-notification']});
--- /dev/null
+{
+ "name": "moodle-backup-backupselectall",
+ "builds": {
+ "moodle-backup-backupselectall": {
+ "jsfiles": [
+ "backupselectall.js"
+ ]
+ }
+ }
+}
-YUI.add('moodle-backup-backupselectall', function(Y) {
+/**
+ * Adds select all/none links to the top of the backup/restore/import schema page.
+ *
+ * @module moodle-backup-backupselectall
+ */
// Namespace for the backup
M.core_backup = M.core_backup || {};
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
+ *
+ * @class M.core_backup.backupselectall
*/
-M.core_backup.select_all_init = function(modnames) {
+M.core_backup.backupselectall = function(modnames) {
var formid = null;
var helper = function(e, check, type, mod) {
if (prefix && name.substring(0, prefix.length) !== prefix) {
return;
}
- if (name.substring(name.length - len) == type) {
+ if (name.substring(name.length - len) === type) {
checkbox.set('checked', check);
}
});
var withuserdata = false;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
- if (name.substring(name.length - 9) == '_userdata') {
+ if (name.substring(name.length - 9) === '_userdata') {
withuserdata = '_userdata';
- } else if (name.substring(name.length - 9) == '_userinfo') {
+ } else if (name.substring(name.length - 9) === '_userinfo') {
withuserdata = '_userinfo';
}
});
if (!modnames.hasOwnProperty(mod)) {
continue;
}
- var html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
+ html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
if (withuserdata) {
html += html_generator('normal_setting', 'userdata-mod_' + mod, modnames[mod]);
}
modlist.currentlyshown = !modlist.currentlyshown;
// Either hide or show the links.
- var animcfg = { node: modlist, duration: 0.2 };
+ var animcfg = { node: modlist, duration: 0.2 },
+ anim;
if (modlist.currentlyshown) {
// Animate reveal of the module links.
modlist.show();
animcfg.to = { maxHeight: modlist.get('clientHeight') + 'px' };
modlist.setStyle('maxHeight', '0px');
- var anim = new Y.Anim(animcfg);
+ anim = new Y.Anim(animcfg);
anim.on('end', function() { modlist.setStyle('maxHeight', 'none'); });
anim.run();
} else {
// Animate hide of the module links.
animcfg.to = { maxHeight: '0px' };
modlist.setStyle('maxHeight', modlist.get('clientHeight') + 'px');
- var anim = new Y.Anim(animcfg);
+ anim = new Y.Anim(animcfg);
anim.on('end', function() { modlist.hide(); modlist.setStyle('maxHeight', 'none'); });
anim.run();
}
};
- Y.one('#backup-bytype').on('click', function(e) { toggletypes(); });
+ Y.one('#backup-bytype').on('click', function() { toggletypes(); });
Y.one('#backup-all-included').on('click', function(e) { helper(e, true, '_included'); });
Y.one('#backup-none-included').on('click', function(e) { helper(e, false, '_included'); });
Y.one('#backup-all-userdata').on('click', function(e) { helper(e, true, withuserdata); });
Y.one('#backup-none-userdata').on('click', function(e) { helper(e, false, withuserdata); });
}
-}
-
-}, '@VERSION@', {'requires':['base', 'node', 'event', 'node-event-simulate', 'anim']});
+};
--- /dev/null
+{
+ "moodle-backup-backupselectall": {
+ "requires": [
+ "node",
+ "event",
+ "node-event-simulate",
+ "anim"
+ ]
+ }
+}
--- /dev/null
+{
+ "moodle-backup-backupselectall": {
+ "requires": [
+ "node",
+ "event",
+ "node-event-simulate",
+ "anim"
+ ]
+ }
+}
--- /dev/null
+{
+ "name": "moodle-backup-confirmcancel",
+ "builds": {
+ "moodle-backup-confirmcancel": {
+ "jsfiles": [
+ "confirmcancel.js"
+ ]
+ }
+ }
+}
--- /dev/null
+/**
+ * Add a confirmation dialogue when cancelling a backup.
+ *
+ * @module moodle-backup-confirmcancel
+ */
+
+/**
+ * Add a confirmation dialogue when cancelling a backup.
+ *
+ * @class M.core_backup.confirmcancel
+ */
+
+
+// Namespace for the backup.
+M.core_backup = M.core_backup || {};
+
+M.core_backup.confirmcancel = {
+ /**
+ * An array of EventHandlers which call the confirm_cancel dialogue.
+ *
+ * @property listeners
+ * @protected
+ * @type Array
+ */
+ listeners: [],
+
+ /**
+ * The configuration supplied to this instance.
+ *
+ * @property config
+ * @protected
+ * @type Object
+ */
+ config: {},
+
+ /**
+ * Initializer to watch all cancel buttons.
+ *
+ * @method watch_cancel_buttons
+ * @param {Object} config The configuration for the confirmation dialogue.
+ */
+ watch_cancel_buttons: function(config) {
+ this.config = config;
+
+ this.listeners.push(
+ Y.one(Y.config.doc.body).delegate('click', this.confirm_cancel, '.confirmcancel', this)
+ );
+ },
+
+ /**
+ * Display the confirmation dialogue.
+ *
+ * @method confirm_cancel
+ * @protected
+ * @param {EventFacade} e
+ */
+ confirm_cancel: function(e) {
+ // Prevent the default event (submit) from firing.
+ e.preventDefault();
+
+ // Create the confirmation dialogue.
+ var confirm = new M.core.confirm(this.config);
+
+ // If the user clicks yes.
+ confirm.on('complete-yes', function(){
+ // Detach the listeners for the confirm box so they don't fire again.
+ new Y.EventHandle(M.core_backup.confirmcancel.listeners).detach();
+
+ // Simulate the original cancel button click.
+ e.currentTarget.simulate('click');
+ }, this);
+
+
+ // Show the confirm box.
+ confirm.show();
+ }
+};
--- /dev/null
+{
+ "moodle-backup-confirmcancel": {
+ "requires": [
+ "node",
+ "node-event-simulate",
+ "moodle-core-notification-confirm"
+ ]
+ }
+}
| Description | Test badge description |
| issuername | Test Badge Site |
| issuercontact | testuser@test-badge-site.com |
- And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
When I press "Create badge"
Then I should see "Edit details"
And I should see "Test Badge"
As an admin
I need to add criteria to badges in the system
- Background:
- Given I am on homepage
- And I log in as "admin"
-
@javascript
Scenario: Award profile badge
- Given I expand "Site administration" node
+ Given I log in as "admin"
+ And I expand "Site administration" node
And I expand "Badges" node
And I follow "Add a new badge"
And I fill the moodle form with:
| Description | Test badge description |
| issuername | Test Badge Site |
| issuercontact | testuser@test-badge-site.com |
- And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
And I select "Profile completion" from "type"
And I check "First name"
| username | firstname | lastname | email |
| teacher | teacher | 1 | teacher1@asd.com |
| student | student | 1 | student1@asd.com |
+ And I log in as "admin"
And I expand "Site administration" node
And I expand "Badges" node
And I follow "Add a new badge"
| Name | Site Badge |
| Description | Site badge description |
| issuername | Tester of site badge |
- And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
And I select "Manual issue by role" from "type"
And I check "Teacher"
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
- And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I click on "//span[text()='Badges']" "xpath_element" in the "Administration" "block"
| Name | Course Badge |
| Description | Course badge description |
| issuername | Tester of course badge |
- And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
And I select "Manual issue by role" from "type"
And I check "Teacher"
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
- And I log out
And I log in as "admin"
And I set the following administration settings values:
| Enable completion tracking | 1 |
| Name | Course Badge |
| Description | Course badge description |
| issuername | Tester of course badge |
- And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
And I select "Activity completion" from "type"
And I check "Test assignment name"
And I follow "Home"
And I follow "Course 1"
And I press "Mark as complete: Test assignment name"
- And I wait "2" seconds
And I expand "My profile" node
And I follow "My badges"
Then I should see "Course Badge"
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
- And I log out
And I log in as "admin"
And I set the following administration settings values:
| Enable completion tracking | 1 |
| Name | Course Badge |
| Description | Course badge description |
| issuername | Tester of course badge |
- And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
And I select "Course completion" from "type"
And I fill the moodle form with:
--- /dev/null
+@block @block_activity_modules @_only_local
+Feature: Block activity modules
+ In order to overview activity modules in a course
+ As a manager
+ I can add activities block in a course or on the frontpage
+
+ Background:
+ Given I log in as "admin"
+ And I expand "Site administration" node
+ And I expand "Plugins" node
+ And I expand "Activity modules" node
+ And I follow "Manage activities"
+ And I click on "//a[@title=\"Show\"]" "xpath_element" in the "Feedback" "table_row"
+ And I click on "//a[@title=\"Show\"]" "xpath_element" in the "Assignment (2.2)" "table_row"
+
+ Scenario: Add activities block on the frontpage
+ And the following "activities" exists:
+ | activity | name | intro | course | idnumber |
+ | assign | Frontpage assignment name | Frontpage assignment description | Acceptance test site | assign0 |
+ | assignment | Frontpage assignment22 name | Frontpage assignment22 description | Acceptance test site | assignment0 |
+ | book | Frontpage book name | Frontpage book description | Acceptance test site | book0 |
+ | chat | Frontpage chat name | Frontpage chat description | Acceptance test site | chat0 |
+ | choice | Frontpage choice name | Frontpage choice description | Acceptance test site | choice0 |
+ | data | Frontpage database name | Frontpage database description | Acceptance test site | data0 |
+ | feedback | Frontpage feedback name | Frontpage feedback description | Acceptance test site | feedback0 |
+ | forum | Frontpage forum name | Frontpage forum description | Acceptance test site | forum0 |
+ | label | Frontpage label name | Frontpage label description | Acceptance test site | label0 |
+ | lti | Frontpage lti name | Frontpage lti description | Acceptance test site | lti0 |
+ | page | Frontpage page name | Frontpage page description | Acceptance test site | page0 |
+ | quiz | Frontpage quiz name | Frontpage quiz description | Acceptance test site | quiz0 |
+ | resource | Frontpage resource name | Frontpage resource description | Acceptance test site | resource0 |
+ | imscp | Frontpage imscp name | Frontpage imscp description | Acceptance test site | imscp0 |
+ | folder | Frontpage folder name | Frontpage folder description | Acceptance test site | folder0 |
+ | glossary | Frontpage glossary name | Frontpage glossary description | Acceptance test site | glossary0 |
+ | scorm | Frontpage scorm name | Frontpage scorm description | Acceptance test site | scorm0 |
+ | lesson | Frontpage lesson name | Frontpage lesson description | Acceptance test site | lesson0 |
+ | survey | Frontpage survey name | Frontpage survey description | Acceptance test site | survey0 |
+ | url | Frontpage url name | Frontpage url description | Acceptance test site | url0 |
+ | wiki | Frontpage wiki name | Frontpage wiki description | Acceptance test site | wiki0 |
+ | workshop | Frontpage workshop name | Frontpage workshop description | Acceptance test site | workshop0 |
+
+ And I am on homepage
+ When I follow "Turn editing on"
+ And I add the "Activities" block
+ And I click on "Assignments" "link" in the "Activities" "block"
+ Then I should see "Frontpage assignment name"
+ And I am on homepage
+ And I click on "Assignments (2.2)" "link" in the "Activities" "block"
+ And I should see "Frontpage assignment22 name"
+ And I am on homepage
+ And I click on "Chats" "link" in the "Activities" "block"
+ And I should see "Frontpage chat name"
+ And I am on homepage
+ And I click on "Choices" "link" in the "Activities" "block"
+ And I should see "Frontpage choice name"
+ And I am on homepage
+ And I click on "Databases" "link" in the "Activities" "block"
+ And I should see "Frontpage database name"
+ And I am on homepage
+ And I click on "Feedback" "link" in the "Activities" "block"
+ And I should see "Frontpage feedback name"
+ And I am on homepage
+ And I click on "Forums" "link" in the "Activities" "block"
+ And I should see "Frontpage forum name"
+ And I am on homepage
+ And I click on "External Tools" "link" in the "Activities" "block"
+ And I should see "Frontpage lti name"
+ And I am on homepage
+ And I click on "Quizzes" "link" in the "Activities" "block"
+ And I should see "Frontpage quiz name"
+ And I am on homepage
+ And I click on "Glossaries" "link" in the "Activities" "block"
+ And I should see "Frontpage glossary name"
+ And I am on homepage
+ And I click on "SCORM packages" "link" in the "Activities" "block"
+ And I should see "Frontpage scorm name"
+ And I am on homepage
+ And I click on "Lessons" "link" in the "Activities" "block"
+ And I should see "Frontpage lesson name"
+ And I am on homepage
+ And I click on "Wikis" "link" in the "Activities" "block"
+ And I should see "Frontpage wiki name"
+ And I am on homepage
+ And I click on "Workshop" "link" in the "Activities" "block"
+ And I should see "Frontpage workshop name"
+ And I am on homepage
+ And I click on "Resources" "link" in the "Activities" "block"
+ And I should see "Frontpage book name"
+ And I should see "Frontpage page name"
+ And I should see "Frontpage resource name"
+ And I should see "Frontpage imscp name"
+ And I should see "Frontpage folder name"
+ And I should see "Frontpage url name"
+
+ Scenario: Add activities block in a course
+ Given the following "courses" exists:
+ | fullname | shortname | format |
+ | Course 1 | C1 | topics |
+ And the following "activities" exists:
+ | activity | name | intro | course | idnumber |
+ | assign | Test assignment name | Test assignment description | C1 | assign1 |
+ | assignment | Test assignment22 name | Test assignment22 description | C1 | assignment1 |
+ | book | Test book name | Test book description | C1 | book1 |
+ | chat | Test chat name | Test chat description | C1 | chat1 |
+ | choice | Test choice name | Test choice description | C1 | choice1 |
+ | data | Test database name | Test database description | C1 | data1 |
+ | feedback | Test feedback name | Test feedback description | C1 | feedback1 |
+ | folder | Test folder name | Test folder description | C1 | folder1 |
+ | forum | Test forum name | Test forum description | C1 | forum1 |
+ | glossary | Test glossary name | Test glossary description | C1 | glossary1 |
+ | imscp | Test imscp name | Test imscp description | C1 | imscp1 |
+ | label | Test label name | Test label description | C1 | label1 |
+ | lesson | Test lesson name | Test lesson description | C1 | lesson1 |
+ | lti | Test lti name | Test lti description | C1 | lti1 |
+ | page | Test page name | Test page description | C1 | page1 |
+ | quiz | Test quiz name | Test quiz description | C1 | quiz1 |
+ | resource | Test resource name | Test resource description | C1 | resource1 |
+ | scorm | Test scorm name | Test scorm description | C1 | scorm1 |
+ | survey | Test survey name | Test survey description | C1 | survey1 |
+ | url | Test url name | Test url description | C1 | url1 |
+ | wiki | Test wiki name | Test wiki description | C1 | wiki1 |
+ | workshop | Test workshop name | Test workshop description | C1 | workshop1 |
+
+ When I follow "Courses"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Activities" block
+ And I click on "Assignments" "link" in the "Activities" "block"
+ Then I should see "Test assignment name"
+ And I follow "Course 1"
+ And I click on "Assignments (2.2)" "link" in the "Activities" "block"
+ And I should see "Test assignment22 name"
+ And I follow "Course 1"
+ And I click on "Chats" "link" in the "Activities" "block"
+ And I should see "Test chat name"
+ And I follow "Course 1"
+ And I click on "Choices" "link" in the "Activities" "block"
+ And I should see "Test choice name"
+ And I follow "Course 1"
+ And I click on "Databases" "link" in the "Activities" "block"
+ And I should see "Test database name"
+ And I follow "Course 1"
+ And I click on "Feedback" "link" in the "Activities" "block"
+ And I should see "Test feedback name"
+ And I follow "Course 1"
+ And I click on "Forums" "link" in the "Activities" "block"
+ And I should see "Test forum name"
+ And I follow "Course 1"
+ And I click on "External Tools" "link" in the "Activities" "block"
+ And I should see "Test lti name"
+ And I follow "Course 1"
+ And I click on "Quizzes" "link" in the "Activities" "block"
+ And I should see "Test quiz name"
+ And I follow "Course 1"
+ And I click on "Glossaries" "link" in the "Activities" "block"
+ And I should see "Test glossary name"
+ And I follow "Course 1"
+ And I click on "SCORM packages" "link" in the "Activities" "block"
+ And I should see "Test scorm name"
+ And I follow "Course 1"
+ And I click on "Lessons" "link" in the "Activities" "block"
+ And I should see "Test lesson name"
+ And I follow "Course 1"
+ And I click on "Wikis" "link" in the "Activities" "block"
+ And I should see "Test wiki name"
+ And I follow "Course 1"
+ And I click on "Workshop" "link" in the "Activities" "block"
+ And I should see "Test workshop name"
+ And I follow "Course 1"
+ And I click on "Resources" "link" in the "Activities" "block"
+ And I should see "Test book name"
+ And I should see "Test page name"
+ And I should see "Test resource name"
+ And I should see "Test imscp name"
+ And I should see "Test folder name"
+ And I should see "Test url name"
$this->content = new stdClass();
$this->content->footer = '';
+ $this->content->text = '';
$context = $this->page->context;
$this->find_link(get_string('savecomment'))->click();
- // Wait for the AJAX request.
- $this->getSession()->wait(4 * 1000, false);
-
} else {
$commentstextarea = $this->find('css', '.block_comments form textarea', $exception);
$deleteicon = $this->find('css', '.comment-delete a img', $deleteexception, $commentnode);
$deleteicon->click();
- // Wait for the AJAX request.
+ // Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case.
$this->getSession()->wait(4 * 1000, false);
}
$this->content->items[] = get_string('remotecourses','mnet');
$this->content->icons[] = '';
foreach ($courses as $course) {
- $coursecontext = context_course::instance($course->id);
- $this->content->items[]="<a title=\"" . format_string($course->shortname, true, array('context' => $coursecontext)) . "\" ".
+ $this->content->items[]="<a title=\"" . format_string($course->shortname, true) . "\" ".
"href=\"{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&wantsurl=/course/view.php?id={$course->remoteid}\">"
.$icon. format_string(get_course_display_name_for_list($course)) . "</a>";
}
And I should see "cat3" in the "Navigation" "block"
And I should not see "cat2" in the "Navigation" "block"
And I expand "cat3" node
- And I wait "2" seconds
And I should see "cat31" in the "Navigation" "block"
And I should see "cat33" in the "Navigation" "block"
And I should not see "cat32" in the "Navigation" "block"
And I expand "cat31" node
- And I wait "2" seconds
And I should see "c31" in the "Navigation" "block"
And I expand "cat33" node
- And I wait "2" seconds
And I should see "c331" in the "Navigation" "block"
And I should not see "c332" in the "Navigation" "block"
instance : {
value : false,
setter : function(val) {
- return parseInt(val);
+ return parseInt(val, 10);
}
}
}
} else {
e.stopPropagation();
}
- if (e.type === 'actionkey' && e.action === 'enter' && e.target.test('A')) {
+ if ((e.type === 'actionkey' && e.action === 'enter') || e.target.test('a')) {
// No ajaxLoad for enter.
this.node.setAttribute('data-expandable', '0');
this.node.setAttribute('data-loaded', '1');
this.node.setAttribute('data-loaded', '1');
try {
var object = Y.JSON.parse(outcome.responseText);
+ if (object.error) {
+ Y.use('moodle-core-notification-ajaxException', function () {
+ return new M.core.ajaxException(object).show();
+ });
+ return false;
+ }
if (object.children && object.children.length > 0) {
var coursecount = 0;
for (var i in object.children) {
return true;
}
Y.log('AJAX loading complete but there were no children.', 'note', 'moodle-block_navigation');
- } catch (ex) {
- // If we got here then there was an error parsing the result.
- Y.log('Error parsing AJAX response or adding branches to the navigation tree', 'error', 'moodle-block_navigation');
+ } catch (error) {
+ if (outcome && outcome.status && outcome.status > 0) {
+ // If we got here then there was an error parsing the result.
+ Y.log('Error parsing AJAX response or adding branches to the navigation tree', 'error', 'moodle-block_navigation');
+ Y.use('moodle-core-notification-exception', function () {
+ return new M.core.exception(error).show();
+ });
+ }
+
+ return false;
}
// The branch is empty so class it accordingly
this.node.replaceClass('branch', 'emptybranch');
mtrace(' ' . $rec->url . ' ', '');
// Fetch the rss feed, using standard simplepie caching
// so feeds will be renewed only if cache has expired
- @set_time_limit(60);
+ core_php_time_limit::raise(60);
$feed = new moodle_simplepie();
// set timeout for longer than normal to be agressive at
}
if (!$ismoving) {
$actions = course_get_cm_edit_actions($mod, -1);
+
+ // Add the action move.
+ $modcontext = context_module::instance($mod->id);
+ $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
+ if ($hasmanageactivities) {
+ $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
+ $actions['move'] = new action_menu_link_primary(
+ new moodle_url($baseurl, array('copy' => $mod->id)),
+ new pix_icon('t/move', get_string('move'), 'moodle', array('class' => 'iconsmall', 'title' => '')),
+ null,
+ array('title' => get_string('move'))
+ );
+ }
+
$editbuttons = html_writer::tag('div',
$courserenderer->course_section_cm_edit_actions($actions, $mod, array('donotenhance' => true)),
array('class' => 'buttons')
}
$this->content = new stdClass;
+ $this->content->text = '';
$this->content->footer = '';
// Get a list of tags.
return $steps;
}
+ /**
+ * Docks a block. Editing mode should be previously enabled.
+ *
+ * @Given /^I dock "(?P<block_name_string>(?:[^"]|\\")*)" block$/
+ * @param string $blockname
+ * @return Given
+ */
+ public function i_dock_block($blockname) {
+
+ // Looking for both title and alt.
+ $xpath = "//input[@type='image'][@title='" . get_string('dockblock', 'block', $blockname) . "' or @alt='" . get_string('addtodock', 'block') . "']";
+ return new Given('I click on " ' . $xpath . '" "xpath_element" in the "' . $this->escape($blockname) . '" "block"');
+ }
+
/**
* Opens a block's actions menu if it is not already opened.
*
$bloglisting->print_entries();
echo $OUTPUT->footer();
-
-add_to_log($courseid, 'blog', 'view', 'index.php?entryid='.$entryid.'&tagid='.@$tagid.'&tag='.$tag, 'view blog entry');
+$eventparams = array(
+ 'other' => array('entryid' => $entryid, 'tagid' => $tagid, 'userid' => $userid, 'modid' => $modid, 'groupid' => $groupid,
+ 'search' => $search, 'fromstart' => $start)
+);
+if (!empty($userid)) {
+ $eventparams['relateduserid'] = $userid;
+}
+$eventparams['other']['courseid'] = ($courseid === SITEID) ? 0 : $courseid;
+$event = \core\event\blog_entries_viewed::create($eventparams);
+$event->trigger();
}
/**
- * function to add all context associations to an entry
- * @param int entry - data object processed to include all 'entry' fields and extra data from the edit_form object
+ * Function to add all context associations to an entry.
+ * TODO : Remove $action in 2.9 (MDL-41330)
+ *
+ * @param string $action - This does nothing, do not use it. This is present only for Backward compatibility.
*/
- public function add_associations($action='add') {
- global $DB, $USER;
+ public function add_associations($action = null) {
+
+ if (!empty($action)) {
+ debugging('blog_entry->add_associations() does not accept any argument', DEBUG_DEVELOPER);
+ }
$this->remove_associations();
if (!empty($this->courseassoc)) {
- $this->add_association($this->courseassoc, $action);
+ $this->add_association($this->courseassoc);
}
if (!empty($this->modassoc)) {
- $this->add_association($this->modassoc, $action);
+ $this->add_association($this->modassoc);
}
}
/**
- * add a single association for a blog entry
- * @param int contextid - id of context to associate with the blog entry
+ * Add a single association for a blog entry
+ * TODO : Remove $action in 2.9 (MDL-41330)
+ *
+ * @param int $contextid - id of context to associate with the blog entry.
+ * @param string $action - This does nothing, do not use it. This is present only for Backward compatibility.
*/
- public function add_association($contextid, $action='add') {
- global $DB, $USER;
+ public function add_association($contextid, $action = null) {
+ global $DB;
+
+ if (!empty($action)) {
+ debugging('blog_entry->add_association() accepts only one argument', DEBUG_DEVELOPER);
+ }
$assocobject = new StdClass;
$assocobject->contextid = $contextid;
$assocobject->blogid = $this->id;
- $DB->insert_record('blog_association', $assocobject);
+ $id = $DB->insert_record('blog_association', $assocobject);
+ // Trigger an association created event.
$context = context::instance_by_id($contextid);
- $courseid = null;
-
+ $eventparam = array(
+ 'objectid' => $id,
+ 'other' => array('associateid' => $context->instanceid, 'subject' => $this->subject, 'blogid' => $this->id),
+ 'relateduserid' => $this->userid
+ );
if ($context->contextlevel == CONTEXT_COURSE) {
- $courseid = $context->instanceid;
- add_to_log($courseid, 'blog', $action, 'index.php?userid='.$this->userid.'&entryid='.$this->id, $this->subject);
+ $eventparam['other']['associatetype'] = 'course';
+
} else if ($context->contextlevel == CONTEXT_MODULE) {
- $cm = $DB->get_record('course_modules', array('id' => $context->instanceid));
- $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module));
- add_to_log($cm->course, 'blog', $action, 'index.php?userid='.$this->userid.'&entryid='.$this->id, $this->subject, $cm->id, $this->userid);
+ $eventparam['other']['associatetype'] = 'coursemodule';
}
+ $event = \core\event\blog_association_created::create($eventparam);
+ $event->trigger();
}
/**
And I follow "Comments (0)"
When I fill in "content" with "$My own >nasty< \"string\"!"
And I follow "Save comment"
- And I wait "4" seconds
Then I should see "$My own >nasty< \"string\"!"
And I fill in "content" with "Another $Nasty <string?>"
And I follow "Save comment"
- And I wait "4" seconds
And I should see "Comments (2)" in the ".comment-link" "css_element"
@javascript
And I follow "Comments (0)"
And I fill in "content" with "$My own >nasty< \"string\"!"
And I follow "Save comment"
- And I wait "4" seconds
When I click on ".comment-delete a" "css_element"
+ # Waiting for the animation to finish.
And I wait "4" seconds
Then I should not see "$My own >nasty< \"string\"!"
And I follow "Blog post from user 1"
When I follow "Comments (0)"
And I fill in "content" with "$My own >nasty< \"string\"!"
And I follow "Save comment"
- And I wait "4" seconds
Then I should see "$My own >nasty< \"string\"!"
$this->assertEventLegacyLogData($arr, $event);
$this->assertEventLegacyData($blog, $event);
}
+
+
+ /**
+ * Tests for event blog_association_created.
+ */
+ public function test_blog_association_created_event() {
+ global $USER;
+
+ $this->setAdminUser();
+ $this->resetAfterTest();
+ $sitecontext = context_system::instance();
+ $coursecontext = context_course::instance($this->courseid);
+ $contextmodule = context_module::instance($this->cmid);
+
+ // Add blog associations with a course.
+ $blog = new blog_entry($this->postid);
+ $sink = $this->redirectEvents();
+ $blog->add_association($coursecontext->id);
+ $events = $sink->get_events();
+ $event = reset($events);
+ $sink->close();
+
+ // Validate event data.
+ $this->assertInstanceOf('\core\event\blog_association_created', $event);
+ $this->assertEquals($sitecontext->id, $event->contextid);
+ $this->assertEquals($blog->id, $event->other['blogid']);
+ $this->assertEquals($this->courseid, $event->other['associateid']);
+ $this->assertEquals('course', $event->other['associatetype']);
+ $this->assertEquals($blog->subject, $event->other['subject']);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals($this->userid, $event->relateduserid);
+ $this->assertEquals('blog_association', $event->objecttable);
+ $arr = array(SITEID, 'blog', 'add association', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id,
+ $blog->subject, 0, $this->userid);
+ $this->assertEventLegacyLogData($arr, $event);
+
+ // Add blog associations with a module.
+ $blog = new blog_entry($this->postid);
+ $sink = $this->redirectEvents();
+ $blog->add_association($contextmodule->id);
+ $events = $sink->get_events();
+ $event = reset($events);
+ $sink->close();
+
+ // Validate event data.
+ $this->assertEquals($blog->id, $event->other['blogid']);
+ $this->assertEquals($this->cmid, $event->other['associateid']);
+ $this->assertEquals('coursemodule', $event->other['associatetype']);
+ $arr = array(SITEID, 'blog', 'add association', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id,
+ $blog->subject, $this->cmid, $this->userid);
+ $this->assertEventLegacyLogData($arr, $event);
+ }
+
+ /**
+ * Tests for event blog_association_created validations.
+ */
+ public function test_blog_association_created_event_validations() {
+
+ $this->resetAfterTest();
+
+ // Make sure associatetype validations work.
+ try {
+ \core\event\blog_association_created::create(array(
+ 'contextid' => 1,
+ 'objectid' => 3,
+ 'other' => array('associateid' => 2 , 'blogid' => 3, 'subject' => 'blog subject')));
+ } catch (coding_exception $e) {
+ $this->assertContains('Invalid associatetype', $e->getMessage());
+ }
+ try {
+ \core\event\blog_association_created::create(array(
+ 'contextid' => 1,
+ 'objectid' => 3,
+ 'other' => array('associateid' => 2 , 'blogid' => 3, 'associatetype' => 'random', 'subject' => 'blog subject')));
+ } catch (coding_exception $e) {
+ $this->assertContains('Invalid associatetype', $e->getMessage());
+ }
+ // Make sure associateid validations work.
+ try {
+ \core\event\blog_association_created::create(array(
+ 'contextid' => 1,
+ 'objectid' => 3,
+ 'other' => array('blogid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject')));
+ } catch (coding_exception $e) {
+ $this->assertContains('Associate id must be set', $e->getMessage());
+ }
+ // Make sure blogid validations work.
+ try {
+ \core\event\blog_association_created::create(array(
+ 'contextid' => 1,
+ 'objectid' => 3,
+ 'other' => array('associateid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject')));
+ } catch (coding_exception $e) {
+ $this->assertContains('Blog id must be set', $e->getMessage());
+ }
+ // Make sure blogid validations work.
+ try {
+ \core\event\blog_association_created::create(array(
+ 'contextid' => 1,
+ 'objectid' => 3,
+ 'other' => array('blogid' => 3, 'associateid' => 3, 'associatetype' => 'course')));
+ } catch (coding_exception $e) {
+ $this->assertContains('Subject must be set', $e->getMessage());
+ }
+ }
+
+ /**
+ * Tests for event blog_entries_viewed.
+ */
+ public function test_blog_entries_viewed_event() {
+
+ $this->setAdminUser();
+ $this->resetAfterTest();
+ $other = array('entryid' => $this->postid, 'tagid' => $this->tagid, 'userid' => $this->userid, 'modid' => $this->cmid,
+ 'groupid' => $this->groupid, 'courseid' => $this->courseid, 'search' => 'search', 'fromstart' => 2);
+
+ // Trigger event.
+ $sink = $this->redirectEvents();
+ $eventparams = array('other' => $other);
+ $eventinst = \core\event\blog_entries_viewed::create($eventparams);
+ $eventinst->trigger();
+ $events = $sink->get_events();
+ $event = reset($events);
+ $sink->close();
+
+ // Validate event data.
+ $url = new moodle_url('/blog/index.php', $other);
+ $url2 = new moodle_url('index.php', $other);
+ $this->assertEquals($url, $event->get_url());
+ $arr = array(SITEID, 'blog', 'view', $url2->out(), 'view blog entry');
+ $this->assertEventLegacyLogData($arr, $event);
+ }
}
--- /dev/null
+=== 2.7 ===
+
+* blog_entry->add_association() does not accept any params.
+* blog_entry->add_associations() accepts only one param.
\ No newline at end of file
$PAGE->set_title($title);
$PAGE->set_heading($SITE->fullname);
+/* @var core_cache_renderer $renderer */
$renderer = $PAGE->get_renderer('core_cache');
echo $renderer->header();
public function has_required_identifiers() {
return (count($this->requireidentifiers) > 0);
}
+
+ /**
+ * Returns the possible sharing options that can be used with this defintion.
+ *
+ * @return int
+ */
+ public function get_sharing_options() {
+ return $this->sharingoptions;
+ }
+
+ /**
+ * Returns the user entered sharing key for this definition.
+ *
+ * @return string
+ */
+ public function get_user_input_sharing_key() {
+ return $this->userinputsharingkey;
+ }
+
+ /**
+ * Returns the user selected sharing option for this definition.
+ *
+ * @return int
+ */
+ public function get_selected_sharing_option() {
+ return $this->selectedsharingoption;
+ }
}
*/
public function create_cache(cache_definition $definition) {
$class = $definition->get_cache_class();
- if ($this->is_initialising()) {
- // Do nothing we just want the dummy store.
- $stores = array();
- } else {
- $stores = cache_helper::get_cache_stores($definition);
- }
+ $stores = cache_helper::get_stores_suitable_for_definition($definition);
if (count($stores) === 0) {
- // Hmm no stores, better provide a dummy store to mimick functionality. The dev will be none the wiser.
+ // Hmm still no stores, better provide a dummy store to mimic functionality. The dev will be none the wiser.
$stores[] = $this->create_dummy_store($definition);
}
$loader = null;
*
* @param array $stores
* @param cache_definition $definition
- * @return array
+ * @return cache_store[]
*/
protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
$return = array();
/**
* Record a cache hit in the stats for the given store and definition.
*
+ * @internal
* @param string $store
* @param string $definition
+ * @param int $hits The number of hits to record (by default 1)
*/
- public static function record_cache_hit($store, $definition) {
+ public static function record_cache_hit($store, $definition, $hits = 1) {
self::ensure_ready_for_stats($store, $definition);
- self::$stats[$definition][$store]['hits']++;
+ self::$stats[$definition][$store]['hits'] += $hits;
}
/**
* Record a cache miss in the stats for the given store and definition.
*
+ * @internal
* @param string $store
* @param string $definition
+ * @param int $misses The number of misses to record (by default 1)
*/
- public static function record_cache_miss($store, $definition) {
+ public static function record_cache_miss($store, $definition, $misses = 1) {
self::ensure_ready_for_stats($store, $definition);
- self::$stats[$definition][$store]['misses']++;
+ self::$stats[$definition][$store]['misses'] += $misses;
}
/**
* Record a cache set in the stats for the given store and definition.
*
+ * @internal
* @param string $store
* @param string $definition
+ * @param int $sets The number of sets to record (by default 1)
*/
- public static function record_cache_set($store, $definition) {
+ public static function record_cache_set($store, $definition, $sets = 1) {
self::ensure_ready_for_stats($store, $definition);
- self::$stats[$definition][$store]['sets']++;
+ self::$stats[$definition][$store]['sets'] += $sets;
}
/**
}
}
}
+
+ /**
+ * Returns an array of stores that would meet the requirements for every definition.
+ *
+ * These stores would be 100% suitable to map as defaults for cache modes.
+ *
+ * @return array[] An array of stores, keys are the store names.
+ */
+ public static function get_stores_suitable_for_mode_default() {
+ $factory = cache_factory::instance();
+ $config = $factory->create_config_instance();
+ $requirements = 0;
+ foreach ($config->get_definitions() as $definition) {
+ $definition = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
+ $requirements = $requirements | $definition->get_requirements_bin();
+ }
+ $stores = array();
+ foreach ($config->get_all_stores() as $name => $store) {
+ if (!empty($store['features']) && ($store['features'] & $requirements)) {
+ $stores[$name] = $store;
+ }
+ }
+ return $stores;
+ }
+
+ /**
+ * Returns stores suitable for use with a given definition.
+ *
+ * @param cache_definition $definition
+ * @return cache_store[]
+ */
+ public static function get_stores_suitable_for_definition(cache_definition $definition) {
+ $factory = cache_factory::instance();
+ $stores = array();
+ if ($factory->is_initialising() || $factory->stores_disabled()) {
+ // No suitable stores here.
+ return $stores;
+ } else {
+ $stores = self::get_cache_stores($definition);
+ if (count($stores) === 0) {
+ // No suitable stores we found for the definition. We need to come up with a sensible default.
+ // If this has happened we can be sure that the user has mapped custom stores to either the
+ // mode of the definition. The first alternative to try is the system default for the mode.
+ // e.g. the default file store instance for application definitions.
+ $config = $factory->create_config_instance();
+ foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
+ if (!empty($details['default'])) {
+ $stores[] = $factory->create_store_from_config($name, $details, $definition);
+ break;
+ }
+ }
+ }
+ }
+ return $stores;
+ }
}
}
}
+ if ($this->perfdebug) {
+ $hits = 0;
+ $misses = 0;
+ foreach ($fullresult as $value) {
+ if ($value === false) {
+ $misses++;
+ } else {
+ $hits++;
+ }
+ }
+ cache_helper::record_cache_hit($this->storetype, $this->definition->get_id(), $hits);
+ cache_helper::record_cache_miss($this->storetype, $this->definition->get_id(), $misses);
+ }
+
// Return the result. Phew!
return $fullresult;
}
$this->static_acceleration_set($data[$key]['key'], $value);
}
}
- if ($this->perfdebug) {
- cache_helper::record_cache_set($this->storetype, $this->definition->get_id());
+ $successfullyset = $this->store->set_many($data);
+ if ($this->perfdebug && $successfullyset) {
+ cache_helper::record_cache_set($this->storetype, $this->definition->get_id(), $successfullyset);
}
- return $this->store->set_many($data);
+ return $successfullyset;
}
/**
if ($hasmissingkeys && $strictness === MUST_EXIST) {
throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
}
-
+ if ($this->perfdebug) {
+ $hits = 0;
+ $misses = 0;
+ foreach ($return as $value) {
+ if ($value === false) {
+ $misses++;
+ } else {
+ $hits++;
+ }
+ }
+ cache_helper::record_cache_hit($this->storetype, $this->get_definition()->get_id(), $hits);
+ cache_helper::record_cache_miss($this->storetype, $this->get_definition()->get_id(), $misses);
+ }
return $return;
}
'value' => $value
);
}
- if ($this->perfdebug) {
- cache_helper::record_cache_set($this->storetype, $definitionid);
+ $successfullyset = $this->get_store()->set_many($data);
+ if ($this->perfdebug && $successfullyset) {
+ cache_helper::record_cache_set($this->storetype, $definitionid, $successfullyset);
}
- return $this->get_store()->set_many($data);
+ return $successfullyset;
}
/**
* @return array
*/
public static function get_definition_summaries() {
- $instance = cache_config::instance();
- $definitions = $instance->get_definitions();
-
+ $factory = cache_factory::instance();
+ $config = $factory->create_config_instance();
$storenames = array();
- foreach ($instance->get_all_stores() as $key => $store) {
+ foreach ($config->get_all_stores() as $key => $store) {
if (!empty($store['default'])) {
$storenames[$key] = new lang_string('store_'.$key, 'cache');
- }
- }
-
- $modemappings = array();
- foreach ($instance->get_mode_mappings() as $mapping) {
- $mode = $mapping['mode'];
- if (!array_key_exists($mode, $modemappings)) {
- $modemappings[$mode] = array();
- }
- if (array_key_exists($mapping['store'], $storenames)) {
- $modemappings[$mode][] = $storenames[$mapping['store']];
} else {
- $modemappings[$mode][] = $mapping['store'];
+ $storenames[$store['name']] = $store['name'];
}
}
-
- $definitionmappings = array();
- foreach ($instance->get_definition_mappings() as $mapping) {
- $definition = $mapping['definition'];
- if (!array_key_exists($definition, $definitionmappings)) {
- $definitionmappings[$definition] = array();
- }
- if (array_key_exists($mapping['store'], $storenames)) {
- $definitionmappings[$definition][] = $storenames[$mapping['store']];
- } else {
- $definitionmappings[$definition][] = $mapping['store'];
- }
+ /* @var cache_definition[] $definitions */
+ $definitions = array();
+ foreach ($config->get_definitions() as $key => $definition) {
+ $definitions[$key] = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
}
-
- $return = array();
-
foreach ($definitions as $id => $definition) {
-
$mappings = array();
- if (array_key_exists($id, $definitionmappings)) {
- $mappings = $definitionmappings[$id];
- } else if (empty($definition['mappingsonly'])) {
- $mappings = $modemappings[$definition['mode']];
+ foreach (cache_helper::get_stores_suitable_for_definition($definition) as $store) {
+ $mappings[] = $storenames[$store->my_name()];
}
-
$return[$id] = array(
'id' => $id,
- 'name' => cache_helper::get_definition_name($definition),
- 'mode' => $definition['mode'],
- 'component' => $definition['component'],
- 'area' => $definition['area'],
+ 'name' => $definition->get_name(),
+ 'mode' => $definition->get_mode(),
+ 'component' => $definition->get_component(),
+ 'area' => $definition->get_area(),
'mappings' => $mappings,
- 'sharingoptions' => self::get_definition_sharing_options($definition['sharingoptions'], false),
- 'selectedsharingoption' => self::get_definition_sharing_options($definition['selectedsharingoption'], true),
- 'userinputsharingkey' => $definition['userinputsharingkey']
+ 'sharingoptions' => self::get_definition_sharing_options($definition->get_sharing_options(), false),
+ 'selectedsharingoption' => self::get_definition_sharing_options($definition->get_selected_sharing_option(), true),
+ 'userinputsharingkey' => $definition->get_user_input_sharing_key()
);
}
return $return;
* @return array An array containing sub-arrays, one for each mode.
*/
public static function get_default_mode_stores() {
+ global $OUTPUT;
$instance = cache_config::instance();
+ $adequatestores = cache_helper::get_stores_suitable_for_mode_default();
+ $icon = new pix_icon('i/warning', new lang_string('inadequatestoreformapping', 'cache'));
$storenames = array();
foreach ($instance->get_all_stores() as $key => $store) {
if (!empty($store['default'])) {
} else {
$modemappings[$mode][$mapping['store']] = $mapping['store'];
}
+ if (!array_key_exists($mapping['store'], $adequatestores)) {
+ $modemappings[$mode][$mapping['store']] = $modemappings[$mode][$mapping['store']].' '.$OUTPUT->render($icon);
+ }
}
return $modemappings;
}
$updatecount = 0;
// Large calendars take a while...
- set_time_limit(300);
+ core_php_time_limit::raise(300);
// Mark all events in a subscription with a zero timestamp.
if (!empty($subscriptionid)) {
$userid = $DB->get_field('user', 'id', array('username' => $username));
$steps = array(
- new Given('I click on "' . get_string('assign', 'cohort') . '" "link" in the "' . $this->escape($cohortidnumber) . '" table row'),
+ new Given('I click on "' . get_string('assign', 'cohort') . '" "link" in the "' . $this->escape($cohortidnumber) . '" "table_row"'),
new Given('I select "' . $userid . '" from "' . get_string('potusers', 'cohort') . '"'),
new Given('I press "' . get_string('add') . '"'),
new Given('I press "' . get_string('backtocohorts', 'cohort') . '"')
And I expand "Users" node
And I expand "Accounts" node
When I follow "Upload users"
- And I upload "lib/tests/fixtures/upload_users_cohorts.csv" file to "File" filepicker
+ And I upload "lib/tests/fixtures/upload_users_cohorts.csv" file to "File" filemanager
And I press "Upload users"
And I press "Upload users"
And I press "Continue"
And I follow "Cohorts"
- And I click on "Assign" "link" in the "Cohort 1" table row
+ And I click on "Assign" "link" in the "Cohort 1" "table_row"
Then the "Current users" select box should contain "Tom Jones (tomjones@example.com)"
And the "Current users" select box should contain "Bob Jones (bobjones@example.com)"
And I press "Back to cohorts"
- And I click on "Assign" "link" in the "Cohort 2" table row
+ And I click on "Assign" "link" in the "Cohort 2" "table_row"
And the "Current users" select box should contain "Mary Smith (marysmith@example.com)"
And the "Current users" select box should contain "Alice Smith (alicesmith@example.com)"
And I am on homepage
}
$newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat);
+ // Trigger comment created event.
+ $eventclassname = '\\' . $this->component . '\\event\comment_created';
+ if (class_exists($eventclassname)) {
+ $event = $eventclassname::create(
+ array(
+ 'context' => $this->context,
+ 'objectid' => $newcmt->id,
+ 'other' => array(
+ 'itemid' => $this->itemid
+ )
+ ));
+ $event->trigger();
+ }
+
return $newcmt;
} else {
throw new comment_exception('dbupdatefailed');
throw new comment_exception('nopermissiontocomment');
}
$DB->delete_records('comments', array('id'=>$commentid));
+ // Trigger comment delete event.
+ $eventclassname = '\\' . $this->component . '\\event\comment_deleted';
+ if (class_exists($eventclassname)) {
+ $event = $eventclassname::create(
+ array(
+ 'context' => $this->context,
+ 'objectid' => $commentid,
+ 'other' => array(
+ 'itemid' => $this->itemid
+ )
+ ));
+ $event->add_record_snapshot('comments', $comment);
+ $event->trigger();
+ }
return true;
}
And I log in as "student1"
And I follow "Course 1"
And I press "Mark as complete: Test forum name"
- And I wait "3" seconds
And I log out
And I log in as "teacher1"
And I follow "Course 1"