Permalink
Browse files

Merge branch 'MOODLE_23_STABLE' into install_23_STABLE

  • Loading branch information...
2 parents ba1d10c + 71e12dc commit f84f35dda1001ca379c9a99c8429ef2848d016c3 AMOS bot committed Sep 8, 2012
Showing with 1,019 additions and 364 deletions.
  1. +117 −1 admin/environment.xml
  2. +7 −2 backup/moodle2/backup_custom_fields.php
  3. +17 −0 backup/upgrade.txt
  4. +9 −5 backup/util/dbops/backup_plan_dbops.class.php
  5. +11 −1 backup/util/dbops/restore_dbops.class.php
  6. +44 −24 backup/util/helper/backup_cron_helper.class.php
  7. +11 −0 backup/util/plan/backup_structure_step.class.php
  8. +8 −2 backup/util/plan/restore_structure_step.class.php
  9. +73 −0 backup/util/structure/backup_nested_element.class.php
  10. +3 −0 backup/util/ui/backup_ui_stage.class.php
  11. +3 −0 backup/util/ui/restore_ui_stage.class.php
  12. +15 −13 blocks/completionstatus/block_completionstatus.php
  13. +127 −112 blocks/completionstatus/details.php
  14. +1 −0 blocks/completionstatus/lang/en/block_completionstatus.php
  15. +9 −0 blog/external_blogs.php
  16. +2 −3 blog/locallib.php
  17. +1 −1 enrol/manual/yui/quickenrolment/quickenrolment.js
  18. +11 −11 enrol/paypal/ipn.php
  19. +3 −0 filter/mediaplugin/tests/filter_test.php
  20. +2 −1 grade/edit/tree/category_form.php
  21. +1 −1 lang/en/admin.php
  22. +2 −0 lang/en/backup.php
  23. +2 −0 lang/en/block.php
  24. +1 −0 lang/en/moodle.php
  25. +1 −1 lib/accesslib.php
  26. +48 −8 lib/blocklib.php
  27. +2 −2 lib/conditionlib.php
  28. +10 −0 lib/db/upgrade.php
  29. +47 −2 lib/db/upgradelib.php
  30. +11 −13 lib/googleapi.php
  31. +11 −6 lib/medialib.php
  32. +33 −3 lib/moodlelib.php
  33. +1 −1 lib/pluginlib.php
  34. +7 −5 lib/questionlib.php
  35. +60 −0 lib/tests/moodlelib_test.php
  36. +38 −9 lib/tests/pluginlib_test.php
  37. +11 −24 message/lib.php
  38. +1 −1 mod/data/field/checkbox/mod.html
  39. +5 −4 mod/data/field/latlong/field.class.php
  40. +1 −1 mod/data/field/menu/mod.html
  41. +1 −1 mod/data/field/multimenu/mod.html
  42. +2 −2 mod/data/field/picture/field.class.php
  43. +4 −4 mod/data/field/picture/mod.html
  44. +1 −1 mod/data/field/radiobutton/mod.html
  45. +2 −2 mod/data/field/textarea/mod.html
  46. +11 −17 mod/data/lib.php
  47. +32 −0 mod/data/styles.css
  48. +7 −7 mod/data/templates.php
  49. +17 −1 mod/quiz/lib.php
  50. +1 −0 mod/quiz/styles.css
  51. +6 −1 mod/workshop/form/comments/backup/moodle1/lib.php
  52. +4 −1 mod/workshop/form/numerrors/backup/moodle1/lib.php
  53. +4 −1 mod/workshop/form/rubric/backup/moodle1/lib.php
  54. +5 −2 question/category_class.php
  55. +3 −0 question/type/multichoice/styles.css
  56. +10 −3 report/backups/index.php
  57. +2 −2 report/stats/lib.php
  58. +1 −1 report/stats/settings.php
  59. +33 −9 repository/flickr/lib.php
  60. +44 −25 repository/flickr_public/lib.php
  61. +3 −1 repository/googledocs/lib.php
  62. +6 −0 repository/lib.php
  63. +10 −6 repository/manage_instances.php
  64. +5 −1 theme/afterburner/style/afterburner_styles.css
  65. +1 −2 theme/anomaly/style/general.css
  66. +1 −0 theme/base/style/admin.css
  67. +2 −1 theme/yui_combo.php
  68. +30 −13 user/profile.php
  69. +2 −1 user/selector/module.js
  70. +2 −2 version.php
View
118 admin/environment.xml
@@ -659,5 +659,121 @@
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
-</MOODLE>
+ </MOODLE>
+ <MOODLE version="2.4" requires="2.2">
+ <UNICODE level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unicoderequired" />
+ </FEEDBACK>
+ </UNICODE>
+ <DATABASE level="required">
+ <VENDOR name="mysql" version="5.1.33" />
+ <VENDOR name="postgres" version="8.3" />
+ <VENDOR name="mssql" version="9.0" />
+ <VENDOR name="odbc_mssql" version="9.0" />
+ <VENDOR name="mssql_n" version="9.0" />
+ <VENDOR name="oracle" version="10.2" />
+ <VENDOR name="sqlite" version="2.0" />
+ </DATABASE>
+ <PHP version="5.3.2" level="required">
+ </PHP>
+ <PCREUNICODE level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="pcreunicodewarning" />
+ </FEEDBACK>
+ </PCREUNICODE>
+ <PHP_EXTENSIONS>
+ <PHP_EXTENSION name="iconv" level="required">
+ <FEEDBACK>
+ <ON_CHECK 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_CHECK 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="gd" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="gdrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="simplexml" level="required">
+ <FEEDBACK>
+ <ON_CHECK message="simplexmlrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="spl" level="required">
+ <FEEDBACK>
+ <ON_CHECK 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="40M" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="settingmemorylimit" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ <PHP_SETTING name="safe_mode" value="0" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="settingsafemode" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ <PHP_SETTING name="file_uploads" value="1" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="settingfileuploads" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ </PHP_SETTINGS>
+ </MOODLE>
</COMPATIBILITY_MATRIX>
View
9 backup/moodle2/backup_custom_fields.php
@@ -96,14 +96,19 @@ public function process($processor) {
if (is_null($this->backupid)) {
$this->backupid = $processor->get_var(backup::VAR_BACKUPID);
}
- parent::process($processor);
+ return parent::process($processor);
}
public function fill_values($values) {
// Fill values
parent::fill_values($values);
// Do our own tasks (copy file from moodle to backup)
- backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
+ try {
+ 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);
+ }
}
}
View
17 backup/upgrade.txt
@@ -0,0 +1,17 @@
+This files describes API changes in /backup/*,
+information provided here is intended especially for developers.
+
+=== 2.4 ===
+
+* Since 2.3.1+ the backup file name schema has changed. The ID of the course will always be part of
+ the filename regardless of the setting 'backup_shortname'. See MDL-33812.
+
+=== 2.3 ===
+
+* Since 2.3.1+ the backup file name schema has changed. The ID of the course will always be part of
+ the filename regardless of the setting 'backup_shortname'. See MDL-33812.
+
+=== 2.2 ===
+
+* Since 2.2.4+ the backup file name schema has changed. The ID of the course will always be part of
+ the filename regardless of the setting 'backup_shortname'. See MDL-33812.
View
14 backup/util/dbops/backup_plan_dbops.class.php
@@ -197,19 +197,19 @@ public static function get_mnet_localhost_wwwroot() {
* @param int $courseid/$sectionid/$cmid
* @param bool $users Should be true is users were included in the backup
* @param bool $anonymised Should be true is user information was anonymized.
- * @param bool $useidasname true to use id, false to use strings (default)
+ * @param bool $useidonly only use the ID in the file name
* @return string The filename to use
*/
- public static function get_default_backup_filename($format, $type, $id, $users, $anonymised, $useidasname = false) {
+ public static function get_default_backup_filename($format, $type, $id, $users, $anonymised, $useidonly = false) {
global $DB;
// Calculate backup word
$backupword = str_replace(' ', '_', textlib::strtolower(get_string('backupfilename')));
$backupword = trim(clean_filename($backupword), '_');
+ // Not $useidonly, lets fetch the name
$shortname = '';
- // Not $useidasname, lets calculate it, else $id will be used
- if (!$useidasname) {
+ if (!$useidonly) {
// Calculate proper name element (based on type)
switch ($type) {
case backup::TYPE_1COURSE:
@@ -231,7 +231,11 @@ public static function get_default_backup_filename($format, $type, $id, $users,
$shortname = textlib::strtolower(trim(clean_filename($shortname), '_'));
}
- $name = empty($shortname) ? $id : $shortname;
+ // The name will always contain the ID, but we append the course short name if requested.
+ $name = $id;
+ if (!$useidonly && $shortname != '') {
+ $name .= '-' . $shortname;
+ }
// Calculate date
$backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
View
12 backup/util/dbops/restore_dbops.class.php
@@ -819,10 +819,13 @@ public static function restore_get_questions($restoreid, $qcatid) {
* @param int|null $olditemid
* @param int|null $forcenewcontextid explicit value for the new contextid (skip mapping)
* @param bool $skipparentitemidctxmatch
+ * @return array of result object
*/
public static function send_files_to_pool($basepath, $restoreid, $component, $filearea, $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null, $forcenewcontextid = null, $skipparentitemidctxmatch = false) {
global $DB;
+ $results = array();
+
if ($forcenewcontextid) {
// Some components can have "forced" new contexts (example: questions can end belonging to non-standard context mappings,
// with questions originally at system/coursecat context in source being restored to course context in target). So we need
@@ -902,8 +905,14 @@ public static function send_files_to_pool($basepath, $restoreid, $component, $fi
// this is a regular file, it must be present in the backup pool
$backuppath = $basepath . backup_file_manager::get_backup_content_file_location($file->contenthash);
+ // The file is not found in the backup.
if (!file_exists($backuppath)) {
- throw new restore_dbops_exception('file_not_found_in_pool', $file);
+ $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;
+ continue;
}
// create the file in the filepool if it does not exist yet
@@ -960,6 +969,7 @@ public static function send_files_to_pool($basepath, $restoreid, $component, $fi
}
}
$rs->close();
+ return $results;
}
/**
View
68 backup/util/helper/backup_cron_helper.class.php
@@ -46,6 +46,8 @@
const BACKUP_STATUS_UNFINISHED = 2;
/** Course automated backup was skipped */
const BACKUP_STATUS_SKIPPED = 3;
+ /** Course automated backup had warnings */
+ const BACKUP_STATUS_WARNING = 4;
/** Run if required by the schedule set in config. Default. **/
const RUN_ON_SCHEDULE = 0;
@@ -139,7 +141,7 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
$params = array('courseid' => $course->id, 'time' => $now-31*24*60*60, 'action' => '%view%');
$logexists = $DB->record_exists_select('log', $sqlwhere, $params);
if (!$logexists) {
- $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
+ $backupcourse->laststatus = self::BACKUP_STATUS_SKIPPED;
$backupcourse->nextstarttime = $nextstarttime;
$DB->update_record('backup_courses', $backupcourse);
mtrace('Skipping unchanged course '.$course->fullname);
@@ -160,7 +162,7 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
$starttime = time();
$backupcourse->laststarttime = time();
- $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED;
+ $backupcourse->laststatus = self::BACKUP_STATUS_UNFINISHED;
$DB->update_record('backup_courses', $backupcourse);
$backupcourse->laststatus = backup_cron_automated_helper::launch_automated_backup($course, $backupcourse->laststarttime, $admin->id);
@@ -169,7 +171,7 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
$DB->update_record('backup_courses', $backupcourse);
- if ($backupcourse->laststatus) {
+ if ($backupcourse->laststatus === self::BACKUP_STATUS_OK) {
// Clean up any excess course backups now that we have
// taken a successful backup.
$removedcount = backup_cron_automated_helper::remove_excess_backups($course);
@@ -188,17 +190,18 @@ public static function run_automated_backup($rundirective = self::RUN_ON_SCHEDUL
$message = "";
$count = backup_cron_automated_helper::get_backup_status_array();
- $haserrors = ($count[backup_cron_automated_helper::BACKUP_STATUS_ERROR] != 0 || $count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED] != 0);
+ $haserrors = ($count[self::BACKUP_STATUS_ERROR] != 0 || $count[self::BACKUP_STATUS_UNFINISHED] != 0);
//Build the message text
//Summary
$message .= get_string('summary')."\n";
$message .= "==================================================\n";
$message .= " ".get_string('courses').": ".array_sum($count)."\n";
- $message .= " ".get_string('ok').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_OK]."\n";
- $message .= " ".get_string('skipped').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_SKIPPED]."\n";
- $message .= " ".get_string('error').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_ERROR]."\n";
- $message .= " ".get_string('unfinished').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED]."\n\n";
+ $message .= " ".get_string('ok').": ".$count[self::BACKUP_STATUS_OK]."\n";
+ $message .= " ".get_string('skipped').": ".$count[self::BACKUP_STATUS_SKIPPED]."\n";
+ $message .= " ".get_string('error').": ".$count[self::BACKUP_STATUS_ERROR]."\n";
+ $message .= " ".get_string('unfinished').": ".$count[self::BACKUP_STATUS_UNFINISHED]."\n";
+ $message .= " ".get_string('warning').": ".$count[self::BACKUP_STATUS_WARNING]."\n\n";
//Reference
if ($haserrors) {
@@ -261,6 +264,7 @@ public static function get_backup_status_array() {
self::BACKUP_STATUS_OK => 0,
self::BACKUP_STATUS_UNFINISHED => 0,
self::BACKUP_STATUS_SKIPPED => 0,
+ self::BACKUP_STATUS_WARNING => 0
);
$statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) AS statuscount FROM {backup_courses} bc GROUP BY bc.laststatus');
@@ -334,7 +338,7 @@ public static function calculate_next_automated_backup($timezone, $now) {
*/
public static function launch_automated_backup($course, $starttime, $userid) {
- $outcome = true;
+ $outcome = self::BACKUP_STATUS_OK;
$config = get_config('backup');
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid);
@@ -369,6 +373,7 @@ public static function launch_automated_backup($course, $starttime, $userid) {
$bc->execute_plan();
$results = $bc->get_results();
+ $outcome = self::outcome_from_results($results);
$file = $results['backup_destination']; // may be empty if file already moved to target location
$dir = $config->backup_auto_destination;
$storage = (int)$config->backup_auto_storage;
@@ -377,8 +382,10 @@ public static function launch_automated_backup($course, $starttime, $userid) {
}
if ($file && !empty($dir) && $storage !== 0) {
$filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, !$config->backup_shortname);
- $outcome = $file->copy_content_to($dir.'/'.$filename);
- if ($outcome && $storage === 1) {
+ if (!$file->copy_content_to($dir.'/'.$filename)) {
+ $outcome = self::BACKUP_STATUS_ERROR;
+ }
+ if ($outcome != self::BACKUP_STATUS_ERROR && $storage === 1) {
$file->delete();
}
}
@@ -387,7 +394,7 @@ public static function launch_automated_backup($course, $starttime, $userid) {
$bc->log('backup_auto_failed_on_course', backup::LOG_ERROR, $course->shortname); // Log error header.
$bc->log('Exception: ' . $e->errorcode, backup::LOG_ERROR, $e->a, 1); // Log original exception problem.
$bc->log('Debug: ' . $e->debuginfo, backup::LOG_DEBUG, null, 1); // Log original debug information.
- $outcome = false;
+ $outcome = self::BACKUP_STATUS_ERROR;
}
$bc->destroy();
@@ -397,6 +404,30 @@ public static function launch_automated_backup($course, $starttime, $userid) {
}
/**
+ * Returns the backup outcome by analysing its results.
+ *
+ * @param array $results returned by a backup
+ * @return int {@link self::BACKUP_STATUS_OK} and other constants
+ */
+ public static function outcome_from_results($results) {
+ $outcome = self::BACKUP_STATUS_OK;
+ foreach ($results as $code => $value) {
+ // Each possible error and warning code has to be specified in this switch
+ // which basically analyses the results to return the correct backup status.
+ switch ($code) {
+ case 'missing_files_in_pool':
+ $outcome = self::BACKUP_STATUS_WARNING;
+ break;
+ }
+ // If we found the highest error level, we exit the loop.
+ if ($outcome == self::BACKUP_STATUS_ERROR) {
+ break;
+ }
+ }
+ return $outcome;
+ }
+
+ /**
* Removes deleted courses fromn the backup_courses table so that we don't
* waste time backing them up.
*
@@ -530,18 +561,7 @@ public static function remove_excess_backups($course) {
if (!empty($dir) && ($storage == 1 || $storage == 2)) {
// Calculate backup filename regex, ignoring the date/time/info parts that can be
// variable, depending of languages, formats and automated backup settings
-
-
- // MDL-33531: use different filenames depending on backup_shortname option
- if ( !empty($config->backup_shortname) ) {
- $context = get_context_instance(CONTEXT_COURSE, $course->id);
- $courseref = format_string($course->shortname, true, array('context' => $context));
- $courseref = str_replace(' ', '_', $courseref);
- $courseref = textlib::strtolower(trim(clean_filename($courseref), '_'));
- } else {
- $courseref = $course->id;
- }
- $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$courseref . '-';
+ $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$course->id . '-';
$regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
// Store all the matching files into fullpath => timemodified array
View
11 backup/util/plan/backup_structure_step.class.php
@@ -94,11 +94,22 @@ public function execute() {
// Process structure definition
$structure->process($pr);
+ // Get the results from the nested elements
+ $results = $structure->get_results();
+
+ // Get the log messages to append to the log
+ $logs = $structure->get_logs();
+ foreach ($logs as $log) {
+ $this->log($log->message, $log->level, $log->a, $log->depth, $log->display);
+ }
+
// Close everything
$xw->stop();
// Destroy the structure. It helps PHP 5.2 memory a lot!
$structure->destroy();
+
+ return $results;
}
/**
View
10 backup/util/plan/restore_structure_step.class.php
@@ -218,8 +218,14 @@ public function get_mapping($itemname, $oldid) {
*/
public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
$filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
- restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
- $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid);
+ $results = restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
+ $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid);
+ $resultstoadd = array();
+ foreach ($results as $result) {
+ $this->log($result->message, $result->level);
+ $resultstoadd[$result->code] = true;
+ }
+ $this->task->add_result($resultstoadd);
}
/**
View
73 backup/util/structure/backup_nested_element.class.php
@@ -37,6 +37,8 @@ class backup_nested_element extends base_nested_element implements processable {
protected $aliases; // Define DB->final element aliases
protected $fileannotations; // array of file areas to be searched by file annotations
protected $counter; // Number of instances of this element that have been processed
+ protected $results; // Logs the results we encounter during the process.
+ protected $logs; // Some log messages that could be retrieved later.
/**
* Constructor - instantiates one backup_nested_element, specifying its basic info.
@@ -55,8 +57,16 @@ public function __construct($name, $attributes = null, $final_elements = null) {
$this->aliases = array();
$this->fileannotations = array();
$this->counter = 0;
+ $this->results = array();
+ $this->logs = array();
}
+ /**
+ * Process the nested element
+ *
+ * @param object $processor the processor
+ * @return void
+ */
public function process($processor) {
if (!$processor instanceof base_processor) { // No correct processor, throw exception
throw new base_element_struct_exception('incorrect_processor');
@@ -113,6 +123,69 @@ public function process($processor) {
$iterator->close();
}
+ /**
+ * Saves a log message to an array
+ *
+ * @see backup_helper::log()
+ * @param string $message to add to the logs
+ * @param int $level level of importance {@link backup::LOG_DEBUG} and other constants
+ * @param mixed $a to be included in $message
+ * @param int $depth of the message
+ * @param display $bool supporting translation via get_string() if true
+ * @return void
+ */
+ protected function add_log($message, $level, $a = null, $depth = null, $display = false) {
+ // Adding the result to the oldest parent.
+ if ($this->get_parent()) {
+ $parent = $this->get_grandparent();
+ $parent->add_log($message, $level, $a, $depth, $display);
+ } else {
+ $log = new stdClass();
+ $log->message = $message;
+ $log->level = $level;
+ $log->a = $a;
+ $log->depth = $depth;
+ $log->display = $display;
+ $this->logs[] = $log;
+ }
+ }
+
+ /**
+ * Saves the results to an array
+ *
+ * @param array $result associative array
+ * @return void
+ */
+ protected function add_result($result) {
+ if (is_array($result)) {
+ // Adding the result to the oldest parent.
+ if ($this->get_parent()) {
+ $parent = $this->get_grandparent();
+ $parent->add_result($result);
+ } else {
+ $this->results = array_merge($this->results, $result);
+ }
+ }
+ }
+
+ /**
+ * Returns the logs
+ *
+ * @return array of log objects
+ */
+ public function get_logs() {
+ return $this->logs;
+ }
+
+ /**
+ * Returns the results
+ *
+ * @return associative array of results
+ */
+ public function get_results() {
+ return $this->results;
+ }
+
public function set_source_array($arr) {
// TODO: Only elements having final elements can set source
$this->var_array = $arr;
View
3 backup/util/ui/backup_ui_stage.class.php
@@ -487,6 +487,9 @@ public function display(core_backup_renderer $renderer) {
if (!empty($this->results['include_file_references_to_external_content'])) {
$output .= $renderer->notification(get_string('filereferencesincluded', 'backup'), 'notifyproblem');
}
+ if (!empty($this->results['missing_files_in_pool'])) {
+ $output .= $renderer->notification(get_string('missingfilesinpool', 'backup'), 'notifyproblem');
+ }
$output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess');
$output .= $renderer->continue_button($restorerul);
$output .= $renderer->box_end();
View
3 backup/util/ui/restore_ui_stage.class.php
@@ -772,6 +772,9 @@ public function display(core_backup_renderer $renderer) {
$html .= $renderer->box_end();
}
$html .= $renderer->box_start();
+ if (array_key_exists('file_missing_in_backup', $this->results)) {
+ $html .= $renderer->notification(get_string('restorefileweremissing', 'backup'), 'notifyproblem');
+ }
$html .= $renderer->notification(get_string('restoreexecutionsuccess', 'backup'), 'notifysuccess');
$html .= $renderer->continue_button(new moodle_url('/course/view.php', array(
'id' => $this->get_ui()->get_controller()->get_courseid())), 'get');
View
28 blocks/completionstatus/block_completionstatus.php
@@ -19,15 +19,14 @@
*
* @package block
* @subpackage completion
- * @copyright 2009 Catalyst IT Ltd
+ * @copyright 2009-2012 Catalyst IT Ltd
* @author Aaron Barnes <aaronb@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir.'/completionlib.php');
+require_once("{$CFG->libdir}/completionlib.php");
/**
* Course completion status
@@ -36,25 +35,27 @@
class block_completionstatus extends block_base {
public function init() {
- $this->title = get_string('pluginname', 'block_completionstatus');
+ $this->title = get_string('pluginname', 'block_completionstatus');
}
public function get_content() {
- global $USER, $CFG, $DB, $COURSE;
+ global $USER;
// If content is cached
if ($this->content !== NULL) {
return $this->content;
}
+ $course = $this->page->course;
+
// Create empty content
- $this->content = new stdClass;
+ $this->content = new stdClass();
// Can edit settings?
- $can_edit = has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $this->page->course->id));
+ $can_edit = has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id));
// Get course completion data
- $info = new completion_info($this->page->course);
+ $info = new completion_info($course);
// Don't display if completion isn't enabled!
if (!completion_info::is_enabled_for_site()) {
@@ -84,9 +85,9 @@ public function get_content() {
// Check this user is enroled
if (!$info->is_tracked_user($USER->id)) {
// If not enrolled, but are can view the report:
- if (has_capability('report/completion:view', get_context_instance(CONTEXT_COURSE, $COURSE->id))) {
- $this->content->text = '<a href="'.$CFG->wwwroot.'/report/completion/index.php?course='.$COURSE->id.
- '">'.get_string('viewcoursereport', 'completion').'</a>';
+ if (has_capability('report/completion:view', get_context_instance(CONTEXT_COURSE, $course->id))) {
+ $report = new moodle_url('/report/completion/index.php', array('course' => $course->id));
+ $this->content->text = '<a href="'.$report->out().'">'.get_string('viewcoursereport', 'completion').'</a>';
return $this->content;
}
@@ -187,7 +188,7 @@ public function get_content() {
// Load course completion
$params = array(
'userid' => $USER->id,
- 'course' => $COURSE->id
+ 'course' => $course->id
);
$ccompletion = new completion_completion($params);
@@ -221,7 +222,8 @@ public function get_content() {
$this->content->text .= $shtml.'</tbody></table>';
// Display link to detailed view
- $this->content->footer = '<br><a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$COURSE->id.'">'.get_string('moredetails', 'completion').'</a>';
+ $details = new moodle_url('/blocks/completionstatus/details.php', array('course' => $course->id));
+ $this->content->footer = '<br><a href="'.$details->out().'">'.get_string('moredetails', 'completion').'</a>';
return $this->content;
}
View
239 blocks/completionstatus/details.php
@@ -19,27 +19,23 @@
*
* @package block
* @subpackage completion
- * @copyright 2009 Catalyst IT Ltd
+ * @copyright 2009-2012 Catalyst IT Ltd
* @author Aaron Barnes <aaronb@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-require_once('../../config.php');
-require_once($CFG->libdir.'/completionlib.php');
-
-
-// TODO: Make this page Moodle 2.0 compliant
+require_once(dirname(__FILE__).'/../../config.php');
+require_once("{$CFG->libdir}/completionlib.php");
///
/// Load data
///
$id = required_param('course', PARAM_INT);
-// User id
$userid = optional_param('user', 0, PARAM_INT);
// Load course
-$course = $DB->get_record('course', array('id' => $id));
+$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
// Load user
if ($userid) {
@@ -76,21 +72,13 @@
// Load completion data
$info = new completion_info($course);
-$returnurl = "{$CFG->wwwroot}/course/view.php?id={$id}";
+$returnurl = new moodle_url('/course/view.php', array('id' => $id));
// Don't display if completion isn't enabled!
if (!$info->is_enabled()) {
print_error('completionnotenabled', 'completion', $returnurl);
}
-// Load criteria to display
-$completions = $info->get_completions($user->id);
-
-// Check if this course has any criteria
-if (empty($completions)) {
- print_error('nocriteriaset', 'completion', $returnurl);
-}
-
// Check this user is enroled
if (!$info->is_tracked_user($user->id)) {
if ($USER->id == $user->id) {
@@ -104,14 +92,15 @@
///
/// Display page
///
+$PAGE->set_context(context_course::instance($course->id));
// Print header
$page = get_string('completionprogressdetails', 'block_completionstatus');
$title = format_string($course->fullname) . ': ' . $page;
$PAGE->navbar->add($page);
$PAGE->set_pagelayout('standard');
-$PAGE->set_url('/blocks/completionstatus/details.php', array('course' => $course->id));
+$PAGE->set_url('/blocks/completionstatus/details.php', array('course' => $course->id, 'user' => $user->id));
$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
$PAGE->set_heading($title);
echo $OUTPUT->header();
@@ -135,122 +124,148 @@
// Has this user completed any criteria?
$criteriacomplete = $info->count_course_user_data($user->id);
+// Load course completion
+$params = array(
+ 'userid' => $user->id,
+ 'course' => $course->id,
+);
+$ccompletion = new completion_completion($params);
+
if ($coursecomplete) {
echo get_string('complete');
-} else if (!$criteriacomplete) {
+} else if (!$criteriacomplete && !$ccompletion->timestarted) {
echo '<i>'.get_string('notyetstarted', 'completion').'</i>';
} else {
echo '<i>'.get_string('inprogress','completion').'</i>';
}
echo '</td></tr>';
-echo '<tr><td colspan="2"><b>'.get_string('required').':</b> ';
-// Get overall aggregation method
-$overall = $info->get_aggregation_method();
+// Load criteria to display
+$completions = $info->get_completions($user->id);
-if ($overall == COMPLETION_AGGREGATION_ALL) {
- echo get_string('criteriarequiredall', 'completion');
+// Check if this course has any criteria
+if (empty($completions)) {
+ echo '<tr><td colspan="2"><br />';
+ echo $OUTPUT->box(get_string('err_nocriteria', 'completion'), 'noticebox');
+ echo '</td></tr></tbody></table>';
} else {
- echo get_string('criteriarequiredany', 'completion');
-}
+ echo '<tr><td colspan="2"><b>'.get_string('required').':</b> ';
-echo '</td></tr></tbody></table>';
-
-// Generate markup for criteria statuses
-echo '<table class="generalbox boxaligncenter" cellpadding="3"><tbody>';
-echo '<tr class="ccheader">';
-echo '<th class="c0 header" scope="col">'.get_string('criteriagroup', 'block_completionstatus').'</th>';
-echo '<th class="c1 header" scope="col">'.get_string('criteria', 'completion').'</th>';
-echo '<th class="c2 header" scope="col">'.get_string('requirement', 'block_completionstatus').'</th>';
-echo '<th class="c3 header" scope="col">'.get_string('status').'</th>';
-echo '<th class="c4 header" scope="col">'.get_string('complete').'</th>';
-echo '<th class="c5 header" scope="col">'.get_string('completiondate', 'report_completion').'</th>';
-echo '</tr>';
-
-// Save row data
-$rows = array();
-
-global $COMPLETION_CRITERIA_TYPES;
-
-// Loop through course criteria
-foreach ($completions as $completion) {
- $criteria = $completion->get_criteria();
- $complete = $completion->is_complete();
-
- $row = array();
- $row['type'] = $criteria->criteriatype;
- $row['title'] = $criteria->get_title();
- $row['status'] = $completion->get_status();
- $row['timecompleted'] = $completion->timecompleted;
- $row['details'] = $criteria->get_details($completion);
- $rows[] = $row;
-}
+ // Get overall aggregation method
+ $overall = $info->get_aggregation_method();
-// Print table
-$last_type = '';
-$agg_type = false;
+ if ($overall == COMPLETION_AGGREGATION_ALL) {
+ echo get_string('criteriarequiredall', 'completion');
+ } else {
+ echo get_string('criteriarequiredany', 'completion');
+ }
+
+ echo '</td></tr></tbody></table>';
+
+ // Generate markup for criteria statuses
+ echo '<table class="generalbox logtable boxaligncenter" id="criteriastatus" width="100%"><tbody>';
+ echo '<tr class="ccheader">';
+ echo '<th class="c0 header" scope="col">'.get_string('criteriagroup', 'block_completionstatus').'</th>';
+ echo '<th class="c1 header" scope="col">'.get_string('criteria', 'completion').'</th>';
+ echo '<th class="c2 header" scope="col">'.get_string('requirement', 'block_completionstatus').'</th>';
+ echo '<th class="c3 header" scope="col">'.get_string('status').'</th>';
+ echo '<th class="c4 header" scope="col">'.get_string('complete').'</th>';
+ echo '<th class="c5 header" scope="col">'.get_string('completiondate', 'report_completion').'</th>';
+ echo '</tr>';
-foreach ($rows as $row) {
+ // Save row data
+ $rows = array();
+
+ // Loop through course criteria
+ foreach ($completions as $completion) {
+ $criteria = $completion->get_criteria();
+
+ $row = array();
+ $row['type'] = $criteria->criteriatype;
+ $row['title'] = $criteria->get_title();
+ $row['status'] = $completion->get_status();
+ $row['complete'] = $completion->is_complete();
+ $row['timecompleted'] = $completion->timecompleted;
+ $row['details'] = $criteria->get_details($completion);
+ $rows[] = $row;
+ }
- // Criteria group
- echo '<td class="c0">';
- if ($last_type !== $row['details']['type']) {
- $last_type = $row['details']['type'];
- echo $last_type;
+ // Print table
+ $last_type = '';
+ $agg_type = false;
+ $oddeven = 0;
- // Reset agg type
- $agg_type = true;
- } else {
- // Display aggregation type
- if ($agg_type) {
- $agg = $info->get_aggregation_method($row['type']);
+ foreach ($rows as $row) {
- echo '(<i>';
+ echo '<tr class="r' . $oddeven . '">';
- if ($agg == COMPLETION_AGGREGATION_ALL) {
- echo strtolower(get_string('all', 'completion'));
- } else {
- echo strtolower(get_string('any', 'completion'));
- }
+ // Criteria group
+ echo '<td class="cell c0">';
+ if ($last_type !== $row['details']['type']) {
+ $last_type = $row['details']['type'];
+ echo $last_type;
+
+ // Reset agg type
+ $agg_type = true;
+ } else {
+ // Display aggregation type
+ if ($agg_type) {
+ $agg = $info->get_aggregation_method($row['type']);
- echo '</i> '.strtolower(get_string('required')).')';
- $agg_type = false;
+ echo '(<i>';
+
+ if ($agg == COMPLETION_AGGREGATION_ALL) {
+ echo strtolower(get_string('aggregateall', 'completion'));
+ } else {
+ echo strtolower(get_string('aggregateany', 'completion'));
+ }
+
+ echo '</i> '.strtolower(get_string('required')).')';
+ $agg_type = false;
+ }
}
+ echo '</td>';
+
+ // Criteria title
+ echo '<td class="cell c1">';
+ echo $row['details']['criteria'];
+ echo '</td>';
+
+ // Requirement
+ echo '<td class="cell c2">';
+ echo $row['details']['requirement'];
+ echo '</td>';
+
+ // Status
+ echo '<td class="cell c3">';
+ echo $row['details']['status'];
+ echo '</td>';
+
+ // Is complete
+ echo '<td class="cell c4">';
+ echo $row['complete'] ? get_string('yes') : get_string('no');
+ echo '</td>';
+
+ // Completion data
+ echo '<td class="cell c5">';
+ if ($row['timecompleted']) {
+ echo userdate($row['timecompleted'], get_string('strftimedate', 'langconfig'));
+ } else {
+ echo '-';
+ }
+ echo '</td>';
+ echo '</tr>';
+ // for row striping
+ $oddeven = $oddeven ? 0 : 1;
}
- echo '</td>';
-
- // Criteria title
- echo '<td class="c1">';
- echo $row['details']['criteria'];
- echo '</td>';
-
- // Requirement
- echo '<td class="c2">';
- echo $row['details']['requirement'];
- echo '</td>';
-
- // Status
- echo '<td class="c3">';
- echo $row['details']['status'];
- echo '</td>';
-
- // Is complete
- echo '<td class="c4">';
- echo ($row['status'] === get_string('yes')) ? get_string('yes') : get_string('no');
- echo '</td>';
-
- // Completion data
- echo '<td class="c5">';
- if ($row['timecompleted']) {
- echo userdate($row['timecompleted'], '%e %B %G');
- } else {
- echo '-';
- }
- echo '</td>';
- echo '</tr>';
+
+ echo '</tbody></table>';
}
-echo '</tbody></table>';
+echo '<div class="buttons">';
+$courseurl = new moodle_url("/course/view.php", array('id' => $course->id));
+echo $OUTPUT->single_button($courseurl, get_string('returntocourse', 'block_completionstatus'), 'get');
+echo '</div>';
echo $OUTPUT->footer();
View
1 blocks/completionstatus/lang/en/block_completionstatus.php
@@ -5,3 +5,4 @@
$string['firstofsecond'] = '{$a->first} of {$a->second}';
$string['pluginname'] = 'Course completion status';
$string['requirement'] = 'Requirement';
+$string['returntocourse'] = 'Return to course';
View
9 blog/external_blogs.php
@@ -44,7 +44,16 @@
if ($delete && confirm_sesskey()) {
$externalbloguserid = $DB->get_field('blog_external', 'userid', array('id' => $delete));
if ($externalbloguserid == $USER->id) {
+ // Delete the external blog
$DB->delete_records('blog_external', array('id' => $delete));
+
+ // Delete the external blog's posts
+ $deletewhere = 'module = :module
+ AND userid = :userid
+ AND ' . $DB->sql_isnotempty('post', 'uniquehash', false, false) . '
+ AND ' . $DB->sql_compare_text('content') . ' = ' . $DB->sql_compare_text(':delete');
+ $DB->delete_records_select('post', $deletewhere, array('module' => 'blog_external', 'userid' => $USER->id, 'delete' => $delete));
+
$message = get_string('externalblogdeleted', 'blog');
}
}
View
5 blog/locallib.php
@@ -405,11 +405,10 @@ public function edit($params=array(), $form=null, $summaryoptions=array(), $atta
* @return void
*/
public function delete() {
- global $DB, $USER;
-
- $returnurl = '';
+ global $DB;
$this->delete_attachments();
+ $this->remove_associations();
$DB->delete_records('post', array('id' => $this->id));
tag_set('post', $this->id, array());
View
2 enrol/manual/yui/quickenrolment/quickenrolment.js
@@ -339,7 +339,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
count++;
var user = result.response.users[i];
users.append(create('<div class="'+CSS.USER+' clearfix" rel="'+user.id+'"></div>')
- .addClass((i%2)?CSS.ODD:CSS.EVEN)
+ .addClass((count%2)?CSS.ODD:CSS.EVEN)
.append(create('<div class="'+CSS.COUNT+'">'+count+'</div>'))
.append(create('<div class="'+CSS.PICTURE+'"></div>')
.append(create(user.picture)))
View
22 enrol/paypal/ipn.php
@@ -34,6 +34,7 @@
require_once("lib.php");
require_once($CFG->libdir.'/eventslib.php');
require_once($CFG->libdir.'/enrollib.php');
+require_once($CFG->libdir . '/filelib.php');
/// Keep out casual intruders
@@ -89,27 +90,27 @@
$plugin = enrol_get_plugin('paypal');
/// Open a connection back to PayPal to validate the data
-$header = '';
-$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
-$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
-$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
+$c = new curl();
+$options = array(
+ 'returntransfer' => true,
+ 'httpheader' => array('application/x-www-form-urlencoded'),
+ 'timeout' => 30,
+);
$paypaladdr = empty($CFG->usepaypalsandbox) ? 'www.paypal.com' : 'www.sandbox.paypal.com';
-$fp = fsockopen ($paypaladdr, 80, $errno, $errstr, 30);
+$location = "https://$paypaladdr/cgi-bin/webscr";
+$result = $c->post($location, $req, $options);
-if (!$fp) { /// Could not open a socket to PayPal - FAIL
+if (!$result) { /// Could not connect to PayPal - FAIL
echo "<p>Error: could not access paypal.com</p>";
message_paypal_error_to_admin("Could not access paypal.com to verify payment", $data);
die;
}
/// Connection is OK, so now we post the data to validate it
-fputs ($fp, $header.$req);
-
/// Now read the response and check if everything is OK.
-while (!feof($fp)) {
- $result = fgets($fp, 1024);
+if (strlen($result) > 0) {
if (strcmp($result, "VERIFIED") == 0) { // VALID PAYMENT!
@@ -296,7 +297,6 @@
}
}
-fclose($fp);
exit;
View
3 filter/mediaplugin/tests/filter_test.php
@@ -57,7 +57,10 @@ function test_filter_mediaplugin_link() {
'<a id="movie player" class="center" href="http://moodle.org/testfile/test.mpg">test mpg</a>',
'<a href="http://moodle.org/testfile/test.ram">test</a>',
'<a href="http://www.youtube.com/watch?v=JghQgA2HMX8" class="href=css">test file</a>',
+ '<a href="http://www.youtube-nocookie.com/watch?v=JghQgA2HMX8" class="href=css">test file</a>',
'<a class="youtube" href="http://www.youtube.com/watch?v=JghQgA2HMX8">test file</a>',
+ '<a href="http://youtu.be/JghQgA2HMX8" class="href=css">test file</a>',
+ '<a href="http://y2u.be/JghQgA2HMX8" class="href=css">test file</a>',
'<a class="_blanktarget" href="http://moodle.org/testfile/test.flv?d=100x100">test flv</a>',
'<a class="hrefcss" href="http://www.youtube.com/watch?v=JghQgA2HMX8">test file</a>',
'<a class="content" href="http://moodle.org/testfile/test.avi">test mp3</a>',
View
3 grade/edit/tree/category_form.php
@@ -225,7 +225,7 @@ function definition() {
$mform->addElement('header', 'headerparent', get_string('parentcategory', 'grades'));
$options = array();
- $default = '';
+ $default = -1;
$categories = grade_category::fetch_all(array('courseid'=>$COURSE->id));
foreach ($categories as $cat) {
@@ -238,6 +238,7 @@ function definition() {
if (count($categories) > 1) {
$mform->addElement('select', 'parentcategory', get_string('parentcategory', 'grades'), $options);
+ $mform->setDefault('parentcategory', $default);
$mform->addElement('static', 'currentparentaggregation', get_string('currentparentaggregation', 'grades'));
}
View
2 lang/en/admin.php
@@ -68,7 +68,7 @@
$string['backgroundcolour'] = 'Transparent colour';
$string['backups'] = 'Backups';
$string['backup_shortname'] = 'Use course name in backup filename';
-$string['backup_shortnamehelp'] = 'Use the course name as part of the backup filename instead of the course id number.';
+$string['backup_shortnamehelp'] = 'Use the course name as part of the backup filename.';
$string['badwordsconfig'] = 'Enter your list of bad words separated by commas.';
$string['badwordsdefault'] = 'If the custom list is empty, a default list from the language pack will be used.';
$string['badwordslist'] = 'Custom bad words list';
View
2 lang/en/backup.php
@@ -163,6 +163,7 @@
$string['lockedbyconfig'] = 'This setting has been locked by the default backup settings';
$string['lockedbyhierarchy'] = 'Locked by dependencies';
$string['managefiles'] = 'Manage backup files';
+$string['missingfilesinpool'] = 'Some files could not be saved during the backup, it won\'t be possible to restore them.';
$string['moodleversion'] = 'Moodle version';
$string['moreresults'] = 'There are too many results, enter a more specific search.';
$string['nomatchingcourses'] = 'There are no courses to display';
@@ -177,6 +178,7 @@
$string['restorecourse'] = 'Restore course';
$string['restorecoursesettings'] = 'Course settings';
$string['restoreexecutionsuccess'] = 'The course was restored successfully, clicking the continue button below will take you to view the course you restored.';
+$string['restorefileweremissing'] = 'Some files could not be restored because they were missing in the backup.';
$string['restorenewcoursefullname'] = 'New course name';
$string['restorenewcourseshortname'] = 'New course short name';
$string['restorenewcoursestartdate'] = 'New start date';
View
2 lang/en/block.php
@@ -37,6 +37,8 @@
$string['defaultregion_help'] = 'Themes may define one or more named block regions where blocks are displayed. This setting defines which of these you want this block to appear in by default. The region may be overridden on specific pages if required.';
$string['defaultweight'] = 'Default weight';
$string['defaultweight_help'] = 'The default weight allows you to choose roughly where you want the block to appear in the chosen region, either at the top or the bottom. The final location is calculated from all the blocks in that region (for example, only one block can actually be at the top). This value can be overridden on specific pages if required.';
+$string['deletecheck'] = 'Delete {$a} block?';
+$string['deleteblockcheck'] = 'Are you sure that you want to delete this block titled {$a}?';
$string['moveblockhere'] = 'Move block here';
$string['movingthisblockcancel'] = 'Moving this block ({$a})';
$string['onthispage'] = 'On this page';
View
1 lang/en/moodle.php
@@ -1803,6 +1803,7 @@
$string['virusplaceholder'] = 'This file that has been uploaded was found to contain a virus and has been moved or deleted and the user notified.';
$string['visible'] = 'Visible';
$string['visibletostudents'] = 'Visible to {$a}';
+$string['warning'] = 'Warning';
$string['warningdeleteresource'] = 'Warning: {$a} is referred in a resource. Would you like to update the resource?';
$string['webpage'] = 'Web page';
$string['week'] = 'Week';
View
2 lib/accesslib.php
@@ -6537,7 +6537,7 @@ public function get_context_name($withprefix = true, $short = false) {
if ($withprefix){
$name = get_string('modulename', $cm->modname).': ';
}
- $name .= $mod->name;
+ $name .= format_string($mod->name, true, array('context' => $this));
}
}
return $name;
View
56 lib/blocklib.php
@@ -1114,25 +1114,65 @@ public function process_url_add() {
* @return boolean true if anything was done. False if not.
*/
public function process_url_delete() {
- $blockid = optional_param('bui_deleteid', null, PARAM_INTEGER);
+ global $CFG, $PAGE, $OUTPUT;
+
+ $blockid = optional_param('bui_deleteid', null, PARAM_INT);
+ $confirmdelete = optional_param('bui_confirm', null, PARAM_INT);
+
if (!$blockid) {
return false;
}
require_sesskey();
-
$block = $this->page->blocks->find_instance($blockid);
-
if (!$block->user_can_edit() || !$this->page->user_can_edit_blocks() || !$block->user_can_addto($this->page)) {
throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));
}
- blocks_delete_instance($block->instance);
-
- // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
- $this->page->ensure_param_not_in_url('bui_deleteid');
+ if (!$confirmdelete) {
+ $deletepage = new moodle_page();
+ $deletepage->set_pagelayout('admin');
+ $deletepage->set_course($this->page->course);
+ $deletepage->set_context($this->page->context);
+ if ($this->page->cm) {
+ $deletepage->set_cm($this->page->cm);
+ }
- return true;
+ $deleteurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
+ $deleteurlparams = $this->page->url->params();
+ $deletepage->set_url($deleteurlbase, $deleteurlparams);
+ $deletepage->set_block_actions_done();
+ // At this point we are either going to redirect, or display the form, so
+ // overwrite global $PAGE ready for this. (Formslib refers to it.)
+ $PAGE = $deletepage;
+ //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
+ $output = $deletepage->get_renderer('core');
+ $OUTPUT = $output;
+
+ $site = get_site();
+ $blocktitle = $block->get_title();
+ $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
+ $message = get_string('deleteblockcheck', 'block', $blocktitle);
+
+ $PAGE->navbar->add($strdeletecheck);
+ $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
+ $PAGE->set_heading($site->fullname);
+ echo $OUTPUT->header();
+ $confirmurl = new moodle_url("$deletepage->url?", array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance->id, 'bui_confirm' => 1));
+ $cancelurl = new moodle_url($deletepage->url);
+ $yesbutton = new single_button($confirmurl, get_string('yes'));
+ $nobutton = new single_button($cancelurl, get_string('no'));
+ echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
+ echo $OUTPUT->footer();
+ // Make sure that nothing else happens after we have displayed this form.
+ exit;
+ } else {
+ blocks_delete_instance($block->instance);
+ // bui_deleteid and bui_confirm should not be in the PAGE url.
+ $this->page->ensure_param_not_in_url('bui_deleteid');
+ $this->page->ensure_param_not_in_url('bui_confirm');
+ return true;
+ }
}
/**
View
4 lib/conditionlib.php
@@ -596,7 +596,7 @@ public function get_full_information($modinfo=null) {
$course = $COURSE;
} else {
$course = $DB->get_record('course', array('id' => $this->item->course),
- 'id, enablecompletion, modinfo', MUST_EXIST);
+ 'id, enablecompletion, modinfo, sectioncache', MUST_EXIST);
}
foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
if (!$modinfo) {
@@ -747,7 +747,7 @@ public function is_available(&$information, $grabthelot=false, $userid=0, $modin
$course = $COURSE;
} else {
$course = $DB->get_record('course', array('id' => $this->item->course),
- 'id, enablecompletion, modinfo', MUST_EXIST);
+ 'id, enablecompletion, modinfo, sectioncache', MUST_EXIST);
}
$completion = new completion_info($course);
View
10 lib/db/upgrade.php
@@ -949,5 +949,15 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2012062501.08);
}
+ if ($oldversion < 2012062501.14) {
+ $subquery = 'SELECT b.id FROM {blog_external} b where b.id = ' . $DB->sql_cast_char2int('{post}.content', true);
+ $sql = 'DELETE FROM {post}
+ WHERE {post}.module = \'blog_external\'
+ AND NOT EXISTS (' . $subquery . ')
+ AND ' . $DB->sql_isnotempty('post', 'uniquehash', false, false);
+ $DB->execute($sql);
+ upgrade_main_savepoint(true, 2012062501.14);
+ }
+
return true;
}
View
49 lib/db/upgradelib.php
@@ -30,6 +30,51 @@
defined('MOODLE_INTERNAL') || die();
+/**
+ * Returns all non-view and non-temp tables with sane names.
+ * Prints list of non-supported tables using $OUTPUT->notification()
+ *
+ * @return array
+ */
+function upgrade_mysql_get_supported_tables() {
+ global $OUTPUT, $DB;
+
+ $tables = array();
+ $patprefix = str_replace('_', '\\_', $DB->get_prefix());
+ $pregprefix = preg_quote($DB->get_prefix(), '/');
+
+ $sql = "SHOW FULL TABLES LIKE '$patprefix%'";
+ $rs = $DB->get_recordset_sql($sql);
+ foreach ($rs as $record) {
+ $record = array_change_key_case((array)$record, CASE_LOWER);
+ $type = $record['table_type'];
+ unset($record['table_type']);
+ $fullname = array_shift($record);
+
+ if ($pregprefix === '') {
+ $name = $fullname;
+ } else {
+ $count = null;
+ $name = preg_replace("/^$pregprefix/", '', $fullname, -1, $count);
+ if ($count !== 1) {
+ continue;
+ }
+ }
+
+ if (!preg_match("/^[a-z][a-z0-9_]*$/", $name)) {
+ echo $OUTPUT->notification("Database table with invalid name '$fullname' detected, skipping.", 'notifyproblem');
+ continue;
+ }
+ if ($type === 'VIEW') {
+ echo $OUTPUT->notification("Unsupported database table view '$fullname' detected, skipping.", 'notifyproblem');
+ continue;
+ }
+ $tables[$name] = $name;
+ }
+ $rs->close();
+
+ return $tables;
+}
/**
* Remove all signed numbers from current database - mysql only.
@@ -50,7 +95,7 @@ function upgrade_mysql_fix_unsigned_columns() {
$pbar = new progress_bar('mysqlconvertunsigned', 500, true);
$prefix = $DB->get_prefix();
- $tables = $DB->get_tables();
+ $tables = upgrade_mysql_get_supported_tables();
$tablecount = count($tables);
$i = 0;
@@ -115,7 +160,7 @@ function upgrade_mysql_fix_lob_columns() {
$pbar = new progress_bar('mysqlconvertlobs', 500, true);
$prefix = $DB->get_prefix();
- $tables = $DB->get_tables();
+ $tables = upgrade_mysql_get_supported_tables();
asort($tables);
$tablecount = count($tables);
View
24 lib/googleapi.php
@@ -109,23 +109,21 @@ public function get_file_list($search = '') {
$source = 'https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key='.$docid.'&exportFormat=xls';
break;
case 'pdf':
- $title = (string)$gdoc->title;
- $source = (string)$gdoc->content[0]->attributes()->src;
- break;
case 'file':
- $title = (string)$gdoc->title;
- $source = (string)$gdoc->content[0]->attributes()->src;
+ $title = (string)$gdoc->title;
+ // Some files don't have a content probably because the download has been restricted.
+ if (isset($gdoc->content)) {
+ $source = (string)$gdoc->content[0]->attributes()->src;
+ }
break;
}
- if (!empty($source)) {
- $files[] = array( 'title' => $title,
- 'url' => "{$gdoc->link[0]->attributes()->href}",
- 'source' => $source,
- 'date' => usertime(strtotime($gdoc->updated)),
- 'thumbnail' => (string) $OUTPUT->pix_url(file_extension_icon($title, 32))
- );
- }
+ $files[] = array( 'title' => $title,
+ 'url' => "{$gdoc->link[0]->attributes()->href}",
+ 'source' => $source,
+ 'date' => usertime(strtotime($gdoc->updated)),
+ 'thumbnail' => (string) $OUTPUT->pix_url(file_extension_icon($title, 32))
+ );
}
return $files;
View
17 lib/medialib.php
@@ -528,8 +528,8 @@ class core_media_player_youtube extends core_media_player_external {
protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
global $CFG;
- $site = $this->matches[1];
- $videoid = $this->matches[3];
+ $site = 'www.youtube.com';
+ $videoid = end($this->matches);
$info = trim($name);
if (empty($info) or strpos($info, 'http') === 0) {
@@ -564,10 +564,15 @@ protected function embed_external(moodle_url $url, $name, $width, $height, $opti
}
protected function get_regex() {
+ // Regex for standard youtube link
+ $link = '(youtube(-nocookie)?\.com/(?:watch\?v=|v/))';
+ // Regex for shortened youtube link
+ $shortlink = '((youtu|y2u)\.be/)';
+
// Initial part of link.
- $start = '~^https?://(www\.youtube(-nocookie)?\.com)/';
- // Middle bit: either watch?v= or v/.
- $middle = '(?:watch\?v=|v/)([a-z0-9\-_]+)';
+ $start = '~^https?://(www\.)?(' . $link . '|' . $shortlink . ')';
+ // Middle bit: Video key value
+ $middle = '([a-z0-9\-_]+)';
return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
}
@@ -578,7 +583,7 @@ public function get_rank() {
}
public function get_embeddable_markers() {
- return array('youtube');
+ return array('youtube.com', 'youtube-nocookie.com', 'youtu.be', 'y2u.be');
}
}
View
36 lib/moodlelib.php
@@ -3915,15 +3915,45 @@ function truncate_userinfo($info) {
* Any plugin that needs to purge user data should register the 'user_deleted' event.
*
* @param stdClass $user full user object before delete
- * @return boolean always true
+ * @return boolean success
+ * @throws coding_exception if invalid $user parameter detected
*/
-function delete_user($user) {
+function delete_user(stdClass $user) {
global $CFG, $DB;
require_once($CFG->libdir.'/grouplib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/message/lib.php');
require_once($CFG->dirroot.'/tag/lib.php');
+ // Make sure nobody sends bogus record type as parameter.
+ if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
+ throw new coding_exception('Invalid $user parameter in delete_user() detected');
+ }
+
+ // Better not trust the parameter and fetch the latest info,
+ // this will be very expensive anyway.
+ if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
+ debugging('Attempt to delete unknown user account.');
+ return false;
+ }
+
+ // There must be always exactly one guest record,
+ // originally the guest account was identified by username only,
+ // now we use $CFG->siteguest for performance reasons.
+ if ($user->username === 'guest' or isguestuser($user)) {
+ debugging('Guest user account can not be deleted.');
+ return false;
+ }
+
+ // Admin can be theoretically from different auth plugin,
+ // but we want to prevent deletion of internal accoutns only,
+ // if anything goes wrong ppl may force somebody to be admin via
+ // config.php setting $CFG->siteadmins.
+ if ($user->auth === 'manual' and is_siteadmin($user)) {
+ debugging('Local administrator accounts can not be deleted.');
+ return false;
+ }
+
// delete all grades - backup is kept in grade_grades_history table
grade_user_delete($user->id);
@@ -4767,7 +4797,7 @@ function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
foreach ($fields as $field) {
$updatesql = "UPDATE {".$modname."}
SET $field = $field + ?
- WHERE course=? AND $field<>0 AND $field<>0";
+ WHERE course=? AND $field<>0";
$return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
}
View
2 lib/pluginlib.php
@@ -1064,7 +1064,7 @@ protected function cron_has_fresh_fetch($now) {
return true;
}
- if ($now - $recent > HOURSECS) {
+ if ($now - $recent > 24 * HOURSECS) {
return false;
}
View
12 lib/questionlib.php
@@ -1117,16 +1117,18 @@ function question_category_options($contexts, $top = false, $currentcat = 0,
// sort cats out into different contexts
$categoriesarray = array();
- foreach ($pcontexts as $pcontext) {
- $contextstring = print_context_name(
- get_context_instance_by_id($pcontext), true, true);
+ foreach ($pcontexts as $contextid) {
+ $context = context::instance_by_id($contextid);
+ $contextstring = $context->get_context_name(true, true);
foreach ($categories as $category) {
- if ($category->contextid == $pcontext) {
+ if ($category->contextid == $contextid) {
$cid = $category->id;
if ($currentcat != $cid || $currentcat == 0) {
$countstring = !empty($category->questioncount) ?
" ($category->questioncount)" : '';
- $categoriesarray[$contextstring][$cid] = $category->indentedname.$countstring;
+ $categoriesarray[$contextstring][$cid] =
+ format_string($category->indentedname, true,
+ array('context' => $context)) . $countstring;
}
}
}
View
60 lib/tests/moodlelib_test.php
@@ -1910,4 +1910,64 @@ public function test_format_float() {
$this->assertEquals('5.43000', format_float(5.43, 5, false));
$this->assertEquals('5.43', format_float(5.43, 5, false, true));
}
+
+ /**
+ * Test deleting of users.
+ */
+ public function test_delete_user() {
+ global $DB, $CFG;
+
+ $this->resetAfterTest();
+
+ $guest = $DB->get_record('user', array('id'=>$CFG->siteguest), '*', MUST_EXIST);
+ $admin = $DB->get_record('user', array('id'=>$CFG->siteadmins), '*', MUST_EXIST);
+ $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
+
+ $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
+ $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
+
+ $result = delete_user($user);
+ $this->assertTrue($result);
+ $deluser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
+ $this->assertEquals(1, $deluser->deleted);
+ $this->assertEquals(0, $deluser->picture);
+ $this->assertSame('', $deluser->idnumber);
+ $this->assertSame(md5($user->username), $deluser->email);
+ $this->assertRegExp('/^'.preg_quote($user->email, '/').'\.\d*$/', $deluser->username);
+
+ $this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
+
+ // Try invalid params.
+
+ $record = new stdClass();
+ $record->grrr = 1;
+ try {
+ delete_user($record);
+ $this->fail('Expecting exception for invalid delete_user() $user parameter');
+ } catch (coding_exception $e) {
+ $this->assertTrue(true);
+ }
+ $record->id = 1;
+ try {
+ delete_user($record);
+ $this->fail('Expecting exception for invalid delete_user() $user parameter');
+ } catch (coding_exception $e) {
+ $this->assertTrue(true);
+ }
+
+ $CFG->debug = DEBUG_MINIMAL; // Prevent standard debug warnings.
+
+ $record = new stdClass();
+ $record->id = 666;
+ $record->username = 'xx';
+ $this->assertFalse($DB->record_exists('user', array('id'=>666))); // Any non-existent id is ok.
+ $result = delete_user($record);
+ $this->assertFalse($result);
+
+ $result = delete_user($guest);
+ $this->assertFalse($result);
+
+ $result = delete_user($admin);
+ $this->assertFalse($result);
+ }
}
View
47 lib/tests/pluginlib_test.php
@@ -103,7 +103,7 @@ public function test_cron_initial_fetch() {
*/
public function test_cron_has_fresh_fetch() {
$provider = testable_available_update_checker::instance();
- $provider->fakerecentfetch = time() - 59 * MINSECS; // fetched an hour ago
+ $provider->fakerecentfetch = time() - 23 * HOURSECS; // fetched 23 hours ago
$provider->fakecurrenttimestamp = -1;
$provider->cron();
$this->assertTrue(true); // we should get here with no exception thrown
@@ -127,23 +127,52 @@ public function test_cron_has_outdated_fetch() {
*/
public function test_cron_offset_execution_not_yet() {
$provider = testable_available_update_checker::instance();
- $provider->fakerecentfetch = time() - 24 * HOURSECS;
- $provider->fakecurrenttimestamp = mktime(1, 40, 02); // 01:40:02 AM