Merge branch 'MDL-45579-master' of git://github.com/FMCorz/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 10 Jun 2014 18:56:14 +0000 (20:56 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 10 Jun 2014 18:56:14 +0000 (20:56 +0200)
296 files changed:
admin/renderer.php
admin/settings/server.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/util/dbops/backup_structure_dbops.class.php
backup/util/structure/backup_structure_processor.class.php
blocks/navigation/tests/behat/expand_courses_node.feature [new file with mode: 0644]
blocks/navigation/tests/behat/view_my_courses.feature
blocks/news_items/tests/behat/display_news.feature [new file with mode: 0644]
cache/README.md
cache/classes/config.php
cache/classes/factory.php
cache/classes/store.php
cache/locallib.php
cache/stores/memcache/lib.php
cache/stores/memcache/tests/memcache_test.php
cache/stores/memcached/lib.php
cache/stores/memcached/tests/memcached_test.php
cache/stores/mongodb/lib.php
cache/stores/mongodb/tests/mongodb_test.php
cache/tests/administration_helper_test.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
composer.json
config-dist.php
course/format/upgrade.txt
course/tests/behat/course_creation.feature [new file with mode: 0644]
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js
course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js
course/yui/src/modchooser/js/modchooser.js
enrol/tests/behat/behat_enrol.php
grade/grading/tests/behat/behat_grading.php
install/lang/ckb/error.php [new file with mode: 0644]
install/lang/de/admin.php
lang/en/admin.php
lang/en/cache.php
lang/en/moodle.php
lib/behat/lib.php
lib/classes/event/comment_created.php
lib/classes/event/comment_deleted.php
lib/classes/event/course_module_completion_updated.php
lib/classes/event/course_module_created.php
lib/classes/event/course_module_deleted.php
lib/classes/event/course_module_updated.php
lib/classes/event/course_module_viewed.php
lib/classes/event/user_enrolment_deleted.php
lib/classes/event/user_enrolment_updated.php
lib/classes/task/scheduled_task.php
lib/classes/useragent.php
lib/db/caches.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js
lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js
lib/editor/tinymce/tests/behat/edit_available_icons.feature
lib/filelib.php
lib/javascript-static.js
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/phpunit/bootstrap.php
lib/questionlib.php
lib/tests/scheduled_task_test.php
lib/tests/theme_config_test.php
lib/weblib.php
mod/assign/classes/event/all_submissions_downloaded.php
mod/assign/classes/event/assessable_submitted.php
mod/assign/classes/event/batch_set_marker_allocation_viewed.php
mod/assign/classes/event/batch_set_workflow_state_viewed.php
mod/assign/classes/event/extension_granted.php
mod/assign/classes/event/feedback_viewed.php
mod/assign/classes/event/grading_form_viewed.php
mod/assign/classes/event/grading_table_viewed.php
mod/assign/classes/event/identities_revealed.php
mod/assign/classes/event/marker_updated.php
mod/assign/classes/event/reveal_identities_confirmation_page_viewed.php
mod/assign/classes/event/statement_accepted.php
mod/assign/classes/event/submission_confirmation_form_viewed.php
mod/assign/classes/event/submission_duplicated.php
mod/assign/classes/event/submission_form_viewed.php
mod/assign/classes/event/submission_graded.php
mod/assign/classes/event/submission_locked.php
mod/assign/classes/event/submission_status_updated.php
mod/assign/classes/event/submission_status_viewed.php
mod/assign/classes/event/submission_unlocked.php
mod/assign/classes/event/submission_viewed.php
mod/assign/classes/event/workflow_state_updated.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/settings.php
mod/assign/gradingtable.php
mod/assign/locallib.php
mod/assign/submission/comments/classes/event/comment_created.php
mod/assign/submission/comments/classes/event/comment_deleted.php
mod/assign/submission/file/classes/event/assessable_uploaded.php
mod/assign/submission/file/classes/event/submission_created.php
mod/assign/submission/file/classes/event/submission_updated.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/classes/event/assessable_uploaded.php
mod/assign/submission/onlinetext/classes/event/submission_created.php
mod/assign/submission/onlinetext/classes/event/submission_updated.php
mod/book/classes/event/chapter_created.php
mod/book/classes/event/chapter_deleted.php
mod/book/classes/event/chapter_updated.php
mod/book/classes/event/chapter_viewed.php
mod/book/tool/exportimscp/classes/event/book_exported.php
mod/book/tool/print/classes/event/book_printed.php
mod/book/tool/print/classes/event/chapter_printed.php
mod/chat/classes/event/message_sent.php
mod/chat/classes/event/sessions_viewed.php
mod/choice/classes/event/answer_submitted.php
mod/choice/classes/event/answer_updated.php
mod/choice/classes/event/report_viewed.php
mod/data/classes/event/comment_created.php
mod/data/classes/event/comment_deleted.php
mod/data/classes/event/field_created.php
mod/data/classes/event/field_deleted.php
mod/data/classes/event/field_updated.php
mod/data/classes/event/record_created.php
mod/data/classes/event/record_deleted.php
mod/data/classes/event/record_updated.php
mod/data/classes/event/template_updated.php
mod/data/classes/event/template_viewed.php
mod/data/lib.php
mod/data/locallib.php
mod/feedback/classes/event/response_deleted.php
mod/feedback/classes/event/response_submitted.php
mod/feedback/item/multichoicerated/lib.php
mod/forum/classes/event/assessable_uploaded.php
mod/forum/classes/event/discussion_created.php
mod/forum/classes/event/discussion_deleted.php
mod/forum/classes/event/discussion_updated.php
mod/forum/classes/event/discussion_viewed.php
mod/forum/classes/event/post_created.php
mod/forum/classes/event/post_deleted.php
mod/forum/classes/event/post_updated.php
mod/forum/classes/event/readtracking_disabled.php
mod/forum/classes/event/readtracking_enabled.php
mod/forum/classes/event/subscribers_viewed.php
mod/forum/classes/event/subscription_created.php
mod/forum/classes/event/subscription_deleted.php
mod/forum/classes/existing_subscriber_selector.php [new file with mode: 0644]
mod/forum/classes/potential_subscriber_selector.php [new file with mode: 0644]
mod/forum/classes/subscriber_selector_base.php [new file with mode: 0644]
mod/forum/deprecatedlib.php [new file with mode: 0644]
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post_form.php [deleted file]
mod/forum/search.php
mod/forum/subscribers.php
mod/forum/tests/behat/behat_mod_forum.php
mod/forum/upgrade.txt
mod/glossary/classes/event/category_created.php
mod/glossary/classes/event/category_deleted.php
mod/glossary/classes/event/category_updated.php
mod/glossary/classes/event/comment_created.php
mod/glossary/classes/event/comment_deleted.php
mod/glossary/classes/event/entry_approved.php
mod/glossary/classes/event/entry_created.php
mod/glossary/classes/event/entry_deleted.php
mod/glossary/classes/event/entry_disapproved.php
mod/glossary/classes/event/entry_updated.php
mod/glossary/classes/event/entry_viewed.php
mod/glossary/lib.php
mod/lesson/classes/event/essay_assessed.php
mod/lesson/classes/event/essay_attempt_viewed.php
mod/lesson/classes/event/highscore_added.php
mod/lesson/classes/event/highscores_viewed.php
mod/lesson/classes/event/lesson_ended.php
mod/lesson/classes/event/lesson_started.php
mod/lesson/lib.php
mod/lti/OAuth.php
mod/lti/OAuthBody.php
mod/lti/backup/moodle2/backup_lti_stepslib.php
mod/lti/backup/moodle2/restore_lti_activity_task.class.php
mod/lti/backup/moodle2/restore_lti_stepslib.php
mod/lti/classes/plugininfo/ltisource.php
mod/lti/db/access.php
mod/lti/edit_form.php
mod/lti/grade.php
mod/lti/lang/en/lti.php
mod/lti/lib.php
mod/lti/locallib.php
mod/lti/mod_form.js
mod/lti/mod_form.php
mod/lti/return.php
mod/lti/service.php
mod/lti/servicelib.php
mod/lti/settings.php
mod/lti/styles.css
mod/lti/submissions.js [deleted file]
mod/lti/tests/locallib_test.php
mod/lti/version.php
mod/lti/view.php
mod/quiz/classes/event/attempt_abandoned.php
mod/quiz/classes/event/attempt_becameoverdue.php
mod/quiz/classes/event/attempt_deleted.php
mod/quiz/classes/event/attempt_preview_started.php
mod/quiz/classes/event/attempt_reviewed.php
mod/quiz/classes/event/attempt_started.php
mod/quiz/classes/event/attempt_submitted.php
mod/quiz/classes/event/attempt_summary_viewed.php
mod/quiz/classes/event/attempt_viewed.php
mod/quiz/classes/event/edit_page_viewed.php
mod/quiz/classes/event/group_override_created.php
mod/quiz/classes/event/group_override_deleted.php
mod/quiz/classes/event/group_override_updated.php
mod/quiz/classes/event/question_manually_graded.php
mod/quiz/classes/event/report_viewed.php
mod/quiz/classes/event/user_override_created.php
mod/quiz/classes/event/user_override_deleted.php
mod/quiz/classes/event/user_override_updated.php
mod/quiz/editlib.php
mod/quiz/lib.php
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/statistics/report.php
mod/quiz/tests/editlib_test.php
mod/quiz/tests/lib_test.php
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js
mod/quiz/yui/src/autosave/js/autosave.js
mod/scorm/classes/event/attempt_deleted.php
mod/scorm/classes/event/interactions_viewed.php
mod/scorm/classes/event/report_viewed.php
mod/scorm/classes/event/sco_launched.php
mod/scorm/classes/event/tracks_viewed.php
mod/scorm/datamodel.php
mod/scorm/grade.php
mod/scorm/index.php
mod/scorm/lib.php
mod/scorm/loadSCO.php
mod/scorm/loaddatamodel.php
mod/scorm/locallib.php
mod/scorm/mod_form.php
mod/scorm/module.js
mod/scorm/player.php
mod/scorm/prereqs.php
mod/scorm/renderer.php
mod/scorm/report.php
mod/scorm/report/basic/report.php
mod/scorm/report/interactions/report.php
mod/scorm/report/objectives/report.php
mod/scorm/reportsettings_form.php
mod/scorm/settings.php
mod/scorm/styles.css
mod/scorm/view.php
mod/wiki/classes/event/comment_created.php
mod/wiki/classes/event/comment_deleted.php
mod/wiki/classes/event/comments_viewed.php
mod/wiki/classes/event/page_created.php
mod/wiki/classes/event/page_deleted.php
mod/wiki/classes/event/page_diff_viewed.php
mod/wiki/classes/event/page_history_viewed.php
mod/wiki/classes/event/page_locks_deleted.php
mod/wiki/classes/event/page_map_viewed.php
mod/wiki/classes/event/page_updated.php
mod/wiki/classes/event/page_version_deleted.php
mod/wiki/classes/event/page_version_restored.php
mod/wiki/classes/event/page_version_viewed.php
mod/wiki/classes/event/page_viewed.php
mod/workshop/classes/event/assessable_uploaded.php
mod/workshop/classes/event/assessment_evaluated.php
mod/workshop/classes/event/assessment_evaluations_reset.php
mod/workshop/classes/event/assessment_reevaluated.php
mod/workshop/classes/event/assessments_reset.php
mod/workshop/classes/event/phase_switched.php
mod/workshop/classes/event/submission_assessed.php
mod/workshop/classes/event/submission_created.php
mod/workshop/classes/event/submission_reassessed.php
mod/workshop/classes/event/submission_updated.php
mod/workshop/classes/event/submission_viewed.php
pix/f/publisher-128.png [new file with mode: 0644]
pix/f/publisher-24.png [new file with mode: 0644]
pix/f/publisher-256.png [new file with mode: 0644]
pix/f/publisher-32.png [new file with mode: 0644]
pix/f/publisher-48.png [new file with mode: 0644]
pix/f/publisher-64.png [new file with mode: 0644]
pix/f/publisher-72.png [new file with mode: 0644]
pix/f/publisher-80.png [new file with mode: 0644]
pix/f/publisher-96.png [new file with mode: 0644]
pix/f/publisher.png [new file with mode: 0644]
question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
question/type/random/db/upgrade.php [new file with mode: 0644]
question/type/random/questiontype.php
question/type/random/version.php
report/log/index.php
theme/base/style/core.css
theme/base/style/pagelayout.css
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
version.php

index 944b501..954b977 100644 (file)
@@ -1384,7 +1384,7 @@ class core_admin_renderer extends plugin_renderer_base {
             get_string('report'),
             get_string('status'),
         );
-        $servertable->colclasses = array('centeralign name', 'centeralign status', 'leftalign report', 'centeralign info');
+        $servertable->colclasses = array('centeralign name', 'centeralign info', 'leftalign report', 'centeralign status');
         $servertable->attributes['class'] = 'admintable environmenttable generaltable';
         $servertable->id = 'serverstatus';
 
index ec37e43..29a5966 100644 (file)
@@ -11,6 +11,7 @@ $temp = new admin_settingpage('systempaths', new lang_string('systempaths','admi
 $temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('pathtodu', 'admin'), new lang_string('configpathtodu', 'admin'), ''));
 $temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
 $temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
+$temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'), new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
 $ADMIN->add('server', $temp);
 
 
index 53cce22..f767133 100644 (file)
@@ -28,8 +28,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 /**
- * create the temp dir where backup/restore will happen,
- * delete old directories and create temp ids table
+ * Create the temp dir where backup/restore will happen and create temp ids table.
  */
 class create_and_clean_temp_stuff extends backup_execution_step {
 
@@ -38,7 +37,6 @@ class create_and_clean_temp_stuff extends backup_execution_step {
         $progress->start_progress('Deleting backup directories');
         backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
         backup_helper::clear_backup_dir($this->get_backupid(), $progress);           // Empty temp dir, just in case
-        backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress);    // Delete > 4 hours temp dirs
         backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
         backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
         $progress->end_progress();
@@ -46,11 +44,11 @@ class create_and_clean_temp_stuff extends backup_execution_step {
 }
 
 /**
- * delete the temp dir used by backup/restore (conditionally),
- * delete old directories and drop tem ids table. Note we delete
+ * Delete the temp dir used by backup/restore (conditionally),
+ * delete old directories and drop temp ids table. Note we delete
  * the directory but not the corresponding log file that will be
- * there for, at least, 4 hours - only delete_old_backup_dirs()
- * deletes log files (for easier access to them)
+ * there for, at least, 1 week - only delete_old_backup_dirs() or cron
+ * deletes log files (for easier access to them).
  */
 class drop_and_clean_temp_stuff extends backup_execution_step {
 
@@ -60,7 +58,7 @@ class drop_and_clean_temp_stuff extends backup_execution_step {
         global $CFG;
 
         backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
-        backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));              // Delete > 4 hours temp dirs
+        backup_helper::delete_old_backup_dirs(strtotime('-1 week'));                // Delete > 1 week old temp dirs.
         // Delete temp dir conditionally:
         // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
         // 2) If backup temp dir deletion has been marked to be avoided
index 512f64d..f86c3f9 100644 (file)
@@ -67,7 +67,7 @@ class restore_drop_and_clean_temp_stuff extends restore_execution_step {
         restore_controller_dbops::drop_restore_temp_tables($this->get_restoreid()); // Drop ids temp table
         $progress = $this->task->get_progress();
         $progress->start_progress('Deleting backup dir');
-        backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress);              // Delete > 4 hours temp dirs
+        backup_helper::delete_old_backup_dirs(strtotime('-1 week'), $progress);      // Delete > 1 week old temp dirs.
         if (empty($CFG->keeptempdirectoriesonbackup)) { // Conditionally
             backup_helper::delete_backup_dir($this->task->get_tempdir(), $progress); // Empty restore dir
         }
@@ -3676,13 +3676,16 @@ class restore_move_module_questions_categories extends restore_execution_step {
  * Execution step that will create all the question/answers/qtype-specific files for the restored
  * questions. It must be executed after {@link restore_move_module_questions_categories}
  * because only then each question is in its final category and only then the
- * context can be determined
- *
- * TODO: Improve this. Instead of looping over each question, it can be reduced to
- *       be done by contexts (this will save a huge ammount of queries)
+ * contexts can be determined.
  */
 class restore_create_question_files extends restore_execution_step {
 
+    /** @var array Question-type specific component items cache. */
+    private $qtypecomponentscache = array();
+
+    /**
+     * Preform the restore_create_question_files step.
+     */
     protected function define_execution() {
         global $DB;
 
@@ -3690,60 +3693,93 @@ class restore_create_question_files extends restore_execution_step {
         $progress = $this->task->get_progress();
         $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
 
-        // Let's process only created questions
-        $questionsrs = $DB->get_recordset_sql("SELECT bi.itemid, bi.newitemid, bi.parentitemid, q.qtype
-                                               FROM {backup_ids_temp} bi
-                                               JOIN {question} q ON q.id = bi.newitemid
+        // Parentitemids of question_createds in backup_ids_temp are the category it is in.
+        // MUST use a recordset, as there is no unique key in the first (or any) column.
+        $catqtypes = $DB->get_recordset_sql("SELECT DISTINCT bi.parentitemid AS categoryid, q.qtype as qtype
+                                               FROM {backup_ids_temp} AS bi
+                                               JOIN {question} AS q ON q.id = bi.newitemid
                                               WHERE bi.backupid = ?
-                                                AND bi.itemname = 'question_created'", array($this->get_restoreid()));
-        foreach ($questionsrs as $question) {
-            // Report progress for each question.
-            $progress->progress();
-
-            // Get question_category mapping, it contains the target context for the question
-            if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'question_category', $question->parentitemid)) {
-                // Something went really wrong, cannot find the question_category for the question
-                debugging('Error fetching target context for question', DEBUG_DEVELOPER);
-                continue;
-            }
-            // Calculate source and target contexts
-            $oldctxid = $qcatmapping->info->contextid;
-            $newctxid = $qcatmapping->parentitemid;
-
-            // Add common question files (question and question_answer ones)
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
-                    $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
-                    $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
-                    $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
-                    $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
-                    $oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
-                    $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
-                    $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
-            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
-                    $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
-
-            // Add qtype dependent files
-            $components = backup_qtype_plugin::get_components_and_fileareas($question->qtype);
-            foreach ($components as $component => $fileareas) {
-                foreach ($fileareas as $filearea => $mapping) {
-                    // Use itemid only if mapping is question_created
-                    $itemid = ($mapping == 'question_created') ? $question->itemid : null;
-                    restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
-                            $oldctxid, $this->task->get_userid(), $mapping, $itemid, $newctxid, true, $progress);
+                                                AND bi.itemname = 'question_created'
+                                           ORDER BY categoryid ASC", array($this->get_restoreid()));
+
+        $currentcatid = -1;
+        foreach ($catqtypes as $categoryid => $row) {
+            $qtype = $row->qtype;
+
+            // Check if we are in a new category.
+            if ($currentcatid !== $categoryid) {
+                // Report progress for each category.
+                $progress->progress();
+
+                if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(),
+                        'question_category', $categoryid)) {
+                    // Something went really wrong, cannot find the question_category for the question_created records.
+                    debugging('Error fetching target context for question', DEBUG_DEVELOPER);
+                    continue;
                 }
+
+                // Calculate source and target contexts.
+                $oldctxid = $qcatmapping->info->contextid;
+                $newctxid = $qcatmapping->parentitemid;
+
+                $this->send_common_files($oldctxid, $newctxid, $progress);
+                $currentcatid = $categoryid;
             }
+
+            $this->send_qtype_files($qtype, $oldctxid, $newctxid, $progress);
         }
-        $questionsrs->close();
+        $catqtypes->close();
         $progress->end_progress();
     }
-}
 
+    /**
+     * Send the common question files to a new context.
+     *
+     * @param int             $oldctxid Old context id.
+     * @param int             $newctxid New context id.
+     * @param \core\progress  $progress Progress object to use.
+     */
+    private function send_common_files($oldctxid, $newctxid, $progress) {
+        // Add common question files (question and question_answer ones).
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
+                $oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
+                $oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
+                $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
+                $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
+                $oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
+                $oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
+                $oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
+        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
+                $oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
+    }
+
+    /**
+     * Send the question type specific files to a new context.
+     *
+     * @param text            $qtype The qtype name to send.
+     * @param int             $oldctxid Old context id.
+     * @param int             $newctxid New context id.
+     * @param \core\progress  $progress Progress object to use.
+     */
+    private function send_qtype_files($qtype, $oldctxid, $newctxid, $progress) {
+        if (!isset($this->qtypecomponentscache[$qtype])) {
+            $this->qtypecomponentscache[$qtype] = backup_qtype_plugin::get_components_and_fileareas($qtype);
+        }
+        $components = $this->qtypecomponentscache[$qtype];
+        foreach ($components as $component => $fileareas) {
+            foreach ($fileareas as $filearea => $mapping) {
+                restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
+                        $oldctxid, $this->task->get_userid(), $mapping, null, $newctxid, true, $progress);
+            }
+        }
+    }
+}
 
 /**
  * Try to restore aliases and references to external files.
index 6202906..4ceb221 100644 (file)
@@ -103,7 +103,18 @@ abstract class backup_structure_dbops extends backup_dbops {
         }
     }
 
-    public static function annotate_files($backupid, $contextid, $component, $filearea, $itemid) {
+    /**
+     * Adds backup id database record for all files in the given file area.
+     *
+     * @param string $backupid Backup ID
+     * @param int $contextid Context id
+     * @param string $component Component
+     * @param string $filearea File area
+     * @param int $itemid Item id
+     * @param \core\progress\base $progress
+     */
+    public static function annotate_files($backupid, $contextid, $component, $filearea, $itemid,
+            \core\progress\base $progress = null) {
         global $DB;
         $sql = 'SELECT id
                   FROM {files}
@@ -120,10 +131,19 @@ abstract class backup_structure_dbops extends backup_dbops {
             $sql .= ' AND itemid = ?';
             $params[] = $itemid;
         }
+        if ($progress) {
+            $progress->start_progress('');
+        }
         $rs = $DB->get_recordset_sql($sql, $params);
         foreach ($rs as $record) {
+            if ($progress) {
+                $progress->progress();
+            }
             self::insert_backup_ids_record($backupid, 'file', $record->id);
         }
+        if ($progress) {
+            $progress->end_progress();
+        }
         $rs->close();
     }
 
index 2cc41dc..9085af0 100644 (file)
@@ -86,7 +86,7 @@ class backup_structure_processor extends base_processor {
                 foreach ($area as $filearea => $info) {
                     $contextid = !is_null($info->contextid) ? $info->contextid : $this->get_var(backup::VAR_CONTEXTID);
                     $itemid    = !is_null($info->element) ? $info->element->get_value() : null;
-                    backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid);
+                    backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid, $this->progress);
                 }
             }
         }
diff --git a/blocks/navigation/tests/behat/expand_courses_node.feature b/blocks/navigation/tests/behat/expand_courses_node.feature
new file mode 100644 (file)
index 0000000..2fd28ca
--- /dev/null
@@ -0,0 +1,190 @@
+@block @block_navigation
+Feature: Expand the courses nodes within the navigation block
+  In order to navigate the site
+  As an anonymous user, a guest, a student, and an admin
+  I need to expand the courses node in the navigation block and check the display of courses and categories.
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@local.host |
+      | student1 | Student | 1 | student1@local.host |
+    And the following "categories" exist:
+      | name   | category | idnumber | visible |
+      | cat1   | 0        | cat1     | 1       |
+      | cat2   | 0        | cat2     | 1       |
+      | cat21  | cat2     | cat21    | 1       |
+      | cat211 | cat21    | cat211   | 1       |
+      | cat3   | 0        | cat3     | 0       |
+    And the following "courses" exist:
+      | fullname  | shortname | category | visible |
+      | Course 1  | c1        | cat1     | 1       |
+      | Course 2  | c2        | cat2     | 1       |
+      | Course 3  | c3        | cat21    | 1       |
+      | Course 4  | c4        | cat211   | 1       |
+      | Course 5  | c5        | cat211   | 0       |
+      | Course 6  | c6        | cat211   | 0       |
+      | Course 7  | c7        | cat3     | 1       |
+      | Course 8  | c8        | cat3     | 0       |
+    And the following "course enrolments" exist:
+      | user     | course | role    |
+      | teacher1 | c1     | teacher |
+      | teacher1 | c3     | teacher |
+      | teacher1 | c5     | teacher |
+      | student1 | c1     | student |
+      | student1 | c2     | student |
+      | student1 | c4     | student |
+    And I log in as "admin"
+    And I follow "Course 2"
+    And I turn editing mode on
+    And I click on "Edit settings" "link" in the "Administration" "block"
+    And I set the following fields to these values:
+      | Allow guest access | Yes |
+    And I press "Save changes"
+    And I set the following administration settings values:
+      | Show all courses | 1 |
+    And I log out
+
+  @javascript
+  Scenario: As an anonymous user I expand the courses node to see courses.
+    When I should see "You are not logged in." in the ".logininfo" "css_element"
+    And I should see "Home" in the "Navigation" "block"
+    And I should see "Courses" in the "Navigation" "block"
+    And I expand "Courses" node
+    And I should see "cat1" in the "Navigation" "block"
+    And I should see "cat2" in the "Navigation" "block"
+    And I should not see "cat3" in the "Navigation" "block"
+    And I expand "cat1" node
+    And I expand "cat2" node
+    And I should see "cat21" in the "Navigation" "block"
+    And I expand "cat21" node
+    And I should see "cat211" in the "Navigation" "block"
+    And I expand "cat211" node
+    Then I should see "c1" in the "Navigation" "block"
+    And I should see "c2" in the "Navigation" "block"
+    And I should see "c3" in the "Navigation" "block"
+    And I should see "c4" in the "Navigation" "block"
+    And I should not see "c5" in the "Navigation" "block"
+    And I should not see "c6" in the "Navigation" "block"
+    And navigation node "c1" should not be expandable
+    And navigation node "c2" should not be expandable
+    And navigation node "c3" should not be expandable
+    And navigation node "c4" should not be expandable
+
+  @javascript
+  Scenario: As the admin user I expand the courses and category nodes to see courses.
+    When I log in as "admin"
+    And I should see "Home" in the "Navigation" "block"
+    And I should see "Courses" in the "Navigation" "block"
+    And I expand "Courses" node
+    And I should see "cat1" in the "Navigation" "block"
+    And I should see "cat2" in the "Navigation" "block"
+    And I should see "cat3" in the "Navigation" "block"
+    And I expand "cat1" node
+    And I expand "cat2" node
+    And I expand "cat3" node
+    And I should see "cat21" in the "Navigation" "block"
+    And I expand "cat21" node
+    And I should see "cat211" in the "Navigation" "block"
+    And I expand "cat211" node
+    Then I should see "c1" in the "Navigation" "block"
+    And I should see "c2" in the "Navigation" "block"
+    And I should see "c3" in the "Navigation" "block"
+    And I should see "c4" in the "Navigation" "block"
+    And I should see "c5" in the "Navigation" "block"
+    And I should see "c6" in the "Navigation" "block"
+    And I should see "c7" in the "Navigation" "block"
+    And I should see "c8" in the "Navigation" "block"
+    And navigation node "c1" should be expandable
+    And navigation node "c2" should be expandable
+    And navigation node "c3" should be expandable
+    And navigation node "c4" should be expandable
+    And navigation node "c5" should be expandable
+    And navigation node "c6" should be expandable
+    And navigation node "c7" should be expandable
+    And navigation node "c8" should be expandable
+
+  @javascript
+  Scenario: As teacher1 I expand the courses and category nodes to see courses.
+    When I log in as "teacher1"
+    And I should see "Home" in the "Navigation" "block"
+    And I should see "Courses" in the "Navigation" "block"
+    And I expand "Courses" node
+    And I should see "cat1" in the "Navigation" "block"
+    And I should see "cat2" in the "Navigation" "block"
+    And I should not see "cat3" in the "Navigation" "block"
+    And I expand "cat1" node
+    And I expand "cat2" node
+    And I should see "cat21" in the "Navigation" "block"
+    And I expand "cat21" node
+    And I should see "cat211" in the "Navigation" "block"
+    And I expand "cat211" node
+    Then I should see "c1" in the "Navigation" "block"
+    And I should see "c2" in the "Navigation" "block"
+    And I should see "c3" in the "Navigation" "block"
+    And I should see "c4" in the "Navigation" "block"
+    And I should see "c5" in the "Navigation" "block"
+    And I should not see "c6" in the "Navigation" "block"
+    And I should not see "c7" in the "Navigation" "block"
+    And I should not see "c8" in the "Navigation" "block"
+    And navigation node "c1" should be expandable
+    And navigation node "c2" should be expandable
+    And navigation node "c3" should be expandable
+    And navigation node "c4" should not be expandable
+    And navigation node "c5" should be expandable
+
+  @javascript
+  Scenario: As student1 I expand the courses and category nodes to see courses.
+    When I log in as "student1"
+    And I should see "Home" in the "Navigation" "block"
+    And I should see "Courses" in the "Navigation" "block"
+    And I expand "Courses" node
+    And I should see "cat1" in the "Navigation" "block"
+    And I should see "cat2" in the "Navigation" "block"
+    And I should not see "cat3" in the "Navigation" "block"
+    And I expand "cat1" node
+    And I expand "cat2" node
+    And I should see "cat21" in the "Navigation" "block"
+    And I expand "cat21" node
+    And I should see "cat211" in the "Navigation" "block"
+    And I expand "cat211" node
+    Then I should see "c1" in the "Navigation" "block"
+    And I should see "c2" in the "Navigation" "block"
+    And I should see "c3" in the "Navigation" "block"
+    And I should see "c4" in the "Navigation" "block"
+    And I should not see "c5" in the "Navigation" "block"
+    And I should not see "c6" in the "Navigation" "block"
+    And I should not see "c7" in the "Navigation" "block"
+    And I should not see "c8" in the "Navigation" "block"
+    And navigation node "c1" should be expandable
+    And navigation node "c2" should be expandable
+    And navigation node "c3" should not be expandable
+    And navigation node "c4" should be expandable
+
+  @javascript
+  Scenario: As guest I expand the courses and category nodes to see courses.
+    When I log in as "guest"
+    And I should see "Home" in the "Navigation" "block"
+    And I should see "Courses" in the "Navigation" "block"
+    And I expand "Courses" node
+    And I should see "cat1" in the "Navigation" "block"
+    And I should see "cat2" in the "Navigation" "block"
+    And I should not see "cat3" in the "Navigation" "block"
+    And I expand "cat1" node
+    And I expand "cat2" node
+    And I should see "cat21" in the "Navigation" "block"
+    And I expand "cat21" node
+    And I should see "cat211" in the "Navigation" "block"
+    And I expand "cat211" node
+    Then I should see "c1" in the "Navigation" "block"
+    And I should see "c2" in the "Navigation" "block"
+    And I should see "c3" in the "Navigation" "block"
+    And I should see "c4" in the "Navigation" "block"
+    And I should not see "c5" in the "Navigation" "block"
+    And I should not see "c6" in the "Navigation" "block"
+    And I should not see "c7" in the "Navigation" "block"
+    And I should not see "c8" in the "Navigation" "block"
+    And navigation node "c1" should not be expandable
+    And navigation node "c2" should be expandable
+    And navigation node "c3" should not be expandable
+    And navigation node "c4" should not be expandable
\ No newline at end of file
index 4c8ad1b..a629d9e 100644 (file)
@@ -87,8 +87,6 @@ Feature: View my courses in navigation block
     When I expand "cat3" node
     And I expand "cat31" node
     And I expand "cat1" node
-    And I should see "c1" in the "Navigation" "block"
-    And I expand "c1" node
     Then I should see "cat1" in the "Navigation" "block"
     And I should see "cat2" in the "Navigation" "block"
     And I should see "cat3" in the "Navigation" "block"
diff --git a/blocks/news_items/tests/behat/display_news.feature b/blocks/news_items/tests/behat/display_news.feature
new file mode 100644 (file)
index 0000000..1768980
--- /dev/null
@@ -0,0 +1,45 @@
+@block @block_news_items
+Feature: Latest news block displays the course latest news
+  In order to be aware of the course news
+  As a user
+  I need to see the latest news in the main course page
+
+  @javascript
+  Scenario: Latest course news are displayed and can be configured
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And I log in as "admin"
+    And I create a course with:
+      | Course full name | Course 1 |
+      | Course short name | C1 |
+      | News items to show | 5 |
+    And I enrol "Teacher 1" user as "Teacher"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    When I add a new topic to "News forum" forum with:
+      | Subject | Discussion One |
+      | Message | Not important |
+    And I add a new topic to "News forum" forum with:
+      | Subject | Discussion Two |
+      | Message | Not important |
+    And I add a new topic to "News forum" forum with:
+      | Subject | Discussion Three |
+      | Message | Not important |
+    And I follow "Course 1"
+    Then I should see "Discussion One" in the "Latest news" "block"
+    And I should see "Discussion Two" in the "Latest news" "block"
+    And I should see "Discussion Three" in the "Latest news" "block"
+    And I follow "Edit settings"
+    And I set the following fields to these values:
+      | News items to show | 2 |
+    And I press "Save changes"
+    And I should not see "Discussion One" in the "Latest news" "block"
+    And I should see "Discussion Two" in the "Latest news" "block"
+    And I should see "Discussion Three" in the "Latest news" "block"
+    And I follow "Edit settings"
+    And I set the following fields to these values:
+      | News items to show | 0 |
+    And I press "Save changes"
+    And "Latest news" "block" should not exist
index 7e9cd59..7745f0e 100644 (file)
@@ -246,3 +246,23 @@ The following snippet illustates how to configure the three core cache stores th
     define('TEST_CACHESTORE_MEMCACHE_TESTSERVERS', '127.0.0.1:11211');
     define('TEST_CACHESTORE_MEMCACHED_TESTSERVERS', '127.0.0.1:11211');
     define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
+
+As of Moodle 2.8 it is also possible to set the default cache stores used when running unit tests.
+You can do this by adding the following define to your config.php file:
+
+    // xxx is one of Memcache, Memecached, mongodb or other cachestore with a test define.
+    define('TEST_CACHE_USING_APPLICATION_STORE', 'xxx');
+
+This allows you to run tests against a defined test store. It uses the defined value to identify a store to test against with a matching TEST_CACHESTORE define.
+Alternatively you can also run unit tests against an actual cache config.
+To do this you must add the following to your config.php file:
+
+    define('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH', true');
+    $CFG->altcacheconfigpath = '/a/temp/directory/yoursite.php'
+
+This tells Moodle to use the config at $CFG->altcacheconfigpath when running unit tests.
+There are a couple of considerations to using this method:
+* By setting $CFG->altcacheconfigpath your site will store the cache config in the specified path, not just the unit test cache config but your site config as well.
+* If you have configured your cache before setting $CFG->altcacheconfigpath you will need to copy it from moodledata/muc/config.php to the destination you specified.
+* This allows you to share a cache config between sites.
+* It also allows you to use unit tests to test your sites cache config.
index 322035f..5fdf2b9 100644 (file)
@@ -100,8 +100,8 @@ class cache_config {
      * @return bool True if it exists
      */
     public static function config_file_exists() {
-        // Allow for late static binding.
-        return file_exists(self::get_config_file_path());
+        // Allow for late static binding by using static.
+        return file_exists(static::get_config_file_path());
     }
 
     /**
@@ -324,7 +324,8 @@ class cache_config {
      */
     protected function include_configuration() {
         $configuration = array();
-        $cachefile = self::get_config_file_path();
+        // We need to allow for late static bindings to allow for class path mudling happending for unit tests.
+        $cachefile = static::get_config_file_path();
 
         if (!file_exists($cachefile)) {
             throw new cache_exception('Default cache config could not be found. It should have already been created by now.');
index 825a441..6cf975f 100644 (file)
@@ -126,6 +126,14 @@ class cache_factory {
                 // situation. It will use disabled alternatives where available.
                 require_once($CFG->dirroot.'/cache/disabledlib.php');
                 self::$instance = new cache_factory_disabled();
+            } else if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
+                // We're using the regular factory.
+                require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
+                self::$instance = new cache_phpunit_factory();
+                if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) {
+                    // The cache stores have been disabled.
+                    self::$instance->set_state(self::STATE_STORES_DISABLED);
+                }
             } else {
                 // We're using the regular factory.
                 self::$instance = new cache_factory();
@@ -325,18 +333,12 @@ class cache_factory {
     public function create_config_instance($writer = false) {
         global $CFG;
 
-        // Check if we need to create a config file with defaults.
-        $needtocreate = !cache_config::config_file_exists();
-
         // The class to use.
         $class = 'cache_config';
-        if ($writer || $needtocreate) {
-            require_once($CFG->dirroot.'/cache/locallib.php');
-            $class .= '_writer';
-        }
+        $unittest = defined('PHPUNIT_TEST') && PHPUNIT_TEST;
 
         // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
-        if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
+        if ($unittest) {
             require_once($CFG->dirroot.'/cache/locallib.php');
             require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
             // We have just a single class for PHP unit tests. We don't care enough about its
@@ -345,11 +347,22 @@ class cache_factory {
             $class = 'cache_config_phpunittest';
         }
 
+        // Check if we need to create a config file with defaults.
+        $needtocreate = !$class::config_file_exists();
+
+        if ($writer || $needtocreate) {
+            require_once($CFG->dirroot.'/cache/locallib.php');
+            if (!$unittest) {
+                $class .= '_writer';
+            }
+        }
+
         $error = false;
         if ($needtocreate) {
             // Create the default configuration.
             // Update the state, we are now initialising the cache.
             self::set_state(self::STATE_INITIALISING);
+            /** @var cache_config_writer $class */
             $configuration = $class::create_default_configuration();
             if ($configuration !== true) {
                 // Failed to create the default configuration. Disable the cache stores and update the state.
index ab22575..22c20db 100644 (file)
@@ -79,6 +79,17 @@ interface cache_store_interface {
      * @return cache_store|false
      */
     public static function initialise_test_instance(cache_definition $definition);
+
+    /**
+     * Initialises a test instance for unit tests.
+     *
+     * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
+     *
+     * @since 2.8
+     * @param cache_definition $definition
+     * @return cache_store|false
+     */
+    public static function initialise_unit_test_instance(cache_definition $definition);
 }
 
 /**
@@ -340,4 +351,18 @@ abstract class cache_store implements cache_store_interface {
         // Any stores that have an issue with this will need to override the create_clone method.
         return clone($this);
     }
+
+    /**
+     * Initialises a test instance for unit tests.
+     *
+     * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
+     * By default however it calls initialise_test_instance to support backwards compatability.
+     *
+     * @since 2.8
+     * @param cache_definition $definition
+     * @return cache_store|false
+     */
+    public static function initialise_unit_test_instance(cache_definition $definition) {
+        return self::initialise_test_instance($definition);
+    }
 }
index c7948cf..042b891 100644 (file)
@@ -68,7 +68,7 @@ class cache_config_writer extends cache_config {
      */
     protected function config_save() {
         global $CFG;
-        $cachefile = self::get_config_file_path();
+        $cachefile = static::get_config_file_path();
         $directory = dirname($cachefile);
         if ($directory !== $CFG->dataroot && !file_exists($directory)) {
             $result = make_writable_directory($directory, false);
index 21e8f6c..42db75b 100644 (file)
@@ -430,6 +430,27 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
         return $store;
     }
 
+    /**
+     * Creates a test instance for unit tests if possible.
+     * @param cache_definition $definition
+     * @return bool|cachestore_memcache
+     */
+    public static function initialise_unit_test_instance(cache_definition $definition) {
+        if (!self::are_requirements_met()) {
+            return false;
+        }
+        if (!defined('TEST_CACHESTORE_MEMCACHE_TESTSERVERS')) {
+            return false;
+        }
+        $configuration = array();
+        $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHE_TESTSERVERS);
+
+        $store = new cachestore_memcache('Test memcache', $configuration);
+        $store->initialise($definition);
+
+        return $store;
+    }
+
     /**
      * Returns the name of this instance.
      * @return string
index 4e3c2aa..62b52f4 100644 (file)
@@ -42,16 +42,6 @@ require_once($CFG->dirroot.'/cache/stores/memcache/lib.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class cachestore_memcache_test extends cachestore_tests {
-    /**
-     * Prepare to run tests.
-     */
-    public function setUp() {
-        if (defined('TEST_CACHESTORE_MEMCACHE_TESTSERVERS')) {
-            set_config('testservers', TEST_CACHESTORE_MEMCACHE_TESTSERVERS, 'cachestore_memcache');
-            $this->resetAfterTest();
-        }
-        parent::setUp();
-    }
     /**
      * Returns the memcache class name
      * @return string
@@ -65,7 +55,7 @@ class cachestore_memcache_test extends cachestore_tests {
      */
     public function test_valid_keys() {
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcache', 'phpunit_test');
-        $instance = cachestore_memcache::initialise_test_instance($definition);
+        $instance = cachestore_memcache::initialise_unit_test_instance($definition);
 
         if (!$instance) { // Something prevented memcache store to be inited (extension, TEST_CACHESTORE_MEMCACHE_TESTSERVERS...).
             $this->markTestSkipped();
index 2c601fe..1570a70 100644 (file)
@@ -487,6 +487,28 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
         return $store;
     }
 
+    /**
+     * Creates a test instance for unit tests if possible.
+     * @param cache_definition $definition
+     * @return bool|cachestore_memcached
+     */
+    public static function initialise_unit_test_instance(cache_definition $definition) {
+        if (!self::are_requirements_met()) {
+            return false;
+        }
+        if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
+            return false;
+        }
+
+        $configuration = array();
+        $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS);
+
+        $store = new cachestore_memcached('Test memcached', $configuration);
+        $store->initialise($definition);
+
+        return $store;
+    }
+
     /**
      * Returns the name of this instance.
      * @return string
index 80a918a..a6c3496 100644 (file)
@@ -42,16 +42,6 @@ require_once($CFG->dirroot.'/cache/stores/memcached/lib.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class cachestore_memcached_test extends cachestore_tests {
-    /**
-     * Prepare to run tests.
-     */
-    public function setUp() {
-        if (defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
-            set_config('testservers', TEST_CACHESTORE_MEMCACHED_TESTSERVERS, 'cachestore_memcached');
-            $this->resetAfterTest();
-        }
-        parent::setUp();
-    }
     /**
      * Returns the memcached class name
      * @return string
@@ -65,7 +55,7 @@ class cachestore_memcached_test extends cachestore_tests {
      */
     public function test_valid_keys() {
         $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
-        $instance = cachestore_memcached::initialise_test_instance($definition);
+        $instance = cachestore_memcached::initialise_unit_test_instance($definition);
 
         if (!$instance) { // Something prevented memcached store to be inited (extension, TEST_CACHESTORE_MEMCACHED_TESTSERVERS...).
             $this->markTestSkipped();
index f30323d..db8653f 100644 (file)
@@ -561,6 +561,30 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
         return $store;
     }
 
+
+    /**
+     * Generates an instance of the cache store that can be used for testing.
+     *
+     * @param cache_definition $definition
+     * @return false
+     */
+    public static function initialise_unit_test_instance(cache_definition $definition) {
+        if (!self::are_requirements_met()) {
+            return false;
+        }
+        if (!defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
+            return false;
+        }
+
+        $configuration = array();
+        $configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
+
+        $store = new cachestore_mongodb('Test mongodb', $configuration);
+        $store->initialise($definition);
+
+        return $store;
+    }
+
     /**
      * Returns the name of this instance.
      * @return string
index b1a3390..97bfb9d 100644 (file)
@@ -42,16 +42,6 @@ require_once($CFG->dirroot.'/cache/stores/mongodb/lib.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class cachestore_mongodb_test extends cachestore_tests {
-    /**
-     * Prepare to run tests.
-     */
-    public function setUp() {
-        if (defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
-            set_config('testserver', TEST_CACHESTORE_MONGODB_TESTSERVER, 'cachestore_mongodb');
-            $this->resetAfterTest();
-        }
-        parent::setUp();
-    }
     /**
      * Returns the MongoDB class name
      * @return string
index c30ae0c..68f8d95 100644 (file)
@@ -165,9 +165,9 @@ class core_cache_administration_helper_testcase extends advanced_testcase {
      */
     public function test_get_edit_store_form() {
         $config = cache_config_writer::instance();
-        $this->assertTrue($config->add_store_instance('summariesstore', 'file'));
+        $this->assertTrue($config->add_store_instance('test_get_edit_store_form', 'file'));
 
-        $form = cache_administration_helper::get_edit_store_form('file', 'summariesstore');
+        $form = cache_administration_helper::get_edit_store_form('file', 'test_get_edit_store_form');
         $this->assertInstanceOf('moodleform', $form);
 
         try {
index 5a85f14..9e00372 100644 (file)
@@ -58,10 +58,37 @@ class core_cache_testcase extends advanced_testcase {
         cache_factory::reset();
     }
 
+    /**
+     * Returns the expected application cache store.
+     * @return string
+     */
+    protected function get_expected_application_cache_store() {
+        $expected = 'cachestore_file';
+        if (defined('TEST_CACHE_USING_APPLICATION_STORE') && preg_match('#[a-zA-Z][a-zA-Z0-9_]*#', TEST_CACHE_USING_APPLICATION_STORE)) {
+            $expected = 'cachestore_'.(string)TEST_CACHE_USING_APPLICATION_STORE;
+        }
+        return $expected;
+    }
+
     /**
      * Tests cache configuration
      */
     public function test_cache_config() {
+        global $CFG;
+
+        if (defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH &&
+            !empty($CFG->altcacheconfigpath)) {
+            // We need to skip this test - it checks the default config structure, but very likely we arn't using the
+            // default config structure here so theres no point in running the test.
+            $this->markTestSkipped('Skipped testing default cache config structure as alt cache path is being used.');
+        }
+
+        if (defined('TEST_CACHE_USING_APPLICATION_STORE')) {
+            // We need to skip this test - it checks the default config structure, but very likely we arn't using the
+            // default config structure here because we are testing against an alternative application store.
+            $this->markTestSkipped('Skipped testing default cache config structure as alt application store is being used.');
+        }
+
         $instance = cache_config::instance();
         $this->assertInstanceOf('cache_config_phpunittest', $instance);
 
@@ -474,27 +501,29 @@ class core_cache_testcase extends advanced_testcase {
      * Test the mappingsonly setting.
      */
     public function test_definition_mappings_only() {
+        /** @var cache_config_phpunittest $instance */
         $instance = cache_config_phpunittest::instance(true);
         $instance->phpunit_add_definition('phpunit/mappingsonly', array(
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'mappingsonly',
             'mappingsonly' => true
-        ));
+        ), false);
         $instance->phpunit_add_definition('phpunit/nonmappingsonly', array(
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'nonmappingsonly',
             'mappingsonly' => false
-        ));
+        ), false);
 
         $cacheonly = cache::make('phpunit', 'mappingsonly');
         $this->assertInstanceOf('cache_application', $cacheonly);
         $this->assertEquals('cachestore_dummy', $cacheonly->phpunit_get_store_class());
 
+        $expected = $this->get_expected_application_cache_store();
         $cachenon = cache::make('phpunit', 'nonmappingsonly');
         $this->assertInstanceOf('cache_application', $cachenon);
-        $this->assertEquals('cachestore_file', $cachenon->phpunit_get_store_class());
+        $this->assertEquals($expected, $cachenon->phpunit_get_store_class());
     }
 
     /**
@@ -1074,6 +1103,9 @@ class core_cache_testcase extends advanced_testcase {
      */
     public function test_alt_cache_path() {
         global $CFG;
+        if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
+            $this->markTestSkipped('Skipped testing alt cache path as it is already being used.');
+        }
         $this->resetAfterTest();
         $CFG->altcacheconfigpath = $CFG->dataroot.'/cache/altcacheconfigpath';
         $instance = cache_config_phpunittest::instance();
@@ -1150,6 +1182,12 @@ class core_cache_testcase extends advanced_testcase {
     public function test_disable() {
         global $CFG;
 
+        if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
+            // We can't run this test as it requires us to delete the cache configuration script which we just
+            // cant do with a custom path in play.
+            $this->markTestSkipped('Skipped testing cache disable functionality as alt cache path is being used.');
+        }
+
         $configfile = $CFG->dataroot.'/muc/config.php';
 
         // That's right, we're deleting the config file.
index e6ecbe0..0e662cb 100644 (file)
@@ -28,6 +28,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+require_once($CFG->dirroot.'/cache/locallib.php');
+
 /**
  * Override the default cache configuration for our own maniacle purposes.
  *
@@ -36,12 +38,143 @@ defined('MOODLE_INTERNAL') || die();
  */
 class cache_config_phpunittest extends cache_config_writer {
 
+    /**
+     * Creates the default configuration and saves it.
+     *
+     * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
+     * be called when there is no configuration file already.
+     *
+     * @param bool $forcesave If set to true then we will forcefully save the default configuration file.
+     * @return true|array Returns true if the default configuration was successfully created.
+     *     Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs.
+     */
+    public static function create_default_configuration($forcesave = false) {
+        global $CFG;
+        // HACK ALERT.
+        // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
+        // default store plugins are protected from deletion.
+        $writer = new self;
+        $writer->configstores = self::get_default_stores();
+        $writer->configdefinitions = self::locate_definitions();
+        $defaultapplication = 'default_application';
+
+        $appdefine = defined('TEST_CACHE_USING_APPLICATION_STORE') ? TEST_CACHE_USING_APPLICATION_STORE : false;
+        if ($appdefine !== false && preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $appdefine)) {
+            $expectedstore = $appdefine;
+            $expecteddefine = 'TEST_CACHESTORE_'.strtoupper($expectedstore).'_TESTSERVERS';
+            $file = $CFG->dirroot.'/cache/stores/'.$appdefine.'/lib.php';
+            $class = 'cachestore_'.$appdefine;
+            if (file_exists($file)) {
+                require_once($file);
+            }
+            if (defined($expecteddefine) && class_exists($class)) {
+                /** @var cache_store $class */
+                $writer->configstores['test_application'] = array(
+                    'use_test_store' => true,
+                    'name' => 'test_application',
+                    'plugin' => $expectedstore,
+                    'alt' => $writer->configstores[$defaultapplication],
+                    'modes' => $class::get_supported_modes(),
+                    'features' => $class::get_supported_features()
+                );
+                $defaultapplication = 'test_application';
+            }
+        }
+
+        $writer->configmodemappings = array(
+            array(
+                'mode' => cache_store::MODE_APPLICATION,
+                'store' => $defaultapplication,
+                'sort' => -1
+            ),
+            array(
+                'mode' => cache_store::MODE_SESSION,
+                'store' => 'default_session',
+                'sort' => -1
+            ),
+            array(
+                'mode' => cache_store::MODE_REQUEST,
+                'store' => 'default_request',
+                'sort' => -1
+            )
+        );
+        $writer->configlocks = array(
+            'default_file_lock' => array(
+                'name' => 'cachelock_file_default',
+                'type' => 'cachelock_file',
+                'dir' => 'filelocks',
+                'default' => true
+            )
+        );
+
+        $factory = cache_factory::instance();
+        // We expect the cache to be initialising presently. If its not then something has gone wrong and likely
+        // we are now in a loop.
+        if (!$forcesave && $factory->get_state() !== cache_factory::STATE_INITIALISING) {
+            return $writer->generate_configuration_array();
+        }
+        $factory->set_state(cache_factory::STATE_SAVING);
+        $writer->config_save();
+        return true;
+    }
+
+    /**
+     * Returns the expected path to the configuration file.
+     *
+     * We override this function to add handling for $CFG->altcacheconfigpath.
+     * We want to support it so that people can run unit tests against alternative cache setups.
+     * However we don't want to ever make changes to the file at $CFG->altcacheconfigpath so we
+     * always use dataroot and copy the alt file there as required.
+     *
+     * @throws cache_exception
+     * @return string The absolute path
+     */
+    protected static function get_config_file_path() {
+        global $CFG;
+        // We always use this path.
+        $configpath = $CFG->dataroot.'/muc/config.php';
+
+        if (!empty($CFG->altcacheconfigpath)) {
+
+            if  (defined('PHPUNIT_TEST') && PHPUNIT_TEST &&
+                (!defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') || !TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH)) {
+                // We're within a unit test, but TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH has not being defined or is
+                // false, we want to use the default.
+                return $configpath;
+            }
+
+            $path = $CFG->altcacheconfigpath;
+            if (is_dir($path) && is_writable($path)) {
+                // Its a writable directory, thats fine. Convert it to a file.
+                $path = $CFG->altcacheconfigpath.'/cacheconfig.php';
+            }
+            if (is_readable($path)) {
+                $directory = dirname($configpath);
+                if ($directory !== $CFG->dataroot && !file_exists($directory)) {
+                    $result = make_writable_directory($directory, false);
+                    if (!$result) {
+                        throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Cannot create config directory. Check the permissions on your moodledata directory.');
+                    }
+                }
+                // We don't care that this fails but we should let the developer know.
+                if (!is_readable($configpath) && !@copy($path, $configpath)) {
+                    debugging('Failed to copy alt cache config file to required location');
+                }
+            }
+        }
+
+        // We always use the dataroot location.
+        return $configpath;
+    }
+
     /**
      * Adds a definition to the stack
      * @param string $area
      * @param array $properties
+     * @param bool $addmapping By default this method adds a definition and a mapping for that definition. You can
+     *    however set this to false if you only want it to add the definition and not the mapping.
      */
-    public function phpunit_add_definition($area, array $properties) {
+    public function phpunit_add_definition($area, array $properties, $addmapping = true) {
         if (!array_key_exists('overrideclass', $properties)) {
             switch ($properties['mode']) {
                 case cache_store::MODE_APPLICATION:
@@ -56,6 +189,19 @@ class cache_config_phpunittest extends cache_config_writer {
             }
         }
         $this->configdefinitions[$area] = $properties;
+        if ($addmapping) {
+            switch ($properties['mode']) {
+                case cache_store::MODE_APPLICATION:
+                    $this->phpunit_add_definition_mapping($area, 'default_application', 0);
+                    break;
+                case cache_store::MODE_SESSION:
+                    $this->phpunit_add_definition_mapping($area, 'default_session', 0);
+                    break;
+                case cache_store::MODE_REQUEST:
+                    $this->phpunit_add_definition_mapping($area, 'default_request', 0);
+                    break;
+            }
+        }
     }
 
     /**
@@ -343,4 +489,34 @@ class cache_phpunit_factory extends cache_factory {
     public static function phpunit_disable() {
         parent::disable();
     }
+
+    /**
+     * Creates a store instance given its name and configuration.
+     *
+     * If the store has already been instantiated then the original object will be returned. (reused)
+     *
+     * @param string $name The name of the store (must be unique remember)
+     * @param array $details
+     * @param cache_definition $definition The definition to instantiate it for.
+     * @return boolean|cache_store
+     */
+    public function create_store_from_config($name, array $details, cache_definition $definition) {
+
+        if (isset($details['use_test_store'])) {
+            // name, plugin, alt
+            $class = 'cachestore_'.$details['plugin'];
+            $method = 'initialise_unit_test_instance';
+            if (class_exists($class) && method_exists($class, $method)) {
+                $instance = $class::$method($definition);
+
+                if ($instance) {
+                    return $instance;
+                }
+            }
+            $details = $details['alt'];
+            $name = $details['name'];
+        }
+
+        return parent::create_store_from_config($name, $details, $definition);
+    }
 }
\ No newline at end of file
index 6aa17f3..b6f5246 100644 (file)
@@ -8,6 +8,6 @@
     "require-dev": {
         "phpunit/phpunit": "3.7.*",
         "phpunit/dbUnit": "1.2.*",
-        "moodlehq/behat-extension": "1.28.3"
+        "moodlehq/behat-extension": "1.28.4"
     }
 }
index c182b87..82b1295 100644 (file)
@@ -711,6 +711,40 @@ $CFG->admin = 'admin';
 // generated by this tool, but only in case you want to generate a JMeter test. The value should be a string.
 // Example:
 //   $CFG->tool_generator_users_password = 'examplepassword';
+//
+//=========================================================================
+// 13. SYSTEM PATHS (You need to set following, depending on your system)
+//=========================================================================
+// Ghostscript path.
+// On most Linux installs, this can be left as '/usr/bin/gs'.
+// On Windows it will be something like 'c:\gs\bin\gswin32c.exe' (make sure
+// there are no spaces in the path - if necessary copy the files 'gswin32c.exe'
+// and 'gsdll32.dll' to a new folder without a space in the path)
+//      $CFG->pathtogs = '/usr/bin/gs';
+//
+// Clam AV path.
+// Probably something like /usr/bin/clamscan or /usr/bin/clamdscan. You need
+// this in order for clam AV to run.
+//      $CFG->pathtoclam = '';
+//
+// Path to du.
+// Probably something like /usr/bin/du. If you enter this, pages that display
+// directory contents will run much faster for directories with a lot of files.
+//      $CFG->pathtodu = '';
+//
+// Path to aspell.
+// To use spell-checking within the editor, you MUST have aspell 0.50 or later
+// installed on your server, and you must specify the correct path to access the
+// aspell binary. On Unix/Linux systems, this path is usually /usr/bin/aspell,
+// but it might be something else.
+//      $CFG->aspellpath = '';
+//
+// Path to dot.
+// Probably something like /usr/bin/dot. To be able to generate graphics from
+// DOT files, you must have installed the dot executable and point to it here.
+// Note that, for now, this only used by the profiling features
+// (Development->Profiling) built into Moodle.
+//      $CFG->pathtodot = '';
 
 //=========================================================================
 // ALL DONE!  To continue installation, visit your main page with a browser
index fffbb93..9d6bc3e 100644 (file)
@@ -2,6 +2,10 @@ This files describes API changes for course formats
 
 Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
 
+=== 2.8 ===
+* The activity chooser now uses M.course.format.get_sectionwrapperclass()
+  to determine the section selector, rather than a hard-coded `li.section`.
+
 === 2.7 ===
 * The ->testedbrowsers array no longer needs to be defined in supports_ajax().
 * format_section_renderer_base::section_hidden has an new second optional argument $courseorid.
diff --git a/course/tests/behat/course_creation.feature b/course/tests/behat/course_creation.feature
new file mode 100644 (file)
index 0000000..668a50d
--- /dev/null
@@ -0,0 +1,31 @@
+@core @core_course
+Feature: Managers can create courses
+  In order to group users and contents
+  As a manager
+  I need to create courses and set default values on them
+
+  @javascript
+  Scenario: Courses are created with the default forum and blocks
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+    And I log in as "admin"
+    And I create a course with:
+      | Course full name | Course 1 |
+      | Course short name | C1 |
+    And I enrol "Teacher 1" user as "Teacher"
+    And I enrol "Student 1" user as "Student"
+    And I log out
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    Then "Latest news" "block" should exist
+    And I follow "News forum"
+    And "Add a new topic" "button" should exist
+    And "Forced subscription" "link" should not exist
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "News forum"
+    And "Add a new topic" "button" should not exist
+    And I should see "Forced subscription" in the "Administration" "block"
index 54312b4..1689ac9 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js differ
index 0425628..1f08287 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js differ
index 54312b4..1689ac9 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js differ
index da70ad8..ef6ac4d 100644 (file)
@@ -6,7 +6,7 @@
 
 var CSS = {
     PAGECONTENT : 'body',
-    SECTION : 'li.section',
+    SECTION: null,
     SECTIONMODCHOOSER : 'span.section-modchooser-link',
     SITEMENU : 'div.block_site_main_menu',
     SITETOPIC : 'div.sitetopic'
@@ -42,6 +42,10 @@ Y.extend(MODCHOOSER, M.core.chooserdialogue, {
      * @method initializer
      */
     initializer : function() {
+        var sectionclass = M.course.format.get_sectionwrapperclass();
+        if (sectionclass) {
+            CSS.SECTION = '.' + sectionclass;
+        }
         var dialogue = Y.one('.chooserdialoguebody');
         var header = Y.one('.choosertitle');
         var params = {};
@@ -73,9 +77,11 @@ Y.extend(MODCHOOSER, M.core.chooserdialogue, {
         }, this);
 
         // Setup for standard course topics
-        Y.one(baseselector).all(CSS.SECTION).each(function(section) {
-            this._setup_for_section(section);
-        }, this);
+        if (CSS.SECTION) {
+            Y.one(baseselector).all(CSS.SECTION).each(function(section) {
+                this._setup_for_section(section);
+            }, this);
+        }
 
         // Setup for the block site menu
         Y.one(baseselector).all(CSS.SITEMENU).each(function(section) {
index f3a99ec..b96c517 100644 (file)
@@ -58,4 +58,42 @@ class behat_enrol extends behat_base {
         );
     }
 
+    /**
+     * Enrols the specified user in the current course without options.
+     *
+     * This is a simple step, to set enrolment options would be better to
+     * create a separate step as a TableNode will be required.
+     *
+     * @Given /^I enrol "(?P<user_fullname_string>(?:[^"]|\\")*)" user as "(?P<rolename_string>(?:[^"]|\\")*)"$/
+     * @param string $userfullname
+     * @param string $rolename
+     * @return Given[]
+     */
+    public function i_enrol_user_as($userfullname, $rolename) {
+
+        $steps = array(
+            new Given('I follow "' . get_string('enrolledusers', 'enrol') . '"'),
+            new Given('I press "' . get_string('enrolusers', 'enrol') . '"')
+        );
+
+        if ($this->running_javascript()) {
+
+            // We have a div here, not a tr.
+            $userliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($userfullname);
+            $userrowxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' user ')][contains(., $userliteral)]";
+
+            $steps[] = new Given('I set the field "' . get_string('assignroles', 'role') . '" to "' . $rolename . '"');
+            $steps[] = new Given('I click on "' . get_string('enrol', 'enrol') . '" "button" in the "' . $userrowxpath . '" "xpath_element"');
+            $steps[] = new Given('I press "' . get_string('finishenrollingusers', 'enrol') . '"');
+
+        } else {
+
+            $steps[] = new Given('I set the field "' . get_string('assignrole', 'role') . '" to "' . $rolename . '"');
+            $steps[] = new Given('I set the field "addselect" to "' . $userfullname . '"');
+            $steps[] = new Given('I press "add"');
+        }
+
+        return $steps;
+    }
+
 }
index 06fe060..0a0bf07 100644 (file)
@@ -138,8 +138,8 @@ class behat_grading extends behat_base {
         // Should work with both templates and own forms.
         $literaltemplate = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('templatepick', 'grading'));
         $literalownform = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('templatepickownform', 'grading'));
-        $usetemplatexpath = "//a[./descendant::div[text()=$literaltemplate]]|" .
-            "//a[./descendant::div[text()=$literalownform]]";
+        $usetemplatexpath = "/a[./descendant::div[text()=$literaltemplate]]|" .
+            "/a[./descendant::div[text()=$literalownform]]";
 
         return array(
             new Given('I go to "' . $this->escape($activityname) . '" advanced grading page'),
diff --git a/install/lang/ckb/error.php b/install/lang/ckb/error.php
new file mode 100644 (file)
index 0000000..617cf84
--- /dev/null
@@ -0,0 +1,55 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['cannotcreatedboninstall'] = '<p>ناتوانرێت داتابەیس دروست بکرێت.</p>
+<p>داتابەیستی دیاری کراو بوونی نییەو بەکارهێنەری دراو مۆڵەتی دروست کردنی داتابەیسی نییە.</p>
+<p>پێویستە سەرپەرشتیاری ماڵپەڕ سازاندنی داتابەیس پەسەند بکات.</p>';
+$string['cannotcreatelangdir'] = 'ناتوانریت ڕابەرنامەی زمان دروست بکرێت';
+$string['cannotcreatetempdir'] = 'ناتوانرێت ڕابەرنامەی کاتیی دروست بکرێت';
+$string['cannotdownloadcomponents'] = 'ناتوانرێت پێکهێنەرەکان داببەزێنرێن';
+$string['cannotdownloadzipfile'] = 'ناتوانریت فایلی زیپ داببەزێنرێت';
+$string['cannotfindcomponent'] = 'تانتوانرێت پێکهێنەر بدۆزرێتەوە';
+$string['cannotsavemd5file'] = 'ناتوانرێت فایلی md5 پاشەکەوت بکرێت';
+$string['cannotsavezipfile'] = 'ناتوانرێت فایلی زیپ پاشەکەوت بکرێت';
+$string['cannotunzipfile'] = 'ناتوانرێت فایلەکە لە زیپ بهێنرێتە دەرەوە';
+$string['componentisuptodate'] = 'سەرنجەکە تەواو نوێیە';
+$string['dmlexceptiononinstall'] = '<p>هەڵە ڕویدا لە داتابەیس [{$a->errorcode}]. <br />{$a->debuginfo}</p>.
+
+<br />{$a->debuginfo}</p>';
+$string['downloadedfilecheckfailed'] = 'پشکنینی دابەزاندنی فایل سەرکەوتوو نەبوو';
+$string['invalidmd5'] = 'پشکنینی گۆڕاو هەڵە بو - دووبارە بکەوە';
+$string['missingrequiredfield'] = 'هەندێ بواری داواکراو دیاری نەکراون';
+$string['remotedownloaderror'] = '<p>دابەزاندنی پێکهێنەری ڕاژەکارەکەت سەرکەوتو نەبوو. تکایە ڕێکخستنەکانی پڕۆکسی پشت ڕاست بکەوە: درێژبوونەوەی PHP cURL بەشێوەیەکی زۆر پێشنیاز کراوە. </p>
+<p>پێویستە تۆ فایلی <a href="{$a->url}">{$a->url}</a> بەدەست دابەزێنی، کۆپی بکە بۆ "{$a->dest}" لە نێو ڕاژەکارەکەت و لەوێ لەنێو زیپ بیهێنە دەرەوە. </p>';
+$string['wrongdestpath'] = 'ڕێڕەوی ئامانج هەڵەیە';
+$string['wrongsourcebase'] = 'بنچینەی URLی سەرچاوە هەڵەیە';
+$string['wrongzipfilename'] = 'ناوی فایلی زیپ هەڵەیە';
index 2d58506..70b7785 100644 (file)
@@ -41,4 +41,4 @@ $string['cliunknowoption'] = 'Nicht erkannte Optionen:
 Hilfe wird über die Option --help angezeigt.';
 $string['cliyesnoprompt'] = 'y (yes=ja) oder n (no=nein) eingeben';
 $string['environmentrequireinstall'] = 'muss installiert und aktiviert sein';
-$string['environmentrequireversion'] = 'Version {$a->needed} ist erforderlich - aktuell ist {$a->current} installiert.';
+$string['environmentrequireversion'] = 'notwendig: {$a->needed} - installiert: {$a->current}';
index 94e2c61..62734b5 100644 (file)
@@ -774,6 +774,8 @@ $string['pathtoclam'] = 'clam AV path';
 $string['pathtodot'] = 'Path to dot';
 $string['pathtodot_help'] = 'Path to dot. Probably something like /usr/bin/dot. To be able to generate graphics from DOT files, you must have installed the dot executable and point to it here. Note that, for now, this only used by the profiling features (Development->Profiling) built into Moodle.';
 $string['pathtodu'] = 'Path to du';
+$string['pathtogs'] = 'Path to ghostscript';
+$string['pathtogs_help'] = 'On most Linux installs, this can be left as \'/usr/bin/gs\'. On Windows it will be something like \'c:\\gs\\bin\\gswin32c.exe\' (make sure there are no spaces in the path - if necessary copy the files \'gswin32c.exe\' and \'gsdll32.dll\' to a new folder without a space in the path)';
 $string['pathtopgdump'] = 'Path to pg_dump';
 $string['pathtopgdumpdesc'] = 'This is only necessary to enter if you have more than one pg_dump on your system (for example if you have more than one version of postgresql installed)';
 $string['pathtopgdumpinvalid'] = 'Invalid path to pg_dump - either wrong path or not executable';
index 6d4eab7..6037469 100644 (file)
@@ -51,6 +51,7 @@ $string['cachedef_groupdata'] = 'Course group information';
 $string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
 $string['cachedef_langmenu'] = 'List of available languages';
 $string['cachedef_locking'] = 'Locking';
+$string['cachedef_navigation_expandcourse'] = 'Navigation expandable courses';
 $string['cachedef_observers'] = 'Event observers';
 $string['cachedef_plugin_manager'] = 'Plugin info manager';
 $string['cachedef_questiondata'] = 'Question definitions';
index a843c19..b4e181f 100644 (file)
@@ -1603,6 +1603,7 @@ $string['searchhelp'] = '<p>You can search for multiple words at once and can re
 $string['searchoptions'] = 'Search options';
 $string['searchresults'] = 'Search results';
 $string['sec'] = 'sec';
+$string['secondsleft'] = '{$a} secs';
 $string['seconds'] = 'seconds';
 $string['secondstotime172800'] = '2 days';
 $string['secondstotime259200'] = '3 days';
index de3a5ae..7cc7761 100644 (file)
@@ -155,7 +155,7 @@ function behat_clean_init_config() {
         'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
         'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
         'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
-        'proxybypass', 'theme'
+        'proxybypass', 'theme', 'pathtogs', 'pathtoclam', 'pathtodu', 'aspellpath', 'pathtodot'
     ));
 
     // Add extra allowed settings.
index a2a3710..fa4befd 100644 (file)
@@ -71,7 +71,7 @@ abstract class comment_created extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' added the comment with id '$this->objectid' to the '$this->component' " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index ab3a2bd..0dbdcab 100644 (file)
@@ -71,7 +71,7 @@ abstract class comment_deleted extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' deleted the comment with id '$this->objectid' from the '$this->component' " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 0d3bef9..b5f2ff2 100644 (file)
@@ -60,8 +60,8 @@ class course_module_completion_updated extends base {
      * @return string
      */
     public function get_description() {
-        return "The course module completion requirements were updated by the user with id '$this->userid' for the user " .
-            "with id '$this->relateduserid'.";
+        return "The user with id '$this->userid' updated the completion state for the course module with id '$this->contextinstanceid' " .
+            "for the user with id '$this->relateduserid'.";
     }
 
     /**
index c8eb858..672a09a 100644 (file)
@@ -69,7 +69,7 @@ class course_module_created extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' created the '{$this->other['modulename']}' activity with the " .
+        return "The user with id '$this->userid' created the '{$this->other['modulename']}' activity with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index 6db1f2e..1bd888b 100644 (file)
@@ -68,7 +68,7 @@ class course_module_deleted extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' deleted the '{$this->other['modulename']}' activity with the " .
+        return "The user with id '$this->userid' deleted the '{$this->other['modulename']}' activity with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index d2508ae..b319df6 100644 (file)
@@ -69,7 +69,7 @@ class course_module_updated extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' updated the '{$this->other['modulename']}' activity with the " .
+        return "The user with id '$this->userid' updated the '{$this->other['modulename']}' activity with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index 1307b92..357e8e3 100644 (file)
@@ -55,7 +55,7 @@ abstract class course_module_viewed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the '{$this->objecttable}' activity with the " .
+        return "The user with id '$this->userid' viewed the '{$this->objecttable}' activity with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index 30dff69..59b60ec 100644 (file)
@@ -66,8 +66,8 @@ class user_enrolment_deleted extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' unenrolled the user with id '$this->relateduserid' from the course with " .
-            "id '$this->courseid'.";
+        return "The user with id '$this->userid' unenrolled the user with id '$this->relateduserid' using the enrolment method " .
+            "'{$this->other['enrol']}' from the course with id '$this->courseid'.";
     }
 
     /**
index 0570cf8..9d9d2e8 100644 (file)
@@ -65,8 +65,8 @@ class user_enrolment_updated extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' updated the enrolment for the user with id '$this->relateduserid' in " .
-            "the course with id '$this->courseid'.";
+        return "The user with id '$this->userid' updated the enrolment for the user with id '$this->relateduserid' using the " .
+            "enrolment method '{$this->other['enrol']}' in the course with id '$this->courseid'.";
     }
 
     /**
index f7fb050..47e93c7 100644 (file)
@@ -340,7 +340,7 @@ abstract class scheduled_task extends task_base {
         // otherwise - choose the soonest (see man 5 cron).
         if ($this->dayofweek == '*') {
             $daysincrement = $daysincrementbymonth;
-        } else if ($this->dayofmonth == '*') {
+        } else if ($this->day == '*') {
             $daysincrement = $daysincrementbyweek;
         } else {
             // Take the smaller increment of days by month or week.
index 7e0d40d..81094ba 100644 (file)
@@ -120,7 +120,12 @@ class core_useragent {
     protected function __construct($forceuseragent = null) {
         global $CFG;
         if (!empty($CFG->devicedetectregex)) {
-            $this->devicetypecustoms = json_decode($CFG->devicedetectregex);
+            $this->devicetypecustoms = json_decode($CFG->devicedetectregex, true);
+        }
+        if ($this->devicetypecustoms === null) {
+            // This shouldn't happen unless you're hardcoding the config value.
+            debugging('Config devicedetectregex is not valid JSON object');
+            $this->devicetypecustoms = array();
         }
         if ($forceuseragent !== null) {
             $this->useragent = $forceuseragent;
index c882073..c2f99cf 100644 (file)
@@ -212,4 +212,14 @@ $definitions = array(
         'staticaccelerationsize' => 2, // Should not be required for more than one user at a time.
         'ttl' => 3600,
     ),
+
+    // A simple cache that stores whether a user can expand a course in the navigation.
+    // The key is the course ID and the value will either be 1 or 0 (cast to bool).
+    // The cache isn't always up to date, it should only ever be used to save a costly call to
+    // can_access_course on the first page request a user makes.
+    'navigation_expandcourse' => array(
+        'mode' => cache_store::MODE_SESSION,
+        'simplekeys' => true,
+        'simpledata' => true
+    )
 );
index 3446f33..bb90953 100644 (file)
@@ -3660,5 +3660,21 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2014051200.02);
     }
 
+    if ($oldversion < 2014060300.00) {
+        $gspath = get_config('assignfeedback_editpdf', 'gspath');
+        if ($gspath !== false) {
+            set_config('pathtogs', $gspath);
+            unset_config('gspath', 'assignfeedback_editpdf');
+        }
+        upgrade_main_savepoint(true, 2014060300.00);
+    }
+
+    if ($oldversion < 2014061000.00) {
+        // Fixing possible wrong MIME type for Publisher files.
+        $filetypes = array('%.pub'=>'application/x-mspublisher');
+        upgrade_mimetypes($filetypes);
+        upgrade_main_savepoint(true, 2014061000.00);
+    }
+
     return true;
 }
index 7e92a1f..a8dcb6b 100644 (file)
@@ -456,3 +456,23 @@ function upgrade_availability_item($groupmembersonly, $groupingid,
         return null;
     }
 }
+
+/**
+ * Updates the mime-types for files that exist in the database, based on their
+ * file extension.
+ *
+ * @param array $filetypes Array with file extension as the key, and mimetype as the value
+ */
+function upgrade_mimetypes($filetypes) {
+    global $DB;
+    $select = $DB->sql_like('filename', '?', false);
+    foreach ($filetypes as $extension=>$mimetype) {
+        $DB->set_field_select(
+            'files',
+            'mimetype',
+            $mimetype,
+            $select,
+            array($extension)
+        );
+    }
+}
\ No newline at end of file
index 3adfbd4..cde46db 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js differ
index 01dfab6..ecebe59 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js differ
index 963a932..3654b27 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js and b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js differ
index 6804837..8aad568 100644 (file)
@@ -206,7 +206,7 @@ EditorPluginButtons.prototype = {
         var title = M.util.get_string(config.title, 'atto_' + pluginname);
 
         // Create the actual button.
-        button = Y.Node.create('<button class="' + buttonClass + '"' +
+        button = Y.Node.create('<button type="button" class="' + buttonClass + '"' +
                 'tabindex="-1">' +
                     '<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" src="' + config.iconurl + '"/>' +
                 '</button>');
index 9297dad..2724d5c 100644 (file)
@@ -9,8 +9,7 @@ Feature: Add or remove items from the TinyMCE editor toolbar
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
     And I log in as "admin"
-    And I follow "Admin User"
-    And I follow "Edit profile"
+    And I navigate to "Edit profile" node in "My profile settings"
     And I set the field "Text editor" to "TinyMCE HTML editor"
     And I press "Update profile"
     And I follow "Home"
index 6e1b9f6..f6a3316 100644 (file)
@@ -1507,7 +1507,6 @@ function &get_mimetypes_array() {
         'pic'  => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
         'pict' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
         'png'  => array ('type'=>'image/png', 'icon'=>'png', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
-
         'pps'  => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
         'ppt'  => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
         'pptx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'icon'=>'powerpoint'),
@@ -1517,8 +1516,9 @@ function &get_mimetypes_array() {
         'ppam' => array ('type'=>'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'icon'=>'powerpoint'),
         'ppsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'icon'=>'powerpoint'),
         'ppsm' => array ('type'=>'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'icon'=>'powerpoint'),
-
         'ps'   => array ('type'=>'application/postscript', 'icon'=>'pdf'),
+        'pub'  => array ('type'=>'application/x-mspublisher', 'icon'=>'publisher', 'groups'=>array('presentation')),
+
         'qt'   => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video','web_video'), 'string'=>'video'),
         'ra'   => array ('type'=>'audio/x-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio','web_audio'), 'string'=>'audio'),
         'ram'  => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
index 29a7d95..96284a9 100644 (file)
@@ -1413,36 +1413,33 @@ function stripHTML(str) {
     return ret;
 }
 
-Number.prototype.fixed=function(n){
-    with(Math)
-        return round(Number(this)*pow(10,n))/pow(10,n);
-};
-function update_progress_bar (id, width, pt, msg, es){
-    var percent = pt;
-    var status = document.getElementById("status_"+id);
-    var percent_indicator = document.getElementById("pt_"+id);
-    var progress_bar = document.getElementById("progress_"+id);
-    var time_es = document.getElementById("time_"+id);
-    status.innerHTML = msg;
-    percent_indicator.innerHTML = percent.fixed(2) + '%';
-    if(percent == 100) {
-        progress_bar.style.background = "green";
-        time_es.style.display = "none";
+function updateProgressBar(id, percent, msg, estimate) {
+    var progressIndicator = Y.one('#' + id);
+    if (!progressIndicator) {
+        return;
+    }
+
+    var progressBar = progressIndicator.one('.bar'),
+        statusIndicator = progressIndicator.one('h2'),
+        estimateIndicator = progressIndicator.one('p');
+
+    statusIndicator.set('innerHTML', Y.Escape.html(msg));
+    progressBar.set('innerHTML', Y.Escape.html('' + percent + '%'));
+    if (percent === 100) {
+        progressIndicator.addClass('progress-success');
+        estimateIndicator.set('innerHTML', null);
     } else {
-        progress_bar.style.background = "#FFCC66";
-        if (es == '?'){
-            time_es.innerHTML = "";
-        }else {
-            time_es.innerHTML = es.fixed(2)+" sec";
-            time_es.style.display
-                = "block";
+        if (estimate) {
+            estimateIndicator.set('innerHTML', Y.Escape.html(estimate));
+        } else {
+            estimateIndicator.set('innerHTML', null);
         }
+        progressIndicator.removeClass('progress-success');
     }
-    progress_bar.style.width = width + "px";
-
+    progressBar.setAttribute('aria-valuenow', percent);
+    progressBar.setStyle('width', percent + '%');
 }
 
-
 // ===== Deprecated core Javascript functions for Moodle ====
 //       DO NOT USE!!!!!!!
 // Do not put this stuff in separate file because it only adds extra load on servers!
index 3ac143e..10fde52 100644 (file)
@@ -1001,6 +1001,8 @@ class global_navigation extends navigation_node {
     protected $expansionlimit = 0;
     /** @var int userid to allow parent to see child's profile page navigation */
     protected $useridtouseforparentchecks = 0;
+    /** @var cache_session A cache that stores information on expanded courses */
+    protected $cacheexpandcourse = null;
 
     /** Used when loading categories to load all top level categories [parent = 0] **/
     const LOAD_ROOT_CATEGORIES = 0;
@@ -1166,6 +1168,14 @@ class global_navigation extends navigation_node {
 
                 // Not enrolled, can't view, and hasn't switched roles
                 if (!can_access_course($course)) {
+                    if ($coursenode->isexpandable === true) {
+                        // Obviously the situation has changed, update the cache and adjust the node.
+                        // This occurs if the user access to a course has been revoked (one way or another) after
+                        // initially logging in for this session.
+                        $this->get_expand_course_cache()->set($course->id, 1);
+                        $coursenode->isexpandable = true;
+                        $coursenode->nodetype = self::NODETYPE_BRANCH;
+                    }
                     // Very ugly hack - do not force "parents" to enrol into course their child is enrolled in,
                     // this hack has been propagated from user/view.php to display the navigation node. (MDL-25805)
                     if (!$this->current_user_is_parent_role()) {
@@ -1175,6 +1185,15 @@ class global_navigation extends navigation_node {
                     }
                 }
 
+                if ($coursenode->isexpandable === false) {
+                    // Obviously the situation has changed, update the cache and adjust the node.
+                    // This occurs if the user has been granted access to a course (one way or another) after initially
+                    // logging in for this session.
+                    $this->get_expand_course_cache()->set($course->id, 1);
+                    $coursenode->isexpandable = true;
+                    $coursenode->nodetype = self::NODETYPE_BRANCH;
+                }
+
                 // Add the essentials such as reports etc...
                 $this->add_course_essentials($coursenode, $course);
                 // Extend course navigation with it's sections/activities
@@ -2228,7 +2247,7 @@ class global_navigation extends navigation_node {
         // Add a node to view the users notes if permitted
         if (!empty($CFG->enablenotes) && has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) {
             $url = new moodle_url('/notes/index.php',array('user'=>$user->id));
-            if ($coursecontext->instanceid) {
+            if ($coursecontext->instanceid != SITEID) {
                 $url->param('course', $coursecontext->instanceid);
             }
             $usernode->add(get_string('notes', 'notes'), $url);
@@ -2373,6 +2392,8 @@ class global_navigation extends navigation_node {
         // This is the name that will be shown for the course.
         $coursename = empty($CFG->navshowfullcoursenames) ? $shortname : $fullname;
 
+        // Can the user expand the course to see its content.
+        $canexpandcourse = true;
         if ($issite) {
             $parent = $this;
             $url = null;
@@ -2392,6 +2413,8 @@ class global_navigation extends navigation_node {
         } else {
             $parent = $this->rootnodes['courses'];
             $url = new moodle_url('/course/view.php', array('id'=>$course->id));
+            // They can only expand the course if they can access it.
+            $canexpandcourse = $this->can_expand_course($course);
             if (!empty($course->category) && $this->show_categories($coursetype == self::COURSE_MY)) {
                 if (!$this->is_category_fully_loaded($course->category)) {
                     // We need to load the category structure for this course
@@ -2408,11 +2431,18 @@ class global_navigation extends navigation_node {
         }
 
         $coursenode = $parent->add($coursename, $url, self::TYPE_COURSE, $shortname, $course->id);
-        $coursenode->nodetype = self::NODETYPE_BRANCH;
         $coursenode->hidden = (!$course->visible);
         // We need to decode &amp;'s here as they will have been added by format_string above and attributes will be encoded again
         // later.
         $coursenode->title(str_replace('&amp;', '&', $fullname));
+        if ($canexpandcourse) {
+            // This course can be expanded by the user, make it a branch to make the system aware that its expandable by ajax.
+            $coursenode->nodetype = self::NODETYPE_BRANCH;
+            $coursenode->isexpandable = true;
+        } else {
+            $coursenode->nodetype = self::NODETYPE_LEAF;
+            $coursenode->isexpandable = false;
+        }
         if (!$forcegeneric) {
             $this->addedcourses[$course->id] = $coursenode;
         }
@@ -2420,6 +2450,45 @@ class global_navigation extends navigation_node {
         return $coursenode;
     }
 
+    /**
+     * Returns a cache instance to use for the expand course cache.
+     * @return cache_session
+     */
+    protected function get_expand_course_cache() {
+        if ($this->cacheexpandcourse === null) {
+            $this->cacheexpandcourse = cache::make('core', 'navigation_expandcourse');
+        }
+        return $this->cacheexpandcourse;
+    }
+
+    /**
+     * Checks if a user can expand a course in the navigation.
+     *
+     * We use a cache here because in order to be accurate we need to call can_access_course which is a costly function.
+     * Because this functionality is basic + non-essential and because we lack good event triggering this cache
+     * permits stale data.
+     * In the situation the user is granted access to a course after we've initialised this session cache the cache
+     * will be stale.
+     * It is brought up to date in only one of two ways.
+     *   1. The user logs out and in again.
+     *   2. The user browses to the course they've just being given access to.
+     *
+     * Really all this controls is whether the node is shown as expandable or not. It is uber un-important.
+     *
+     * @param stdClass $course
+     * @return bool
+     */
+    protected function can_expand_course($course) {
+        $cache = $this->get_expand_course_cache();
+        $canexpand = $cache->get($course->id);
+        if ($canexpand === false) {
+            $canexpand = isloggedin() && can_access_course($course);
+            $canexpand = (int)$canexpand;
+            $cache->set($course->id, $canexpand);
+        }
+        return ($canexpand === 1);
+    }
+
     /**
      * Returns true if the category has already been loaded as have any child categories
      *
index 41444e4..76b0c83 100644 (file)
@@ -1366,7 +1366,7 @@ class html_writer {
         if (empty($attributes['id'])) {
             $attributes['id'] = self::random_id('ts_');
         }
-        $timerselector = self::select($timeunits, $name, $currentdate[$userdatetype], null, array('id'=>$attributes['id']));
+        $timerselector = self::select($timeunits, $name, $currentdate[$userdatetype], null, $attributes);
         $label = self::tag('label', get_string(substr($type, 0, -1), 'form'), array('for'=>$attributes['id'], 'class'=>'accesshide'));
 
         return $label.$timerselector;
index b31a2ac..2a1f56e 100644 (file)
@@ -2885,7 +2885,7 @@ EOD;
 
         //accessibility: heading for navbar list  (MDL-20446)
         $navbarcontent = html_writer::tag('span', get_string('pagepath'), array('class'=>'accesshide'));
-        $navbarcontent .= html_writer::tag('ul', join('', $htmlblocks), array('role'=>'navigation'));
+        $navbarcontent .= html_writer::tag('nav', html_writer::tag('ul', join('', $htmlblocks)));
         // XHTML
         return $navbarcontent;
     }
index 983e5e2..9aae348 100644 (file)
@@ -181,6 +181,7 @@ $CFG->dboptions = isset($CFG->phpunit_dboptions) ? $CFG->phpunit_dboptions : $CF
 $allowed = array('wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
                  'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions',
                  'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass', // keep proxy settings from config.php
+                 'altcacheconfigpath', 'pathtogs', 'pathtoclam', 'pathtodu', 'aspellpath', 'pathtodot'
                 );
 $productioncfg = (array)$CFG;
 $CFG = new stdClass();
index 21bdeb3..1b2837a 100644 (file)
@@ -321,9 +321,6 @@ function question_delete_question($questionid) {
         return;
     }
 
-    // Check permissions.
-    question_require_capability_on($question, 'edit');
-
     $dm = new question_engine_data_mapper();
     $dm->delete_previews($questionid);
 
index e60b99a..c29e7f8 100644 (file)
@@ -90,6 +90,28 @@ class core_scheduled_task_testcase extends advanced_testcase {
         $testclass->set_disabled(true);
         $nexttime = $testclass->get_next_scheduled_time();
         $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
+
+        // Test hourly job executed on Sundays only.
+        $testclass = new \core\task\scheduled_test_task();
+        $testclass->set_minute('0');
+        $testclass->set_day_of_week('7');
+
+        $nexttime = $testclass->get_next_scheduled_time();
+
+        $this->assertEquals(7, date('N', $nexttime));
+        $this->assertEquals(0, date('i', $nexttime));
+
+        // Test monthly job
+        $testclass = new \core\task\scheduled_test_task();
+        $testclass->set_minute('32');
+        $testclass->set_hour('0');
+        $testclass->set_day('1');
+
+        $nexttime = $testclass->get_next_scheduled_time();
+
+        $this->assertEquals(32, date('i', $nexttime));
+        $this->assertEquals(0, date('G', $nexttime));
+        $this->assertEquals(1, date('j', $nexttime));
     }
 
     public function test_timezones() {
index 3f88d22..99ab6a9 100644 (file)
@@ -126,4 +126,31 @@ class core_theme_config_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * This function will test custom device detection regular expression setting.
+     */
+    public function test_devicedetectregex() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        // Check config currently empty.
+        $this->assertEmpty(json_decode($CFG->devicedetectregex));
+        $this->assertTrue(core_useragent::set_user_device_type('tablet'));
+        $exceptionoccured = false;
+        try {
+            core_useragent::set_user_device_type('featurephone');
+        } catch (moodle_exception $e) {
+            $exceptionoccured = true;
+        }
+        $this->assertTrue($exceptionoccured);
+
+        // Set config and recheck.
+        $config = array('featurephone' => '(Symbian|MIDP-1.0|Maemo|Windows CE)');
+        $CFG->devicedetectregex = json_encode($config);
+        core_useragent::instance(true); // Clears singleton cache.
+        $this->assertTrue(core_useragent::set_user_device_type('tablet'));
+        $this->assertTrue(core_useragent::set_user_device_type('featurephone'));
+    }
 }
index e6b0b7f..55ae5f7 100644 (file)
@@ -2994,21 +2994,22 @@ class progress_bar {
      * @return void Echo's output
      */
     public function create() {
+        global $PAGE;
+
         $this->time_start = microtime(true);
         if (CLI_SCRIPT) {
             return; // Temporary solution for cli scripts.
         }
-        $widthplusborder = $this->width + 2;
+
+        $PAGE->requires->string_for_js('secondsleft', 'moodle');
+
         $htmlcode = <<<EOT
-        <div style="text-align:center;width:{$widthplusborder}px;clear:both;padding:0;margin:0 auto;">
-            <h2 id="status_{$this->html_id}" style="text-align: center;margin:0 auto"></h2>
-            <p id="time_{$this->html_id}"></p>
-            <div id="bar_{$this->html_id}" style="border-style:solid;border-width:1px;width:{$this->width}px;height:50px;">
-                <div id="progress_{$this->html_id}"
-                style="text-align:center;background:#FFCC66;width:4px;border:1px
-                solid gray;height:38px; padding-top:10px;">&nbsp;<span id="pt_{$this->html_id}"></span>
-                </div>
+        <div class="progressbar_container" style="width: {$this->width}px;" id="{$this->html_id}">
+            <h2></h2>
+            <div class="progress progress-striped active">
+                <div class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">&nbsp;</div>
             </div>
+            <p></p>
         </div>
 EOT;
         flush();
@@ -3034,12 +3035,11 @@ EOT;
             return; // Temporary solution for cli scripts.
         }
 
-        $es = $this->estimate($percent);
+        $estimate = $this->estimate($percent);
 
-        if ($es === null) {
+        if ($estimate === null) {
             // Always do the first and last updates.
-            $es = "?";
-        } else if ($es == 0) {
+        } else if ($estimate == 0) {
             // Always do the last updates.
         } else if ($this->lastupdate + 20 < time()) {
             // We must update otherwise browser would time out.
@@ -3047,13 +3047,15 @@ EOT;
             // No significant change, no need to update anything.
             return;
         }
+        if (is_numeric($estimate)) {
+            $estimate = get_string('secondsleft', 'moodle', round($estimate, 2));
+        }
 
-        $this->percent = $percent;
+        $this->percent = round($percent, 2);
         $this->lastupdate = microtime(true);
 
-        $w = ($this->percent/100) * $this->width;
-        echo html_writer::script(js_writer::function_call('update_progress_bar',
-            array($this->html_id, $w, $this->percent, $msg, $es)));
+        echo html_writer::script(js_writer::function_call('updateProgressBar',
+            array($this->html_id, $this->percent, $msg, $estimate)));
         flush();
     }
 
index 8e2b3fb..541e407 100644 (file)
@@ -69,7 +69,7 @@ class all_submissions_downloaded extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has downloaded all the submissions for the assignment " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 9174948..b1e16e5 100644 (file)
@@ -78,7 +78,7 @@ class assessable_submitted extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has submitted the submission with id '$this->objectid' " .
-            "for the assignment with the course module id '$this->contextinstanceid'.";
+            "for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 35c6dd6..510549d 100644 (file)
@@ -91,7 +91,7 @@ class batch_set_marker_allocation_viewed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the batch set marker allocation for the assignment with the course " .
+        return "The user with id '$this->userid' viewed the batch set marker allocation for the assignment with course " .
             "module id '$this->contextinstanceid'.";
     }
 
index d6faf30..b4b5c25 100644 (file)
@@ -91,7 +91,7 @@ class batch_set_workflow_state_viewed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the batch set workflow for the assignment with the course " .
+        return "The user with id '$this->userid' viewed the batch set workflow for the assignment with course " .
             "module id '$this->contextinstanceid'.";
     }
 
index 2d70647..3ed4558 100644 (file)
@@ -71,7 +71,7 @@ class extension_granted extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has granted an extension for the user with id '$this->relateduserid' " .
-            "for the assignment with the course module id '$this->contextinstanceid'.";
+            "for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 09d02a6..37e0359 100644 (file)
@@ -89,7 +89,7 @@ class feedback_viewed extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' viewed the feedback for the user with id '$this->relateduserid' " .
-            "for the assignment with the course module id '$this->contextinstanceid'.";
+            "for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 9a1347b..6ee48c2 100644 (file)
@@ -95,7 +95,7 @@ class grading_form_viewed extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' viewed the grading form for the user with id '$this->relateduserid' " .
-            "for the assignment with the course module id '$this->contextinstanceid'.";
+            "for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 4c1b5f7..e3bd463 100644 (file)
@@ -91,7 +91,7 @@ class grading_table_viewed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the grading table for the assignment with the course module " .
+        return "The user with id '$this->userid' viewed the grading table for the assignment with course module " .
             "id '$this->contextinstanceid'.";
     }
 
index b816771..339a6f4 100644 (file)
@@ -68,7 +68,7 @@ class identities_revealed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has revealed identities in the assignment with the course module " .
+        return "The user with id '$this->userid' has revealed identities in the assignment with course module " .
             "id '$this->contextinstanceid'.";
     }
 
index 301e212..64b5084 100644 (file)
@@ -83,7 +83,7 @@ class marker_updated extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has set the marker for the user with id '$this->relateduserid' to " .
-            "'{$this->other['markerid']}' for the assignment with the course module id '$this->contextinstanceid'.";
+            "'{$this->other['markerid']}' for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 19b4fd2..d76411f 100644 (file)
@@ -92,7 +92,7 @@ class reveal_identities_confirmation_page_viewed extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' viewed the confirmation page for revealing identities for the " .
-            "assignment with the course module id '$this->contextinstanceid'.";
+            "assignment with course module id '$this->contextinstanceid'.";
     }
 
 
index d18b533..ec5d59c 100644 (file)
@@ -71,7 +71,7 @@ class statement_accepted extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has accepted the statement of the submission with id '$this->objectid' " .
-            "for the assignment with the course module id '$this->contextinstanceid'.";
+            "for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 13fd931..43980de 100644 (file)
@@ -91,7 +91,7 @@ class submission_confirmation_form_viewed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the submission confirmation form for the assignment with the " .
+        return "The user with id '$this->userid' viewed the submission confirmation form for the assignment with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index ac3fca3..ac7875c 100644 (file)
@@ -71,7 +71,7 @@ class submission_duplicated extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' duplicated their submission with id '$this->objectid' for the " .
-            "assignment with the course module id '$this->contextinstanceid'.";
+            "assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 28883ff..bea0352 100644 (file)
@@ -90,17 +90,17 @@ class submission_form_viewed extends base {
 
     /**
      * Returns description of what happened.
-     *the course module id '$this->contextinstanceid'
+     *
      * @return string
      */
     public function get_description() {
         if ($this->userid != $this->relateduserid) {
             return "The user with id '$this->userid' viewed the submission form for the user with id '$this->relateduserid' " .
-                "for the assignment with the course module id '$this->contextinstanceid'.";
+                "for the assignment with course module id '$this->contextinstanceid'.";
         }
 
-        return "The user with id '$this->userid' viewed their submission for the assignment with the course " .
-            "module id '$this->contextinstanceid'.";
+        return "The user with id '$this->userid' viewed their submission for the assignment with course module id " .
+            "'$this->contextinstanceid'.";
     }
 
     /**
index 94b8ce8..8e16305 100644 (file)
@@ -72,7 +72,7 @@ class submission_graded extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has graded the submission '$this->objectid' for the user with " .
-            "id '$this->relateduserid' for the assignment with the course module id '$this->contextinstanceid'.";
+            "id '$this->relateduserid' for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 372eb51..3aaf927 100644 (file)
@@ -72,7 +72,7 @@ class submission_locked extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' locked the submission for the user with id '$this->relateduserid' for " .
-            "the assignment with the course module id '$this->contextinstanceid'.";
+            "the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index d12f158..5609e16 100644 (file)
@@ -73,7 +73,7 @@ class submission_status_updated extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has updated the status of the submission with id '$this->objectid' for " .
-            "the assignment with the course module id '$this->contextinstanceid' to the status '{$this->other['newstatus']}'.";
+            "the assignment with course module id '$this->contextinstanceid' to the status '{$this->other['newstatus']}'.";
     }
 
     /**
index ee94864..ce3ea15 100644 (file)
@@ -93,7 +93,7 @@ class submission_status_viewed extends base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has viewed the submission status page for the assignment with the " .
+        return "The user with id '$this->userid' has viewed the submission status page for the assignment with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index fcfc28b..c461068 100644 (file)
@@ -72,7 +72,7 @@ class submission_unlocked extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' locked the submission for the user with id '$this->relateduserid' " .
-            "for the assignment with the course module id '$this->contextinstanceid'.";
+            "for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 4936649..e0956ab 100644 (file)
@@ -89,7 +89,7 @@ class submission_viewed extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' viewed the submission for the user with id '$this->relateduserid' for the " .
-            "assignment with the course module id '$this->contextinstanceid'.";
+            "assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 5b8eabc..e758599 100644 (file)
@@ -82,7 +82,7 @@ class workflow_state_updated extends base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has set the workflow state of the user with id '$this->relateduserid' " .
-            "to the state '{$this->other['newstate']}' for the assignment with the course module id '$this->contextinstanceid'.";
+            "to the state '{$this->other['newstate']}' for the assignment with course module id '$this->contextinstanceid'.";
     }
 
     /**
index e0ea32e..e308bcb 100644 (file)
@@ -236,7 +236,7 @@ class assign_feedback_comments extends assign_feedback_plugin {
 
         foreach ($this->assignment->get_submission_plugins() as $plugin) {
             $fields = $plugin->get_editor_fields();
-            if ($plugin->is_enabled() && $plugin->is_visible() && !empty($fields)) {
+            if ($plugin->is_enabled() && $plugin->is_visible() && !$plugin->is_empty($submission) && !empty($fields)) {
                 foreach ($fields as $key => $description) {
                     $rawtext = strip_pluginfile_content($plugin->get_editor_text($key, $submission->id));
 
@@ -253,8 +253,12 @@ class assign_feedback_comments extends assign_feedback_plugin {
             }
         }
 
+        if ($format === false) {
+            $format = FORMAT_HTML;
+        }
         $data->assignfeedbackcomments_editor['text'] = $text;
         $data->assignfeedbackcomments_editor['format'] = $format;
+
         return true;
     }
 
index 87bec4c..e2884d8 100644 (file)
@@ -414,6 +414,8 @@ class pdf extends \FPDI {
      * @return string the filename of the generated image
      */
     public function get_image($pageno) {
+        global $CFG;
+
         if (!$this->filename) {
             throw new \coding_exception('Attempting to generate a page image without first setting the PDF filename');
         }
@@ -437,7 +439,7 @@ class pdf extends \FPDI {
 
         if ($generate) {
             // Use ghostscript to generate an image of the specified page.
-            $gsexec = \escapeshellarg(\get_config('assignfeedback_editpdf', 'gspath'));
+            $gsexec = \escapeshellarg($CFG->pathtogs);
             $imageres = \escapeshellarg(100);
             $imagefilearg = \escapeshellarg($imagefile);
             $filename = \escapeshellarg($this->filename);
@@ -467,6 +469,8 @@ class pdf extends \FPDI {
      * @return string path to copy or converted pdf (false == fail)
      */
     public static function ensure_pdf_compatible(\stored_file $file) {
+        global $CFG;
+
         $temparea = \make_temp_directory('assignfeedback_editpdf');
         $hash = $file->get_contenthash(); // Use the contenthash to make sure the temp files have unique names.
         $tempsrc = $temparea . "/src-$hash.pdf";
@@ -488,7 +492,7 @@ class pdf extends \FPDI {
         }
 
 
-        $gsexec = \escapeshellarg(\get_config('assignfeedback_editpdf', 'gspath'));
+        $gsexec = \escapeshellarg($CFG->pathtogs);
         $tempdstarg = \escapeshellarg($tempdst);
         $tempsrcarg = \escapeshellarg($tempsrc);
         $command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg";
@@ -528,7 +532,7 @@ class pdf extends \FPDI {
             'status' => self::GSPATH_OK,
             'message' => null,
         );
-        $gspath = \get_config('assignfeedback_editpdf', 'gspath');
+        $gspath = $CFG->pathtogs;
         if (empty($gspath)) {
             $ret->status = self::GSPATH_EMPTY;
             return $ret;
index 2d48136..d604b73 100644 (file)
@@ -51,8 +51,7 @@ $string['filter'] = 'Filter comments...';
 $string['generatefeedback'] = 'Generate feedback PDF';
 $string['gotopage'] = 'Go to page';
 $string['green'] = 'Green';
-$string['gspath'] = 'Ghostscript path';
-$string['gspath_help'] = 'On most Linux installs, this can be left as \'/usr/bin/gs\'. On Windows it will be something like \'c:\\gs\\bin\\gswin32c.exe\' (make sure there are no spaces in the path - if necessary copy the files \'gswin32c.exe\' and \'gsdll32.dll\' to a new folder without a space in the path)';
+$string['pathtogspathdesc'] = 'Please note that annotate PDF requires the path to ghostscript to be set in {$a}.';
 $string['highlight'] = 'Highlight';
 $string['jsrequired'] = 'JavaScript is required to annotate a PDF. Please enable JavaScript in your browser to use this feature.';
 $string['launcheditor'] = 'Launch PDF editor...';
index 991e57c..674a21f 100644 (file)
@@ -34,10 +34,10 @@ $setting = new admin_setting_configstoredfile($name, $title, $description, 'stam
 $settings->add($setting);
 
 // Ghostscript setting.
-$settings->add(new admin_setting_configexecutable('assignfeedback_editpdf/gspath',
-                                                  get_string('gspath', 'assignfeedback_editpdf'),
-                                                  get_string('gspath_help', 'assignfeedback_editpdf'),
-                                                  '/usr/bin/gs'));
+$systempathslink = new moodle_url('/admin/settings.php', array('section' => 'systempaths'));
+$systempathlink = html_writer::link($systempathslink, get_string('systempaths', 'admin'));
+$settings->add(new admin_setting_heading('pathtogs', get_string('pathtogs', 'admin'),
+        get_string('pathtogspathdesc', 'assignfeedback_editpdf', $systempathlink)));
 
 $url = new moodle_url('/mod/assign/feedback/editpdf/testgs.php');
 $link = html_writer::link($url, get_string('testgs', 'assignfeedback_editpdf'));
index fa48610..522dfa3 100644 (file)
@@ -915,20 +915,21 @@ class assign_grading_table extends table_sql implements renderable {
 
         $instance = $this->assignment->get_instance();
 
+        $due = $instance->duedate;
+        if ($row->extensionduedate) {
+            $due = $row->extensionduedate;
+        }
+
         if ($this->assignment->is_any_submission_plugin_enabled()) {
 
             $o .= $this->output->container(get_string('submissionstatus_' . $row->status, 'assign'),
                                            array('class'=>'submissionstatus' .$row->status));
-            if ($instance->duedate &&
-                    $row->timesubmitted > $instance->duedate) {
-                if (!$row->extensionduedate ||
-                        $row->timesubmitted > $row->extensionduedate) {
-                    $usertime = format_time($row->timesubmitted - $instance->duedate);
-                    $latemessage = get_string('submittedlateshort',
-                                              'assign',
-                                              $usertime);
-                    $o .= $this->output->container($latemessage, 'latesubmission');
-                }
+            if ($due && $row->timesubmitted > $due) {
+                $usertime = format_time($row->timesubmitted - $due);
+                $latemessage = get_string('submittedlateshort',
+                                          'assign',
+                                          $usertime);
+                $o .= $this->output->container($latemessage, 'latesubmission');
             }
             if ($row->locked) {
                 $lockedstr = get_string('submissionslockedshort', 'assign');
@@ -944,10 +945,6 @@ class assign_grading_table extends table_sql implements renderable {
 
             if (!$row->timesubmitted) {
                 $now = time();
-                $due = $instance->duedate;
-                if ($row->extensionduedate) {
-                    $due = $row->extensionduedate;
-                }
                 if ($due && ($now > $due)) {
                     $overduestr = get_string('overdue', 'assign', format_time($now - $due));
                     $o .= $this->output->container($overduestr, 'overduesubmission');
index 647f1ab..95b8dde 100644 (file)
@@ -3731,7 +3731,9 @@ class assign {
                                                               $gradingcontrollerpreview,
                                                               $instance->attemptreopenmethod,
                                                               $instance->maxattempts);
-            $o .= $this->get_renderer()->render($submissionstatus);
+            if (has_capability('mod/assign:submit', $this->get_context(), $user)) {
+                $o .= $this->get_renderer()->render($submissionstatus);
+            }
 
             require_once($CFG->libdir.'/gradelib.php');
             require_once($CFG->dirroot.'/grade/grading/lib.php');
index a7ea940..d390f72 100644 (file)
@@ -50,6 +50,6 @@ class comment_created extends \core\event\comment_created {
      */
     public function get_description() {
         return "The user with id '$this->userid' added the comment with id '$this->objectid' to the submission " .
-            "with id '{$this->other['itemid']}' for the assignment with the course module id '$this->contextinstanceid'.";
+            "with id '{$this->other['itemid']}' for the assignment with course module id '$this->contextinstanceid'.";
     }
 }
index 8d9523d..e359999 100644 (file)
@@ -50,6 +50,6 @@ class comment_deleted extends \core\event\comment_deleted {
      */
     public function get_description() {
         return "The user with id '$this->userid' deleted the comment with id '$this->objectid' from the submission " .
-            "with id '{$this->other['itemid']}' for the assignment with the course module id '$this->contextinstanceid'.";
+            "with id '{$this->other['itemid']}' for the assignment with course module id '$this->contextinstanceid'.";
     }
 }
index 36e36fc..3c60a8d 100644 (file)
@@ -50,7 +50,7 @@ class assessable_uploaded extends \core\event\assessable_uploaded {
      */
     public function get_description() {
         return "The user with id '$this->userid' has uploaded a file to the submission with id '$this->objectid' " .
-            "in the assignment activity with the course module id '$this->contextinstanceid'.";
+            "in the assignment activity with course module id '$this->contextinstanceid'.";
     }
 
     /**
index e18c843..a788a11 100644 (file)
@@ -57,7 +57,7 @@ class submission_created extends \mod_assign\event\submission_created {
      */
     public function get_description() {
         $descriptionstring = "The user with id '$this->userid' created a file submission and uploaded " .
-            "'{$this->other['filesubmissioncount']}' file/s in the assignment with the course module id " .
+            "'{$this->other['filesubmissioncount']}' file/s in the assignment with course module id " .
             "'$this->contextinstanceid'";
         if (!empty($this->other['groupid'])) {
             $descriptionstring .= " for the group with id '{$this->other['groupid']}'.";
index 6675125..65e8b54 100644 (file)
@@ -57,7 +57,7 @@ class submission_updated extends \mod_assign\event\submission_updated {
      */
     public function get_description() {
         $descriptionstring = "The user with id '$this->userid' updated a file submission and uploaded " .
-            "'{$this->other['filesubmissioncount']}' file/s in the assignment with the course module id " .
+            "'{$this->other['filesubmissioncount']}' file/s in the assignment with course module id " .
             "'$this->contextinstanceid'";
         if (!empty($this->other['groupid'])) {
             $descriptionstring .= " for the group with id '{$this->other['groupid']}'.";
index 2c1b5c5..e460e3d 100644 (file)
@@ -228,7 +228,7 @@ class assign_submission_file extends assign_submission_plugin {
             )
         );
         if (!empty($submission->userid) && ($submission->userid != $USER->id)) {
-            $params->relateduserid = $submission->userid;
+            $params['relateduserid'] = $submission->userid;
         }
         $event = \assignsubmission_file\event\assessable_uploaded::create($params);
         $event->set_legacy_files($files);
index 6a99896..c186471 100644 (file)
@@ -49,7 +49,7 @@ class assessable_uploaded extends \core\event\assessable_uploaded {
      */
     public function get_description() {
         return "The user with id '$this->userid' has saved an online text submission with id '$this->objectid' " .
-            "in the assignment activity with the course module id '$this->contextinstanceid'.";
+            "in the assignment activity with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 8ba2d16..f730d32 100644 (file)
@@ -57,7 +57,7 @@ class submission_created extends \mod_assign\event\submission_created {
      */
     public function get_description() {
         $descriptionstring = "The user with id '$this->userid' created an online text submission with " .
-            "'{$this->other['onlinetextwordcount']}' words in the assignment with the course module id " .
+            "'{$this->other['onlinetextwordcount']}' words in the assignment with course module id " .
             "'$this->contextinstanceid'";
         if (!empty($this->other['groupid'])) {
             $descriptionstring .= " for the group with id '{$this->other['groupid']}'.";
index 6deb6eb..fbde4ac 100644 (file)
@@ -57,7 +57,7 @@ class submission_updated extends \mod_assign\event\submission_updated {
      */
     public function get_description() {
         $descriptionstring = "The user with id '$this->userid' updated an online text submission with " .
-            "'{$this->other['onlinetextwordcount']}' words in the assignment with the course module id " .
+            "'{$this->other['onlinetextwordcount']}' words in the assignment with course module id " .
             "'$this->contextinstanceid'";
         if (!empty($this->other['groupid'])) {
             $descriptionstring .= " for the group with id '{$this->other['groupid']}'.";
index 80a73b9..0fd344c 100644 (file)
@@ -62,7 +62,7 @@ class chapter_created extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' created the chapter with id '$this->objectid' for the book with the " .
+        return "The user with id '$this->userid' created the chapter with id '$this->objectid' for the book with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index 2089ce6..b53a9d0 100644 (file)
@@ -62,7 +62,7 @@ class chapter_deleted extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' deleted the chapter with id '$this->objectid' for the book with the " .
+        return "The user with id '$this->userid' deleted the chapter with id '$this->objectid' for the book with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index b8f2c73..c577e4e 100644 (file)
@@ -62,7 +62,7 @@ class chapter_updated extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' updated the chapter with id '$this->objectid' for the book with the " .
+        return "The user with id '$this->userid' updated the chapter with id '$this->objectid' for the book with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index 61bc31d..6d5bb00 100644 (file)
@@ -62,7 +62,7 @@ class chapter_viewed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the chapter with id '$this->objectid' for the book with the " .
+        return "The user with id '$this->userid' viewed the chapter with id '$this->objectid' for the book with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index 46967d8..08d5a44 100644 (file)
@@ -60,7 +60,7 @@ class book_exported extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has exported the book with the course module id '$this->contextinstanceid'.";
+        return "The user with id '$this->userid' has exported the book with course module id '$this->contextinstanceid'.";
     }
 
     /**
index b062498..ef11cbd 100644 (file)
@@ -60,7 +60,7 @@ class book_printed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has printed the book with the course module id '$this->contextinstanceid'.";
+        return "The user with id '$this->userid' has printed the book with course module id '$this->contextinstanceid'.";
     }
 
     /**
index ca02b45..8f83074 100644 (file)
@@ -62,8 +62,8 @@ class chapter_printed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has printed the chapter with id '$this->objectid' of the book with the
-            course module id '$this->contextinstanceid'.";
+        return "The user with id '$this->userid' has printed the chapter with id '$this->objectid' of the book with " .
+            "course module id '$this->contextinstanceid'.";
     }
 
     /**
index d695f82..5b14c34 100644 (file)
@@ -41,7 +41,7 @@ class message_sent extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->relateduserid' has sent a message in the chat with the course module id
+        return "The user with id '$this->relateduserid' has sent a message in the chat with course module id
             '$this->contextinstanceid'.";
     }
 
index 91f4003..8b369bf 100644 (file)
@@ -48,7 +48,7 @@ class sessions_viewed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has viewed the sessions of the chat with the course module id
+        return "The user with id '$this->userid' has viewed the sessions of the chat with course module id
             '$this->contextinstanceid'.";
     }
 
index a1b1935..3ef4025 100644 (file)
@@ -50,7 +50,7 @@ class answer_submitted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' made the choice with id '$this->objectid' in the choice activity
-            with the course module id '$this->contextinstanceid'.";
+            with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 3918322..cad67ef 100644 (file)
@@ -50,7 +50,7 @@ class answer_updated extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' updated their choice with id '$this->objectid' in the choice activity
-            with the course module id '$this->contextinstanceid'.";
+            with course module id '$this->contextinstanceid'.";
     }
 
     /**
index efa3566..4611427 100644 (file)
@@ -59,7 +59,7 @@ class report_viewed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has viewed the report for the choice activity with the course module id
+        return "The user with id '$this->userid' has viewed the report for the choice activity with course module id
             '$this->contextinstanceid'";
     }
 
index 1c2eb48..f63fde1 100644 (file)
@@ -50,6 +50,6 @@ class comment_created extends \core\event\comment_created {
      */
     public function get_description() {
         return "The user with id '$this->userid' added the comment with id '$this->objectid' to the database activity with " .
-            "the course module id '$this->contextinstanceid'.";
+            "course module id '$this->contextinstanceid'.";
     }
 }
index 70a4e52..ff514d6 100644 (file)
@@ -50,6 +50,6 @@ class comment_deleted extends \core\event\comment_deleted {
      */
     public function get_description() {
         return "The user with id '$this->userid' deleted the comment with id '$this->objectid' from the database activity with " .
-            "the course module id '$this->contextinstanceid'.";
+            "course module id '$this->contextinstanceid'.";
     }
 }
index ca66a9d..2084724 100644 (file)
@@ -71,7 +71,7 @@ class field_created extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' created the field with id '$this->objectid' for the data activity " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 647cad8..ff597eb 100644 (file)
@@ -70,7 +70,7 @@ class field_deleted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' deleted the field with id '$this->objectid' in the data activity " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 3a4e7c1..416dad0 100644 (file)
@@ -70,7 +70,7 @@ class field_updated extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' updated the field with id '$this->objectid' in the data activity " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 81a607a..f90465d 100644 (file)
@@ -69,7 +69,7 @@ class record_created extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' created the data record with id '$this->objectid' for the data activity " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 463b648..cbaafd9 100644 (file)
@@ -69,7 +69,7 @@ class record_deleted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' deleted the data record with id '$this->objectid' in the data activity " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 89fbfa8..baa6ed5 100644 (file)
@@ -69,7 +69,7 @@ class record_updated extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' updated the data record with id '$this->objectid' in the data activity " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 14e11a3..78211b3 100644 (file)
@@ -68,7 +68,7 @@ class template_updated extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' updated the template for the data activity with the course module " .
+        return "The user with id '$this->userid' updated the template for the data activity with course module " .
             "id '$this->contextinstanceid'.";
     }
 
index f8c34f7..a9a385e 100644 (file)
@@ -67,7 +67,7 @@ class template_viewed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' viewed the template for the data activity with the course module " .
+        return "The user with id '$this->userid' viewed the template for the data activity with course module " .
             "id '$this->contextinstanceid'.";
     }
 
index 5936e79..f3bb8f2 100644 (file)
@@ -1121,37 +1121,6 @@ function data_update_grades($data, $userid=0, $nullifnone=true) {
     }
 }
 
-/**
- * Update all grades in gradebook.
- *
- * @global object
- */
-function data_upgrade_grades() {
-    global $DB;
-
-    $sql = "SELECT COUNT('x')
-              FROM {data} d, {course_modules} cm, {modules} m
-             WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id";
-    $count = $DB->count_records_sql($sql);
-
-    $sql = "SELECT d.*, cm.idnumber AS cmidnumber, d.course AS courseid
-              FROM {data} d, {course_modules} cm, {modules} m
-             WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id";
-    $rs = $DB->get_recordset_sql($sql);
-    if ($rs->valid()) {
-        // too much debug output
-        $pbar = new progress_bar('dataupgradegrades', 500, true);
-        $i=0;
-        foreach ($rs as $data) {
-            $i++;
-            upgrade_set_timeout(60*5); // set up timeout, may also abort execution
-            data_update_grades($data, 0, false);
-            $pbar->update($i, $count, "Updating Database grades ($i/$count).");
-        }
-    }
-    $rs->close();
-}
-
 /**
  * Update/create grade item for given data
  *
index 2cb0d2a..9d82963 100644 (file)
@@ -348,7 +348,9 @@ class data_portfolio_caller extends portfolio_module_caller_base {
         $includedfiles = array();
         foreach ($fields as $singlefield) {
             if (is_callable(array($singlefield, 'get_file'))) {
-                $includedfiles[] = $singlefield->get_file($record->id);
+                if ($file = $singlefield->get_file($record->id)) {
+                    $includedfiles[] = $file;
+                }
             }
         }
         if (count($includedfiles) == 1 && count($fields) == 1) {
index 4ab2d00..686f757 100644 (file)
@@ -70,7 +70,7 @@ class response_deleted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' deleted the feedback for the user with id '$this->relateduserid' " .
-            "for the feedback activity with the course module id '$this->contextinstanceid'.";
+            "for the feedback activity with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 2a9a632..8dc8d98 100644 (file)
@@ -73,7 +73,7 @@ class response_submitted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' created feedback for the user with id '$this->relateduserid' " .
-            "for the feedback activity with the course module id '$this->contextinstanceid'.";
+            "for the feedback activity with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 09b8fdf..a2bc2b2 100644 (file)
@@ -650,6 +650,7 @@ class feedback_item_multichoicerated extends feedback_item_base {
     }
 
     public function prepare_presentation_values_print($valuestring, $valuesep1, $valuesep2) {
+        $valuestring = str_replace(array("\n","\r"), "", $valuestring);
         return $this->prepare_presentation_values(FEEDBACK_MULTICHOICERATED_LINE_SEP,
                                                   "\n",
                                                   $valuestring,
@@ -658,6 +659,8 @@ class feedback_item_multichoicerated extends feedback_item_base {
     }
 
     public function prepare_presentation_values_save($valuestring, $valuesep1, $valuesep2) {
+        $valuestring = str_replace("\r", "\n", $valuestring);
+        $valuestring = str_replace("\n\n", "\n", $valuestring);
         return $this->prepare_presentation_values("\n",
                         FEEDBACK_MULTICHOICERATED_LINE_SEP,
                         $valuestring,
index c4e05cf..b5d9c0b 100644 (file)
@@ -50,7 +50,7 @@ class assessable_uploaded extends \core\event\assessable_uploaded {
      */
     public function get_description() {
         return "The user with id '$this->userid' has posted content in the forum post with id '$this->objectid' " .
-            "in the discussion '{$this->other['discussionid']}' located in the forum with the course module id " .
+            "in the discussion '{$this->other['discussionid']}' located in the forum with course module id " .
             "'$this->contextinstanceid'.";
     }
 
index a988faf..e974753 100644 (file)
@@ -59,7 +59,7 @@ class discussion_created extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has created the discussion with id '$this->objectid' in the forum " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index e389579..813f37b 100644 (file)
@@ -60,7 +60,7 @@ class discussion_deleted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has deleted the discussion with id '$this->objectid' in the forum " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 503f4e9..c0c7814 100644 (file)
@@ -59,7 +59,7 @@ class discussion_updated extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has updated the discussion with id '$this->objectid' in the forum " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 21da0e3..3986531 100644 (file)
@@ -54,7 +54,7 @@ class discussion_viewed extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has viewed the discussion with id '$this->objectid' in the forum " .
-            "with the course module id '$this->contextinstanceid'.";
+            "with course module id '$this->contextinstanceid'.";
     }
 
     /**
index cc15d1c..91f1554 100644 (file)
@@ -61,7 +61,7 @@ class post_created extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has created the post with id '$this->objectid' in the discussion with " .
-            "id '{$this->other['discussionid']}' in the forum with the course module id '$this->contextinstanceid'.";
+            "id '{$this->other['discussionid']}' in the forum with course module id '$this->contextinstanceid'.";
     }
 
     /**
index be2cbf8..311d899 100644 (file)
@@ -61,7 +61,7 @@ class post_deleted extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has deleted the post with id '$this->objectid' in the discussion with " .
-            "id '{$this->other['discussionid']}' in the forum with the course module id '$this->contextinstanceid'.";
+            "id '{$this->other['discussionid']}' in the forum with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 2c05747..eff30eb 100644 (file)
@@ -61,7 +61,7 @@ class post_updated extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has updated the post with id '$this->objectid' in the discussion with " .
-            "id '{$this->other['discussionid']}' in the forum with the course module id '$this->contextinstanceid'.";
+            "id '{$this->other['discussionid']}' in the forum with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 6d49915..44b005e 100644 (file)
@@ -58,7 +58,7 @@ class readtracking_disabled extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has disabled read tracking for the user with id '$this->relateduserid' " .
-            "in the forum with the course module id '$this->contextinstanceid'.";
+            "in the forum with course module id '$this->contextinstanceid'.";
     }
 
     /**
index afab7b4..07de971 100644 (file)
@@ -58,7 +58,7 @@ class readtracking_enabled extends \core\event\base {
      */
     public function get_description() {
         return "The user with id '$this->userid' has enabled read tracking for the user with id '$this->relateduserid' " .
-            "in the forum with the course module id '$this->contextinstanceid'.";
+            "in the forum with course module id '$this->contextinstanceid'.";
     }
 
     /**
index 4289213..b7b6139 100644 (file)
@@ -58,7 +58,7 @@ class subscribers_viewed extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' has viewed the subscribers list for the forum with the course " .
+        return "The user with id '$this->userid' has viewed the subscribers list for the forum with course " .
             "module id '$this->contextinstanceid'.";
     }
 
index 9a7aec9..e5af00f 100644 (file)
@@ -58,7 +58,7 @@ class subscription_created extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' subscribed the user with id '$this->relateduserid' to the forum with the " .
+        return "The user with id '$this->userid' subscribed the user with id '$this->relateduserid' to the forum with " .
             "course module id '$this->contextinstanceid'.";
     }
 
index a2f3e27..738a5c0 100644 (file)
@@ -58,7 +58,7 @@ class subscription_deleted extends \core\event\base {
      * @return string
      */
     public function get_description() {
-        return "The user with id '$this->userid' unsubscribed the user with id '$this->relateduserid' to the forum with the " .
+        return "The user with id '$this->userid' unsubscribed the user with id '$this->relateduserid' to the forum with " .
             "course module id '$this->contextinstanceid'.";
     }
 
diff --git a/mod/forum/classes/existing_subscriber_selector.php b/mod/forum/classes/existing_subscriber_selector.php
new file mode 100644 (file)
index 0000000..ae7ef99
--- /dev/null
@@ -0,0 +1,64 @@
+<?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/>.
+
+/**
+ * A type of forum.
+ *
+ * @package    mod_forum
+ * @copyright  2014 Andrew Robert Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/user/selector/lib.php');
+
+/**
+ * User selector control for removing subscribed users
+ * @package   mod_forum
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_forum_existing_subscriber_selector extends mod_forum_subscriber_selector_base {
+
+    /**
+     * Finds all subscribed users
+     *
+     * @param string $search
+     * @return array
+     */
+    public function find_users($search) {
+        global $DB;
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        $params['forumid'] = $this->forumid;
+
+        // only active enrolled or everybody on the frontpage
+        list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
+        $fields = $this->required_fields_sql('u');
+        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
+        $params = array_merge($params, $eparams, $sortparams);
+
+        $subscribers = $DB->get_records_sql("SELECT $fields
+                                               FROM {user} u
+                                               JOIN ($esql) je ON je.id = u.id
+                                               JOIN {forum_subscriptions} s ON s.userid = u.id
+                                              WHERE $wherecondition AND s.forum = :forumid
+                                           ORDER BY $sort", $params);
+
+        return array(get_string("existingsubscribers", 'forum') => $subscribers);
+    }
+
+}
diff --git a/mod/forum/classes/potential_subscriber_selector.php b/mod/forum/classes/potential_subscriber_selector.php
new file mode 100644 (file)
index 0000000..cc9c6c0
--- /dev/null
@@ -0,0 +1,156 @@
+<?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/>.
+
+/**
+ * A type of forum.
+ *
+ * @package    mod_forum
+ * @copyright  2014 Andrew Robert Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/user/selector/lib.php');
+
+/**
+ * A user selector control for potential subscribers to the selected forum
+ * @package   mod_forum
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_forum_potential_subscriber_selector extends mod_forum_subscriber_selector_base {
+    /**
+     * If set to true EVERYONE in this course is force subscribed to this forum
+     * @var bool
+     */
+    protected $forcesubscribed = false;
+    /**
+     * Can be used to store existing subscribers so that they can be removed from
+     * the potential subscribers list
+     */
+    protected $existingsubscribers = array();
+
+    /**
+     * Constructor method
+     * @param string $name
+     * @param array $options
+     */
+    public function __construct($name, $options) {
+        parent::__construct($name, $options);
+        if (isset($options['forcesubscribed'])) {
+            $this->forcesubscribed=true;
+        }
+    }
+
+    /**
+     * Returns an arary of options for this control
+     * @return array
+     */
+    protected function get_options() {
+        $options = parent::get_options();
+        if ($this->forcesubscribed===true) {
+            $options['forcesubscribed']=1;
+        }
+        return $options;
+    }
+
+    /**
+     * Finds all potential users
+     *
+     * Potential subscribers are all enroled users who are not already subscribed.
+     *
+     * @param string $search
+     * @return array
+     */
+    public function find_users($search) {
+        global $DB;
+
+        $whereconditions = array();
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        if ($wherecondition) {
+            $whereconditions[] = $wherecondition;
+        }
+
+        if (!$this->forcesubscribed) {
+            $existingids = array();
+            foreach ($this->existingsubscribers as $group) {
+                foreach ($group as $user) {
+                    $existingids[$user->id] = 1;
+                }
+            }
+            if ($existingids) {
+                list($usertest, $userparams) = $DB->get_in_or_equal(
+                        array_keys($existingids), SQL_PARAMS_NAMED, 'existing', false);
+                $whereconditions[] = 'u.id ' . $usertest;
+                $params = array_merge($params, $userparams);
+            }
+        }
+
+        if ($whereconditions) {
+            $wherecondition = 'WHERE ' . implode(' AND ', $whereconditions);
+        }
+
+        list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
+        $params = array_merge($params, $eparams);
+
+        $fields      = 'SELECT ' . $this->required_fields_sql('u');
+        $countfields = 'SELECT COUNT(u.id)';
+
+        $sql = " FROM {user} u
+                 JOIN ($esql) je ON je.id = u.id
+                      $wherecondition";
+
+        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
+        $order = ' ORDER BY ' . $sort;
+
+        // Check to see if there are too many to show sensibly.
+        if (!$this->is_validating()) {
+            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
+            if ($potentialmemberscount > $this->maxusersperpage) {
+                return $this->too_many_results($search, $potentialmemberscount);
+            }
+        }
+
+        // If not, show them.
+        $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
+
+        if (empty($availableusers)) {
+            return array();
+        }
+
+        if ($this->forcesubscribed) {
+            return array(get_string("existingsubscribers", 'forum') => $availableusers);
+        } else {
+            return array(get_string("potentialsubscribers", 'forum') => $availableusers);
+        }
+    }
+
+    /**
+     * Sets the existing subscribers
+     * @param array $users
+     */
+    public function set_existing_subscribers(array $users) {
+        $this->existingsubscribers = $users;
+    }
+
+    /**
+     * Sets this forum as force subscribed or not
+     */
+    public function set_force_subscribed($setting=true) {
+        $this->forcesubscribed = true;
+    }
+}
diff --git a/mod/forum/classes/subscriber_selector_base.php b/mod/forum/classes/subscriber_selector_base.php
new file mode 100644 (file)
index 0000000..45da3b3
--- /dev/null
@@ -0,0 +1,87 @@
+<?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/>.
+
+/**
+ * A type of forum.
+ *
+ * @package    mod_forum
+ * @copyright  2014 Andrew Robert Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/user/selector/lib.php');
+
+/**
+ * Abstract class used by forum subscriber selection controls
+ * @package   mod_forum
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class mod_forum_subscriber_selector_base extends user_selector_base {
+
+    /**
+     * The id of the forum this selector is being used for
+     * @var int
+     */
+    protected $forumid = null;
+    /**
+     * The context of the forum this selector is being used for
+     * @var object
+     */
+    protected $context = null;
+    /**
+     * The id of the current group
+     * @var int
+     */
+    protected $currentgroup = null;
+
+    /**
+     * Constructor method
+     * @param string $name
+     * @param array $options
+     */
+    public function __construct($name, $options) {
+        $options['accesscontext'] = $options['context'];
+        parent::__construct($name, $options);
+        if (isset($options['context'])) {
+            $this->context = $options['context'];
+        }
+        if (isset($options['currentgroup'])) {
+            $this->currentgroup = $options['currentgroup'];
+        }
+        if (isset($options['forumid'])) {
+            $this->forumid = $options['forumid'];
+        }
+    }
+
+    /**
+     * Returns an array of options to seralise and store for searches
+     *
+     * @return array
+     */
+    protected function get_options() {
+        global $CFG;
+        $options = parent::get_options();
+        $options['file'] =  substr(__FILE__, strlen($CFG->dirroot.'/'));
+        $options['context'] = $this->context;
+        $options['currentgroup'] = $this->currentgroup;
+        $options['forumid'] = $this->forumid;
+        return $options;
+    }
+
+}
diff --git a/mod/forum/deprecatedlib.php b/mod/forum/deprecatedlib.php
new file mode 100644 (file)
index 0000000..725a337
--- /dev/null
@@ -0,0 +1,510 @@
+<?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/>.
+
+/**
+ * @package   mod_forum
+ * @copyright 2014 Andrew Robert Nicols <andrew@nicols.co.uk>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Deprecated a very long time ago.
+
+/**
+ * How many posts by other users are unrated by a given user in the given discussion?
+ *
+ * @param int $discussionid
+ * @param int $userid
+ * @return mixed
+ * @deprecated since Moodle 1.1 - please do not use this function any more.
+ */
+function forum_count_unrated_posts($discussionid, $userid) {
+    global $CFG, $DB;
+    debugging('forum_count_unrated_posts() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    $sql = "SELECT COUNT(*) as num
+              FROM {forum_posts}
+             WHERE parent > 0
+               AND discussion = :discussionid
+               AND userid <> :userid";
+    $params = array('discussionid' => $discussionid, 'userid' => $userid);
+    $posts = $DB->get_record_sql($sql, $params);
+    if ($posts) {
+        $sql = "SELECT count(*) as num
+                  FROM {forum_posts} p,
+                       {rating} r
+                 WHERE p.discussion = :discussionid AND
+                       p.id = r.itemid AND
+                       r.userid = userid AND
+                       r.component = 'mod_forum' AND
+                       r.ratingarea = 'post'";
+        $rated = $DB->get_record_sql($sql, $params);
+        if ($rated) {
+            if ($posts->num > $rated->num) {
+                return $posts->num - $rated->num;
+            } else {
+                return 0;    // Just in case there was a counting error
+            }
+        } else {
+            return $posts->num;
+        }
+    } else {
+        return 0;
+    }
+}
+
+
+// Since Moodle 1.5.
+
+/**
+ * Returns the count of records for the provided user and discussion.
+ *
+ * @global object
+ * @global object
+ * @param int $userid
+ * @param int $discussionid
+ * @return bool
+ * @deprecated since Moodle 1.5 - please do not use this function any more.
+ */
+function forum_tp_count_discussion_read_records($userid, $discussionid) {
+    debugging('forum_tp_count_discussion_read_records() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+
+    $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
+
+    $sql = 'SELECT COUNT(DISTINCT p.id) '.
+           'FROM {forum_discussions} d '.
+           'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
+           'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
+                'AND (p.modified < ? OR p.id = r.postid) '.
+           'WHERE d.id = ? ';
+
+    return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
+}
+
+/**
+ * Get all discussions started by a particular user in a course (or group)
+ *
+ * @global object
+ * @global object
+ * @param int $courseid
+ * @param int $userid
+ * @param int $groupid
+ * @return array
+ * @deprecated since Moodle 1.5 - please do not use this function any more.
+ */
+function forum_get_user_discussions($courseid, $userid, $groupid=0) {
+    debugging('forum_get_user_discussions() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+    $params = array($courseid, $userid);
+    if ($groupid) {
+        $groupselect = " AND d.groupid = ? ";
+        $params[] = $groupid;
+    } else  {
+        $groupselect = "";
+    }
+
+    $allnames = get_all_user_name_fields(true, 'u');
+    return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, u.email, u.picture, u.imagealt,
+                                   f.type as forumtype, f.name as forumname, f.id as forumid
+                              FROM {forum_discussions} d,
+                                   {forum_posts} p,
+                                   {user} u,
+                                   {forum} f
+                             WHERE d.course = ?
+                               AND p.discussion = d.id
+                               AND p.parent = 0
+                               AND p.userid = u.id
+                               AND u.id = ?
+                               AND d.forum = f.id $groupselect
+                          ORDER BY p.created DESC", $params);
+}
+
+
+// Since Moodle 1.6.
+
+/**
+ * Returns the count of posts for the provided forum and [optionally] group.
+ * @global object
+ * @global object
+ * @param int $forumid
+ * @param int|bool $groupid
+ * @return int
+ * @deprecated since Moodle 1.6 - please do not use this function any more.
+ */
+function forum_tp_count_forum_posts($forumid, $groupid=false) {
+    debugging('forum_tp_count_forum_posts() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+    $params = array($forumid);
+    $sql = 'SELECT COUNT(*) '.
+           'FROM {forum_posts} fp,{forum_discussions} fd '.
+           'WHERE fd.forum = ? AND fp.discussion = fd.id';
+    if ($groupid !== false) {
+        $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
+        $params[] = $groupid;
+    }
+    $count = $DB->count_records_sql($sql, $params);
+
+
+    return $count;
+}
+
+/**
+ * Returns the count of records for the provided user and forum and [optionally] group.
+ * @global object
+ * @global object
+ * @param int $userid
+ * @param int $forumid
+ * @param int|bool $groupid
+ * @return int
+ * @deprecated since Moodle 1.6 - please do not use this function any more.
+ */
+function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
+    debugging('forum_tp_count_forum_read_records() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+
+    $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
+
+    $groupsel = '';
+    $params = array($userid, $forumid, $cutoffdate);
+    if ($groupid !== false) {
+        $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
+        $params[] = $groupid;
+    }
+
+    $sql = "SELECT COUNT(p.id)
+              FROM  {forum_posts} p
+                    JOIN {forum_discussions} d ON d.id = p.discussion
+                    LEFT JOIN {forum_read} r   ON (r.postid = p.id AND r.userid= ?)
+              WHERE d.forum = ?
+                    AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
+                    $groupsel";
+
+    return $DB->get_field_sql($sql, $params);
+}
+
+
+// Since Moodle 1.7.
+
+/**
+ * Returns array of forum open modes.
+ *
+ * @return array
+ * @deprecated since Moodle 1.7 - please do not use this function any more.
+ */
+function forum_get_open_modes() {
+    debugging('forum_get_open_modes() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+    return array();
+}
+
+
+// Since Moodle 1.9.
+
+/**
+ * Gets posts with all info ready for forum_print_post
+ * We pass forumid in because we always know it so no need to make a
+ * complicated join to find it out.
+ *
+ * @global object
+ * @global object
+ * @param int $parent
+ * @param int $forumid
+ * @return array
+ * @deprecated since Moodle 1.9 MDL-13303 - please do not use this function any more.
+ */
+function forum_get_child_posts($parent, $forumid) {
+    debugging('forum_get_child_posts() is deprecated.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+
+    $allnames = get_all_user_name_fields(true, 'u');
+    return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
+                              FROM {forum_posts} p
+                         LEFT JOIN {user} u ON p.userid = u.id
+                             WHERE p.parent = ?
+                          ORDER BY p.created ASC", array($parent));
+}
+
+/**
+ * Gets posts with all info ready for forum_print_post
+ * We pass forumid in because we always know it so no need to make a
+ * complicated join to find it out.
+ *
+ * @global object
+ * @global object
+ * @return mixed array of posts or false
+ * @deprecated since Moodle 1.9 MDL-13303 - please do not use this function any more.
+ */
+function forum_get_discussion_posts($discussion, $sort, $forumid) {
+    debugging('forum_get_discussion_posts() is deprecated.', DEBUG_DEVELOPER);
+
+    global $CFG, $DB;
+
+    $allnames = get_all_user_name_fields(true, 'u');
+    return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
+                              FROM {forum_posts} p
+                         LEFT JOIN {user} u ON p.userid = u.id
+                             WHERE p.discussion = ?
+                               AND p.parent > 0 $sort", array($discussion));
+}
+
+
+// Since Moodle 2.0.
+
+/**
+ * Returns a list of ratings for a particular post - sorted.
+ *
+ * @param stdClass $context
+ * @param int $postid
+ * @param string $sort
+ * @return array Array of ratings or false
+ * @deprecated since Moodle 2.0 MDL-21657 - please do not use this function any more.
+ */
+function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
+    debugging('forum_get_ratings() is deprecated.', DEBUG_DEVELOPER);
+    $options = new stdClass;
+    $options->context = $context;
+    $options->component = 'mod_forum';
+    $options->ratingarea = 'post';
+    $options->itemid = $postid;
+    $options->sort = "ORDER BY $sort";
+
+    $rm = new rating_manager();
+    return $rm->get_all_ratings_for_item($options);
+}
+
+/**
+ * Generate and return the track or no track link for a forum.
+ *
+ * @global object
+ * @global object
+ * @global object
+ * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
+ * @param array $messages
+ * @param bool $fakelink
+ * @return string
+ * @deprecated since Moodle 2.0 MDL-14632 - please do not use this function any more.
+ */
+function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
+    debugging('forum_get_tracking_link() is deprecated.', DEBUG_DEVELOPER);
+
+    global $CFG, $USER, $PAGE, $OUTPUT;
+
+    static $strnotrackforum, $strtrackforum;
+
+    if (isset($messages['trackforum'])) {
+         $strtrackforum = $messages['trackforum'];
+    }
+    if (isset($messages['notrackforum'])) {
+         $strnotrackforum = $messages['notrackforum'];
+    }
+    if (empty($strtrackforum)) {
+        $strtrackforum = get_string('trackforum', 'forum');
+    }
+    if (empty($strnotrackforum)) {
+        $strnotrackforum = get_string('notrackforum', 'forum');
+    }
+
+    if (forum_tp_is_tracked($forum)) {
+        $linktitle = $strnotrackforum;
+        $linktext = $strnotrackforum;
+    } else {
+        $linktitle = $strtrackforum;
+        $linktext = $strtrackforum;
+    }
+
+    $link = '';
+    if ($fakelink) {
+        $PAGE->requires->js('/mod/forum/forum.js');
+        $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
+        // use <noscript> to print button in case javascript is not enabled
+        $link .= '<noscript>';
+    }
+    $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
+    $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
+
+    if ($fakelink) {
+        $link .= '</noscript>';
+    }
+
+    return $link;
+}
+
+/**
+ * Returns the count of records for the provided user and discussion.
+ *
+ * @global object
+ * @global object
+ * @param int $userid
+ * @param int $discussionid
+ * @return int
+ * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
+ */
+function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
+    debugging('forum_tp_count_discussion_unread_posts() is deprecated.', DEBUG_DEVELOPER);
+    global $CFG, $DB;
+
+    $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
+
+    $sql = 'SELECT COUNT(p.id) '.
+           'FROM {forum_posts} p '.
+           'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
+           'WHERE p.discussion = ? '.
+                'AND p.modified >= ? AND r.id is NULL';
+
+    return $DB->count_records_sql($sql, array($userid, $discussionid, $cutoffdate));
+}
+
+/**
+ * Converts a forum to use the Roles System
+ *
+ * @deprecated since Moodle 2.0 MDL-23479 - please do not use this function any more.
+ */
+function forum_convert_to_roles() {
+    debugging('forum_convert_to_roles() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+}
+
+/**
+ * Returns all records in the 'forum_read' table matching the passed keys, indexed
+ * by userid.
+ *
+ * @global object
+ * @param int $userid
+ * @param int $postid
+ * @param int $discussionid
+ * @param int $forumid
+ * @return array
+ * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
+ */
+function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
+    debugging('forum_tp_get_read_records() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    global $DB;
+    $select = '';
+    $params = array();
+
+    if ($userid > -1) {
+        if ($select != '') $select .= ' AND ';
+        $select .= 'userid = ?';
+        $params[] = $userid;
+    }
+    if ($postid > -1) {
+        if ($select != '') $select .= ' AND ';
+        $select .= 'postid = ?';
+        $params[] = $postid;
+    }
+    if ($discussionid > -1) {
+        if ($select != '') $select .= ' AND ';
+        $select .= 'discussionid = ?';
+        $params[] = $discussionid;
+    }
+    if ($forumid > -1) {
+        if ($select != '') $select .= ' AND ';
+        $select .= 'forumid = ?';
+        $params[] = $forumid;
+    }
+
+    return $DB->get_records_select('forum_read', $select, $params);
+}
+
+/**
+ * Returns all read records for the provided user and discussion, indexed by postid.
+ *
+ * @global object
+ * @param inti $userid
+ * @param int $discussionid
+ * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
+ */
+function forum_tp_get_discussion_read_records($userid, $discussionid) {
+    debugging('forum_tp_get_discussion_read_records() is deprecated and will not be replaced.', DEBUG_DEVELOPER);
+
+    global $DB;
+    $select = 'userid = ? AND discussionid = ?';
+    $fields = 'postid, firstread, lastread';
+    return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
+}
+
+// Deprecated in 2.3.
+
+/**
+ * This function gets run whenever user is enrolled into course
+ *
+ * @deprecated since Moodle 2.3 MDL-33166 - please do not use this function any more.
+ * @param stdClass $cp
+ * @return void
+ */
+function forum_user_enrolled($cp) {
+    debugging('forum_user_enrolled() is deprecated. Please use forum_user_role_assigned instead.', DEBUG_DEVELOPER);
+    global $DB;
+
+    // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
+    //       Originally there used to be 'mod/forum:initialsubscriptions' which was
+    //       introduced because we did not have enrolment information in earlier versions...
+
+    $sql = "SELECT f.id
+              FROM {forum} f
+         LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
+             WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
+    $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
+
+    $forums = $DB->get_records_sql($sql, $params);
+    foreach ($forums as $forum) {
+        forum_subscribe($cp->userid, $forum->id);
+    }
+}
+
+
+// Deprecated in 2.4.
+
+/**
+ * Checks to see if a user can view a particular post.
+ *
+ * @deprecated since Moodle 2.4 use forum_user_can_see_post() instead
+ *
+ * @param object $post
+ * @param object $course
+ * @param object $cm
+ * @param object $forum
+ * @param object $discussion
+ * @param object $user
+ * @return boolean
+ */
+function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=null){
+    debugging('forum_user_can_view_post() is deprecated. Please use forum_user_can_see_post() instead.', DEBUG_DEVELOPER);
+    return forum_user_can_see_post($forum, $discussion, $post, $user, $cm);
+}
+
+
+// Deprecated in 2.6.
+
+/**
+ * FORUM_TRACKING_ON - deprecated alias for FORUM_TRACKING_FORCED.
+ * @deprecated since 2.6
+ */
+define('FORUM_TRACKING_ON', 2);
+
+/**
+ * @deprecated since Moodle 2.6
+ * @see shorten_text()
+ */
+function forum_shorten_post($message) {
+    throw new coding_exception('forum_shorten_post() can not be used any more. Please use shorten_text($message, $CFG->forum_shortpost) instead.');
+}
index c72c07e..ffbcac4 100644 (file)
@@ -339,9 +339,6 @@ $string['numposts'] = '{$a} posts';
 $string['olderdiscussions'] = 'Older discussions';
 $string['oldertopics'] = 'Older topics';
 $string['oldpostdays'] = 'Read after days';
-$string['openmode0'] = 'No discussions, no replies';
-$string['openmode1'] = 'No discussions, but replies are allowed';
-$string['openmode2'] = 'Discussions and replies are allowed';
 $string['overviewnumpostssince'] = '{$a} posts since last login';
 $string['overviewnumunread'] = '{$a} total unread';
 $string['page-mod-forum-x'] = 'Any forum module page';
index 8e05f86..a17d66e 100644 (file)
@@ -23,9 +23,9 @@
 defined('MOODLE_INTERNAL') || die();
 
 /** Include required files */
+require_once(__DIR__ . '/deprecatedlib.php');
 require_once($CFG->libdir.'/filelib.php');
 require_once($CFG->libdir.'/eventslib.php');
-require_once($CFG->dirroot.'/user/selector/lib.php');
 
 /// CONSTANTS ///////////////////////////////////////////////////////////
 
@@ -55,12 +55,6 @@ define('FORUM_TRACKING_OPTIONAL', 1);
  */
 define('FORUM_TRACKING_FORCED', 2);
 
-/**
- * FORUM_TRACKING_ON - deprecated alias for FORUM_TRACKING_FORCED.
- * @deprecated since 2.6
- */
-define('FORUM_TRACKING_ON', 2);
-
 define('FORUM_MAILED_PENDING', 0);
 define('FORUM_MAILED_SUCCESS', 1);
 define('FORUM_MAILED_ERROR', 2);
@@ -1675,35 +1669,6 @@ function forum_update_grades($forum, $userid=0, $nullifnone=true) {
     }
 }
 
-/**
- * Update all grades in gradebook.