Merge branch 'MDL-59921-master' of git://github.com/lameze/moodle
authorJun Pataleta <jun@moodle.com>
Wed, 11 Oct 2017 03:07:14 +0000 (11:07 +0800)
committerJun Pataleta <jun@moodle.com>
Wed, 11 Oct 2017 03:07:14 +0000 (11:07 +0800)
239 files changed:
.eslintignore
.stylelintignore
admin/settings/analytics.php
analytics/classes/local/analyser/base.php
analytics/classes/local/analyser/by_course.php
analytics/classes/local/analyser/sitewide.php
analytics/classes/model.php
analytics/tests/fixtures/test_analyser.php [new file with mode: 0644]
analytics/tests/model_test.php
backup/backupfilesedit.php
backup/restorefile.php
backup/util/ui/renderer.php
blocks/calendar_upcoming/block_calendar_upcoming.php
calendar/classes/external/event_exporter_base.php
calendar/classes/external/footer_options_exporter.php
calendar/lib.php
calendar/templates/footer_options.mustache
calendar/templates/upcoming_mini.mustache [new file with mode: 0644]
completion/classes/external.php
completion/tests/externallib_test.php
composer.json
composer.lock
course/externallib.php
course/lib.php
course/renderer.php
course/tests/courselib_test.php
enrol/cohort/lib.php
enrol/cohort/tests/cohortlib_test.php
enrol/database/lib.php
enrol/database/tests/lib_test.php [new file with mode: 0644]
enrol/flatfile/lib.php
enrol/flatfile/tests/flatfile_test.php
enrol/locallib.php
enrol/lti/lib.php
enrol/lti/tests/lib_test.php
enrol/manual/lib.php
enrol/manual/tests/lib_test.php
enrol/meta/lib.php
enrol/meta/tests/plugin_test.php
enrol/paypal/lib.php
enrol/paypal/tests/paypal_test.php
enrol/self/lib.php
enrol/self/tests/self_test.php
lang/en/analytics.php
lang/en/calendar.php
lang/en/completion.php
lang/en/deprecated.txt
lang/en/role.php
lib/accesslib.php
lib/adodb/adodb-active-record.inc.php
lib/adodb/adodb-active-recordx.inc.php
lib/adodb/adodb-csvlib.inc.php
lib/adodb/adodb-datadict.inc.php
lib/adodb/adodb-error.inc.php
lib/adodb/adodb-errorhandler.inc.php
lib/adodb/adodb-errorpear.inc.php
lib/adodb/adodb-exceptions.inc.php
lib/adodb/adodb-iterator.inc.php
lib/adodb/adodb-lib.inc.php
lib/adodb/adodb-memcache.lib.inc.php
lib/adodb/adodb-pager.inc.php
lib/adodb/adodb-pear.inc.php
lib/adodb/adodb-perf.inc.php
lib/adodb/adodb-php4.inc.php
lib/adodb/adodb-time.inc.php
lib/adodb/adodb.inc.php
lib/adodb/datadict/datadict-access.inc.php
lib/adodb/datadict/datadict-db2.inc.php
lib/adodb/datadict/datadict-firebird.inc.php
lib/adodb/datadict/datadict-generic.inc.php
lib/adodb/datadict/datadict-ibase.inc.php
lib/adodb/datadict/datadict-informix.inc.php
lib/adodb/datadict/datadict-mssql.inc.php
lib/adodb/datadict/datadict-mssqlnative.inc.php
lib/adodb/datadict/datadict-mysql.inc.php
lib/adodb/datadict/datadict-oci8.inc.php
lib/adodb/datadict/datadict-postgres.inc.php
lib/adodb/datadict/datadict-sapdb.inc.php
lib/adodb/datadict/datadict-sqlite.inc.php
lib/adodb/datadict/datadict-sybase.inc.php
lib/adodb/drivers/adodb-access.inc.php
lib/adodb/drivers/adodb-ado.inc.php
lib/adodb/drivers/adodb-ado5.inc.php
lib/adodb/drivers/adodb-ado_access.inc.php
lib/adodb/drivers/adodb-ado_mssql.inc.php
lib/adodb/drivers/adodb-borland_ibase.inc.php
lib/adodb/drivers/adodb-csv.inc.php
lib/adodb/drivers/adodb-db2.inc.php
lib/adodb/drivers/adodb-db2oci.inc.php
lib/adodb/drivers/adodb-db2ora.inc.php
lib/adodb/drivers/adodb-fbsql.inc.php
lib/adodb/drivers/adodb-firebird.inc.php
lib/adodb/drivers/adodb-ibase.inc.php
lib/adodb/drivers/adodb-informix.inc.php
lib/adodb/drivers/adodb-informix72.inc.php
lib/adodb/drivers/adodb-ldap.inc.php
lib/adodb/drivers/adodb-mssql.inc.php
lib/adodb/drivers/adodb-mssqlnative.inc.php
lib/adodb/drivers/adodb-mssqlpo.inc.php
lib/adodb/drivers/adodb-mysql.inc.php
lib/adodb/drivers/adodb-mysqli.inc.php
lib/adodb/drivers/adodb-mysqlpo.inc.php
lib/adodb/drivers/adodb-mysqlt.inc.php
lib/adodb/drivers/adodb-netezza.inc.php
lib/adodb/drivers/adodb-oci8.inc.php
lib/adodb/drivers/adodb-oci805.inc.php
lib/adodb/drivers/adodb-oci8po.inc.php
lib/adodb/drivers/adodb-oci8quercus.inc.php
lib/adodb/drivers/adodb-odbc.inc.php
lib/adodb/drivers/adodb-odbc_db2.inc.php
lib/adodb/drivers/adodb-odbc_mssql.inc.php
lib/adodb/drivers/adodb-odbc_mssql2012.inc.php [deleted file]
lib/adodb/drivers/adodb-odbc_oracle.inc.php
lib/adodb/drivers/adodb-odbtp.inc.php
lib/adodb/drivers/adodb-odbtp_unicode.inc.php
lib/adodb/drivers/adodb-oracle.inc.php
lib/adodb/drivers/adodb-pdo.inc.php
lib/adodb/drivers/adodb-pdo_mssql.inc.php
lib/adodb/drivers/adodb-pdo_mysql.inc.php
lib/adodb/drivers/adodb-pdo_oci.inc.php
lib/adodb/drivers/adodb-pdo_pgsql.inc.php
lib/adodb/drivers/adodb-pdo_sqlite.inc.php
lib/adodb/drivers/adodb-postgres.inc.php
lib/adodb/drivers/adodb-postgres64.inc.php
lib/adodb/drivers/adodb-postgres7.inc.php
lib/adodb/drivers/adodb-postgres8.inc.php
lib/adodb/drivers/adodb-postgres9.inc.php
lib/adodb/drivers/adodb-proxy.inc.php
lib/adodb/drivers/adodb-sapdb.inc.php
lib/adodb/drivers/adodb-sqlanywhere.inc.php
lib/adodb/drivers/adodb-sqlite.inc.php
lib/adodb/drivers/adodb-sqlite3.inc.php
lib/adodb/drivers/adodb-sqlitepo.inc.php
lib/adodb/drivers/adodb-sybase.inc.php
lib/adodb/drivers/adodb-sybase_ase.inc.php
lib/adodb/drivers/adodb-text.inc.php
lib/adodb/drivers/adodb-vfp.inc.php
lib/adodb/perf/perf-db2.inc.php
lib/adodb/perf/perf-informix.inc.php
lib/adodb/perf/perf-mssql.inc.php
lib/adodb/perf/perf-mssqlnative.inc.php
lib/adodb/perf/perf-mysql.inc.php
lib/adodb/perf/perf-oci8.inc.php
lib/adodb/perf/perf-postgres.inc.php
lib/adodb/pivottable.inc.php
lib/adodb/readme_moodle.txt
lib/adodb/rsfilter.inc.php
lib/adodb/toexport.inc.php
lib/adodb/tohtml.inc.php
lib/classes/access/get_user_capability_course_helper.php [new file with mode: 0644]
lib/classes/analytics/target/course_dropout.php
lib/classes/event/course_module_completion_updated.php
lib/classes/oauth2/client.php
lib/completionlib.php
lib/db/access.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/enrollib.php
lib/filebrowser/file_info_context_course.php
lib/filebrowser/tests/file_browser_test.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/form/form.js
lib/html2text/Html2Text.php
lib/tests/accesslib_test.php
lib/tests/completionlib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/webdavlib.php
media/player/videojs/amd/build/Youtube-lazy.min.js
media/player/videojs/amd/build/loader.min.js
media/player/videojs/amd/build/video-lazy.min.js
media/player/videojs/amd/build/videojs-flash-lazy.min.js [new file with mode: 0644]
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/amd/src/loader.js
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/videojs-flash-lazy.js [new file with mode: 0644]
media/player/videojs/classes/plugin.php
media/player/videojs/fonts/VideoJS.svg
media/player/videojs/readme_moodle.txt
media/player/videojs/styles.css
media/player/videojs/thirdpartylibs.xml
media/player/videojs/videojs/lang/ar.js
media/player/videojs/videojs/lang/de.js
media/player/videojs/videojs/lang/en.js
media/player/videojs/videojs/lang/es.js
media/player/videojs/videojs/lang/fr.js
media/player/videojs/videojs/lang/gl.js [new file with mode: 0644]
media/player/videojs/videojs/lang/nl.js
media/player/videojs/videojs/lang/pt-PT.js [new file with mode: 0644]
media/player/videojs/videojs/lang/sk.js [new file with mode: 0644]
media/player/videojs/videojs/lang/tr.js
media/player/videojs/videojs/lang/vi.js
media/player/videojs/videojs/lang/zh-CN.js
media/player/videojs/videojs/video-js.swf
mod/assign/amd/build/grading_actions.min.js
mod/assign/amd/build/grading_navigation.min.js
mod/assign/amd/build/grading_panel.min.js
mod/assign/amd/src/grading_actions.js
mod/assign/amd/src/grading_navigation.js
mod/assign/amd/src/grading_panel.js
mod/assign/templates/grading_actions.mustache
mod/book/lib.php
mod/book/tests/behat/reorganize_chapters.feature [new file with mode: 0644]
mod/forum/lib.php
mod/glossary/lib.php
mod/lti/locallib.php
mod/quiz/attemptlib.php
mod/quiz/report/overview/report.php
mod/quiz/report/overview/tests/report_test.php
mod/quiz/report/responses/report.php
mod/workshop/classes/external.php
mod/workshop/tests/external_test.php
pix/i/completion-auto-n-override.png [new file with mode: 0644]
pix/i/completion-auto-n-override.svg [new file with mode: 0644]
pix/i/completion-auto-y-override.png [new file with mode: 0644]
pix/i/completion-auto-y-override.svg [new file with mode: 0644]
pix/i/completion-manual-n-override.png [new file with mode: 0644]
pix/i/completion-manual-n-override.svg [new file with mode: 0644]
pix/i/completion-manual-y-override.png [new file with mode: 0644]
pix/i/completion-manual-y-override.svg [new file with mode: 0644]
report/progress/amd/build/completion_override.min.js [new file with mode: 0644]
report/progress/amd/src/completion_override.js [new file with mode: 0644]
report/progress/index.php
report/progress/tests/behat/activity_completion_report.feature [new file with mode: 0644]
search/engine/solr/classes/engine.php
theme/boost/lang/en/theme_boost.php
theme/boost/lib.php
theme/boost/scss/moodle/calendar.scss
theme/boost/settings.php
theme/boost/templates/mod_assign/grading_actions.mustache
theme/bootstrapbase/less/moodle/calendar.less
theme/bootstrapbase/style/moodle.css
user/amd/build/unified_filter.min.js
user/amd/src/unified_filter.js
user/templates/unified_filter.mustache
user/tests/behat/filter_participants.feature
version.php

index 0622612..7d19e9c 100644 (file)
@@ -62,6 +62,8 @@ lib/validateurlsyntax.php
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
+media/player/videojs/amd/src/videojs-flash-lazy.js
+media/player/videojs/videojs/video-js.swf
 mod/assign/feedback/editpdf/fpdi/
 repository/s3/S3.php
 theme/boost/scss/bootstrap/
index cfcf702..c35c560 100644 (file)
@@ -63,6 +63,8 @@ lib/validateurlsyntax.php
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
+media/player/videojs/amd/src/videojs-flash-lazy.js
+media/player/videojs/videojs/video-js.swf
 mod/assign/feedback/editpdf/fpdi/
 repository/s3/S3.php
 theme/boost/scss/bootstrap/
index e8247c8..b42a252 100644 (file)
@@ -101,5 +101,10 @@ if ($hassiteconfig) {
         // Disable web interface evaluation and get predictions.
         $settings->add(new admin_setting_configcheckbox('analytics/onlycli', new lang_string('onlycli', 'analytics'),
             new lang_string('onlycliinfo', 'analytics'), 1));
+
+        // Training and prediction time limit per model.
+        $settings->add(new admin_setting_configduration('analytics/modeltimelimit', new lang_string('modeltimelimit', 'analytics'),
+            new lang_string('modeltimelimitinfo', 'analytics'), 20 * MINSECS));
+
     }
 }
index 63c75b5..e516977 100644 (file)
@@ -118,7 +118,7 @@ abstract class base {
      * \core_analytics\local\analyser\by_course and \core_analytics\local\analyser\sitewide are implementing
      * this method returning site courses (by_course) and the whole system (sitewide) as analysables.
      *
-     * @return \core_analytics\analysable[]
+     * @return \core_analytics\analysable[] Array of analysable elements using the analysable id as array key.
      */
     abstract public function get_analysables();
 
@@ -180,10 +180,16 @@ abstract class base {
      * @return \stored_file[]
      */
     public function get_analysable_data($includetarget) {
+        global $DB;
+
+        // Time limit control.
+        $modeltimelimit = intval(get_config('analytics', 'modeltimelimit'));
 
         $filesbytimesplitting = array();
 
-        $analysables = $this->get_analysables();
+        list($analysables, $processedanalysables) = $this->get_sorted_analysables($includetarget);
+
+        $inittime = time();
         foreach ($analysables as $analysable) {
 
             $files = $this->process_analysable($analysable, $includetarget);
@@ -192,6 +198,16 @@ abstract class base {
             foreach ($files as $timesplittingid => $file) {
                 $filesbytimesplitting[$timesplittingid][$analysable->get_id()] = $file;
             }
+
+            $this->update_analysable_analysed_time($processedanalysables, $analysable->get_id(), $includetarget);
+
+            // Apply time limit.
+            if (!$this->options['evaluation']) {
+                $timespent = time() - $inittime;
+                if ($modeltimelimit <= $timespent) {
+                    break;
+                }
+            }
         }
 
         // We join the datasets by time splitting method.
@@ -721,4 +737,86 @@ abstract class base {
             $data[1][] = $value;
         }
     }
+
+    /**
+     * Returns the list of analysables sorted in processing priority order.
+     *
+     * It will first return analysables that have never been analysed before
+     * and it will continue with the ones we have already seen by timeanalysed DESC
+     * order.
+     *
+     * @param bool $includetarget
+     * @return array(0 => \core_analytics\analysable[], 1 => \stdClass[])
+     */
+    protected function get_sorted_analysables($includetarget) {
+
+        $analysables = $this->get_analysables();
+
+        // Get the list of analysables that have been already processed.
+        $processedanalysables = $this->get_processed_analysables($includetarget);
+
+        // We want to start processing analysables we have not yet processed and later continue
+        // with analysables that we already processed.
+        $unseen = array_diff_key($analysables, $processedanalysables);
+
+        // Var $processed first as we want to respect its timeanalysed DESC order so analysables that
+        // have recently been processed are on the bottom of the stack.
+        $seen = array_intersect_key($processedanalysables, $analysables);
+        array_walk($seen, function(&$value, $analysableid) use ($analysables) {
+            // We replace the analytics_used_analysables record by the analysable object.
+            $value = $analysables[$analysableid];
+        });
+
+        return array($unseen + $seen, $processedanalysables);
+    }
+
+    /**
+     * Get analysables that have been already processed.
+     *
+     * @param bool $includetarget
+     * @return \stdClass[]
+     */
+    protected function get_processed_analysables($includetarget) {
+        global $DB;
+
+        $params = array('modelid' => $this->modelid);
+        $params['action'] = ($includetarget) ? 'training' : 'prediction';
+        $select = 'modelid = :modelid and action = :action';
+
+        // Weird select fields ordering for performance (analysableid key matching, analysableid is also unique by modelid).
+        return $DB->get_records_select('analytics_used_analysables', $select,
+            $params, 'timeanalysed DESC', 'analysableid, modelid, action, timeanalysed, id AS primarykey');
+    }
+
+    /**
+     * Updates the analysable analysis time.
+     *
+     * @param array $processedanalysables
+     * @param int $analysableid
+     * @param bool $includetarget
+     * @return null
+     */
+    protected function update_analysable_analysed_time($processedanalysables, $analysableid, $includetarget) {
+        global $DB;
+
+        if (!empty($processedanalysables[$analysableid])) {
+            $obj = $processedanalysables[$analysableid];
+
+            $obj->id = $obj->primarykey;
+            unset($obj->primarykey);
+
+            $obj->timeanalysed = time();
+            $DB->update_record('analytics_used_analysables', $obj);
+
+        } else {
+
+            $obj = new \stdClass();
+            $obj->modelid = $this->modelid;
+            $obj->action = ($includetarget) ? 'training' : 'prediction';
+            $obj->analysableid = $analysableid;
+            $obj->timeanalysed = time();
+
+            $DB->insert_record('analytics_used_analysables', $obj);
+        }
+    }
 }
index 8e5b266..6661806 100644 (file)
@@ -55,7 +55,7 @@ abstract class by_course extends base {
         foreach ($courses as $course) {
             // Skip the frontpage course.
             $analysable = \core_analytics\course::instance($course);
-            $analysables[] = $analysable;
+            $analysables[$analysable->get_id()] = $analysable;
         }
 
         if (empty($analysables)) {
index 2a21359..075b930 100644 (file)
@@ -42,6 +42,6 @@ abstract class sitewide extends base {
      */
     public function get_analysables() {
         $analysable = new \core_analytics\site();
-        return array($analysable);
+        return array(SYSCONTEXTID => $analysable);
     }
 }
index 4bf9ea1..7d9c2bd 100644 (file)
@@ -1446,6 +1446,7 @@ class model {
         $DB->delete_records('analytics_predict_samples', array('modelid' => $this->model->id));
         $DB->delete_records('analytics_train_samples', array('modelid' => $this->model->id));
         $DB->delete_records('analytics_used_files', array('modelid' => $this->model->id));
+        $DB->delete_records('analytics_used_analysables', array('modelid' => $this->model->id));
 
         // Purge all generated files.
         \core_analytics\dataset_manager::clear_model_files($this->model->id);
diff --git a/analytics/tests/fixtures/test_analyser.php b/analytics/tests/fixtures/test_analyser.php
new file mode 100644 (file)
index 0000000..736388c
--- /dev/null
@@ -0,0 +1,48 @@
+<?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/>.
+
+/**
+ * Test analyser.
+ *
+ * @package   core_analytics
+ * @copyright 2017 David MonllaĆ³ {@link http://www.davidmonllao.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Test analyser.
+ *
+ * @package   core_analytics
+ * @copyright 2017 David MonllaĆ³ {@link http://www.davidmonllao.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_analyser extends \core\analytics\analyser\courses {
+
+    /**
+     * Overwritten to add a delay.
+     *
+     * @param \core_analytics\analysable $analysable
+     * @param mixed $includetarget
+     * @return null
+     */
+    public function process_analysable($analysable, $includetarget) {
+        // A bit more than 1 second.
+        usleep(1100000);
+        return parent::process_analysable($analysable, $includetarget);
+    }
+}
index fcb7eac..ef2715a 100644 (file)
@@ -28,6 +28,8 @@ require_once(__DIR__ . '/fixtures/test_indicator_max.php');
 require_once(__DIR__ . '/fixtures/test_indicator_min.php');
 require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
 require_once(__DIR__ . '/fixtures/test_target_shortname.php');
+require_once(__DIR__ . '/fixtures/test_target_course_level_shortname.php');
+require_once(__DIR__ . '/fixtures/test_analyser.php');
 
 /**
  * Unit tests for the model.
@@ -256,6 +258,64 @@ class analytics_model_testcase extends advanced_testcase {
         $this->assertTrue(\core_analytics\model::exists($target));
     }
 
+    /**
+     * test_model_timelimit
+     *
+     * @return null
+     */
+    public function test_model_timelimit() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        set_config('modeltimelimit', 2, 'analytics');
+
+        $courses = array();
+        for ($i = 0; $i < 5; $i++) {
+            $course = $this->getDataGenerator()->create_course();
+            $analysable = new \core_analytics\course($course);
+            $courses[$analysable->get_id()] = $course;
+        }
+
+        $target = new test_target_course_level_shortname();
+        $analyser = new test_analyser(1, $target, [], [], []);
+
+        // Each analysable element takes 1.1 secs, so the max (and likely) number of analysable
+        // elements that will be processed is 2.
+        $analyser->get_analysable_data(false);
+        $params = array('modelid' => 1, 'action' => 'prediction');
+        $this->assertLessThanOrEqual(2, $DB->count_records('analytics_used_analysables', $params));
+
+        $analyser->get_analysable_data(false);
+        $this->assertLessThanOrEqual(4, $DB->count_records('analytics_used_analysables', $params));
+
+        // Check that analysable elements have been processed following the analyser order
+        // (course->sortorder here). We can not check this nicely after next get_analysable_data round
+        // because the first analysed element will be analysed again.
+        $analysedelems = $DB->get_records('analytics_used_analysables', $params, 'timeanalysed ASC');
+        // Just a default for the first checked element.
+        $last = (object)['sortorder' => PHP_INT_MAX];
+        foreach ($analysedelems as $analysed) {
+            if ($courses[$analysed->analysableid]->sortorder > $last->sortorder) {
+                $this->fail('Analysable elements have not been analysed sorted by course sortorder.');
+            }
+            $last = $courses[$analysed->analysableid];
+        }
+
+        $analyser->get_analysable_data(false);
+        $this->assertGreaterThanOrEqual(5, $DB->count_records('analytics_used_analysables', $params));
+
+        // New analysable elements are immediately pulled.
+        $this->getDataGenerator()->create_course();
+        $analyser->get_analysable_data(false);
+        $this->assertGreaterThanOrEqual(6, $DB->count_records('analytics_used_analysables', $params));
+
+        // Training and prediction data do not get mixed.
+        $analyser->get_analysable_data(true);
+        $params = array('modelid' => 1, 'action' => 'training');
+        $this->assertLessThanOrEqual(2, $DB->count_records('analytics_used_analysables', $params));
+    }
+
     /**
      * Generates a model log record.
      */
index 86ce136..9c171ae 100644 (file)
@@ -42,6 +42,9 @@ $url = new moodle_url('/backup/backupfilesedit.php', array('currentcontext'=>$cu
 
 require_login($course, false, $cm);
 require_capability('moodle/restore:uploadfile', $context);
+if ($filearea == 'automated' && !can_download_from_backup_filearea($filearea, $context)) {
+    throw new required_capability_exception($context, 'moodle/backup:downloadfile', 'nopermissions', '');
+}
 
 $PAGE->set_url($url);
 $PAGE->set_context($context);
index 7b0fcd2..be9ae4a 100644 (file)
@@ -79,6 +79,9 @@ if (!check_dir_exists($tmpdir, true, true)) {
 
 // choose the backup file from backup files tree
 if ($action == 'choosebackupfile') {
+    if ($filearea == 'automated') {
+        require_capability('moodle/restore:viewautomatedfilearea', $context);
+    }
     if ($fileinfo = $browser->get_file_info($filecontext, $component, $filearea, $itemid, $filepath, $filename)) {
         if (is_a($fileinfo, 'file_info_stored')) {
             // Use the contenthash rather than copying the file where possible,
index b4855bb..7499d6f 100644 (file)
@@ -573,27 +573,51 @@ class core_backup_renderer extends plugin_renderer_base {
             $params['contextid'] = $viewer->currentcontext->id;
             $params['itemid'] = $file->get_itemid();
             $restoreurl = new moodle_url('/backup/restorefile.php', $params);
+            $restorelink = html_writer::link($restoreurl, get_string('restore'));
+            $downloadlink = html_writer::link($fileurl, get_string('download'));
+
+            // Conditional display of the restore and download links, initially only for the 'automated' filearea.
+            if ($params['filearea'] == 'automated') {
+                if (!has_capability('moodle/restore:viewautomatedfilearea', $viewer->currentcontext)) {
+                    $restorelink = '';
+                }
+                if (!can_download_from_backup_filearea($params['filearea'], $viewer->currentcontext)) {
+                    $downloadlink = '';
+                }
+            }
             $table->data[] = array(
                 $file->get_filename(),
                 userdate($file->get_timemodified()),
                 display_size($file->get_filesize()),
-                html_writer::link($fileurl, get_string('download')),
-                html_writer::link($restoreurl, get_string('restore')),
+                $downloadlink,
+                $restorelink,
                 );
         }
 
         $html = html_writer::table($table);
-        $html .= $this->output->single_button(
-            new moodle_url('/backup/backupfilesedit.php', array(
-                'currentcontext' => $viewer->currentcontext->id,
-                'contextid' => $viewer->filecontext->id,
-                'filearea' => $viewer->filearea,
-                'component' => $viewer->component,
-                'returnurl' => $this->page->url->out())
-            ),
-            get_string('managefiles', 'backup'),
-            'post'
-        );
+
+        // For automated backups, the ability to manage backup files is controlled by the ability to download them.
+        // All files must be from the same file area in a backup_files_viewer.
+        $canmanagebackups = true;
+        if ($viewer->filearea == 'automated') {
+            if (!can_download_from_backup_filearea($viewer->filearea, $viewer->currentcontext)) {
+                $canmanagebackups = false;
+            }
+        }
+
+        if ($canmanagebackups) {
+            $html .= $this->output->single_button(
+                new moodle_url('/backup/backupfilesedit.php', array(
+                        'currentcontext' => $viewer->currentcontext->id,
+                        'contextid' => $viewer->filecontext->id,
+                        'filearea' => $viewer->filearea,
+                        'component' => $viewer->component,
+                        'returnurl' => $this->page->url->out())
+                ),
+                get_string('managefiles', 'backup'),
+                'post'
+            );
+        }
 
         return $html;
     }
index 858a0e4..825868c 100644 (file)
@@ -46,106 +46,35 @@ class block_calendar_upcoming extends block_base {
         $this->content = new stdClass;
         $this->content->text = '';
 
-        $filtercourse    = array();
-        if (empty($this->instance)) { // Overrides: use no course at all.
-            $courseshown = false;
-            $this->content->footer = '';
-
+        $renderer = $this->page->get_renderer('core_calendar');
+        $courseid = $this->page->course->id;
+        $issite = ($courseid == SITEID);
+
+        if ($issite) {
+            // Being displayed at site level. This will cause the filter to fall back to auto-detecting
+            // the list of courses it will be grabbing events from.
+            $course = get_site();
+            $courses = calendar_get_default_courses();
         } else {
-            $courseshown = $this->page->course->id;
-            $this->content->footer = '<div class="gotocal"><a href="'.$CFG->wwwroot.
-                                     '/calendar/view.php?view=upcoming&amp;course='.$courseshown.'">'.
-                                      get_string('gotocalendar', 'calendar').'</a>...</div>';
-            $context = context_course::instance($courseshown);
-            if (has_any_capability(array('moodle/calendar:manageentries', 'moodle/calendar:manageownentries'), $context)) {
-                $this->content->footer .= '<div class="newevent"><a href="'.$CFG->wwwroot.
-                                          '/calendar/event.php?action=new&amp;course='.$courseshown.'">'.
-                                           get_string('newevent', 'calendar').'</a>...</div>';
-            }
-            if ($courseshown == SITEID) {
-                // Being displayed at site level. This will cause the filter to fall back to auto-detecting
-                // the list of courses it will be grabbing events from.
-                $filtercourse = calendar_get_default_courses();
-            } else {
-                // Forcibly filter events to include only those from the particular course we are in.
-                $filtercourse = array($courseshown => $this->page->course);
-            }
+            // Forcibly filter events to include only those from the particular course we are in.
+            $course = $this->page->course;
+            $courses = [$course->id => $course];
         }
+        $calendar = new calendar_information(0, 0, 0, time());
+        $calendar->set_sources($course, $courses);
 
-        list($courses, $group, $user) = calendar_set_filters($filtercourse);
-
-        $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD;
-        if (isset($CFG->calendar_lookahead)) {
-            $defaultlookahead = intval($CFG->calendar_lookahead);
-        }
-        $lookahead = get_user_preferences('calendar_lookahead', $defaultlookahead);
-
-        $defaultmaxevents = CALENDAR_DEFAULT_UPCOMING_MAXEVENTS;
-        if (isset($CFG->calendar_maxevents)) {
-            $defaultmaxevents = intval($CFG->calendar_maxevents);
-        }
-        $maxevents = get_user_preferences('calendar_maxevents', $defaultmaxevents);
-        $events = calendar_get_upcoming($courses, $group, $user, $lookahead, $maxevents);
-
-        if (!empty($this->instance)) {
-            $link = 'view.php?view=day&amp;course='.$courseshown.'&amp;';
-            $showcourselink = ($this->page->course->id == SITEID);
-            $this->content->text = self::get_upcoming_content($events, $link, $showcourselink);
-        }
+        list($data, $template) = calendar_get_view($calendar, 'upcoming_mini');
+        $this->content->text .= $renderer->render_from_template($template, $data);
 
         if (empty($this->content->text)) {
             $this->content->text = '<div class="post">'. get_string('noupcomingevents', 'calendar').'</div>';
         }
 
-        return $this->content;
-    }
-
-    /**
-     * Get the upcoming event block content.
-     *
-     * @param array $events list of events
-     * @param \moodle_url|string $linkhref link to event referer
-     * @param boolean $showcourselink whether links to courses should be shown
-     * @return string|null $content html block content
-     */
-    public static function get_upcoming_content($events, $linkhref = null, $showcourselink = false) {
-        $content = '';
-        $lines = count($events);
-
-        if (!$lines) {
-            return $content;
-        }
-
-        for ($i = 0; $i < $lines; ++$i) {
-            if (!isset($events[$i]->time)) {
-                continue;
-            }
-            $events[$i] = calendar_add_event_metadata($events[$i]);
-            $content .= '<div class="event"><span class="icon c0">' . $events[$i]->icon . '</span>';
-            if (!empty($events[$i]->referer)) {
-                // That's an activity event, so let's provide the hyperlink.
-                $content .= $events[$i]->referer;
-            } else {
-                if (!empty($linkhref)) {
-                    $href = calendar_get_link_href(new \moodle_url(CALENDAR_URL . $linkhref), 0, 0, 0,
-                        $events[$i]->timestart);
-                    $href->set_anchor('event_' . $events[$i]->id);
-                    $content .= \html_writer::link($href, $events[$i]->name);
-                } else {
-                    $content .= $events[$i]->name;
-                }
-            }
-            $events[$i]->time = str_replace('&raquo;', '<br />&raquo;', $events[$i]->time);
-            if ($showcourselink && !empty($events[$i]->courselink)) {
-                $content .= \html_writer::div($events[$i]->courselink, 'course');
-            }
-            $content .= '<div class="date">' . $events[$i]->time . '</div></div>';
-            if ($i < $lines - 1) {
-                $content .= '<hr />';
-            }
-        }
+        $this->content->footer = '<div class="gotocal">
+                <a href="'.$CFG->wwwroot.'/calendar/view.php?view=upcoming&amp;course='.$courseid.'">'.
+                get_string('gotocalendar', 'calendar').'</a>...</div>';
 
-        return $content;
+        return $this->content;
     }
 }
 
index 85457e2..0f17777 100644 (file)
@@ -208,6 +208,9 @@ class event_exporter_base extends exporter {
             'editurl' => [
                 'type' => PARAM_URL
             ],
+            'viewurl' => [
+                'type' => PARAM_URL
+            ],
             'formattedtime' => [
                 'type' => PARAM_RAW,
             ],
@@ -280,6 +283,10 @@ class event_exporter_base extends exporter {
         $editurl = new moodle_url('/calendar/event.php', ['action' => 'edit', 'id' => $event->get_id(),
                 'course' => $courseid]);
         $values['editurl'] = $editurl->out(false);
+        $viewurl = new moodle_url('/calendar/view.php', ['view' => 'day', 'course' => $courseid,
+                'time' => $timesort]);
+        $viewurl->set_anchor('event_' . $event->get_id());
+        $values['viewurl'] = $viewurl->out(false);
         $values['formattedtime'] = calendar_format_event_time($legacyevent, time(), null, false,
                 $timesort);
 
index e4478bb..9cce687 100644 (file)
@@ -76,18 +76,6 @@ class footer_options_exporter extends exporter {
         return new \single_button($exportcalendarurl, get_string('exportcalendar', 'calendar'));
     }
 
-    /**
-     * Get the iCal url.
-     *
-     * @return string The iCal url.
-     */
-    protected function get_ical_url() {
-        if ($this->token) {
-            return new moodle_url('/calendar/export_execute.php', ['preset_what' => 'all',
-                    'preset_time' => 'recentupcoming', 'userid' => $this->userid, 'authtoken' => $this->token]);
-        }
-    }
-
     /**
      * Get manage subscription button.
      *
@@ -120,7 +108,6 @@ class footer_options_exporter extends exporter {
             if ($managesubscriptionbutton = $this->get_manage_subscriptions_button()) {
                 $values->managesubscriptionbutton = $managesubscriptionbutton->export_for_template($output);
             }
-            $values->icalurl = $this->get_ical_url()->out(false);
         }
 
         return (array) $values;
@@ -141,10 +128,6 @@ class footer_options_exporter extends exporter {
                 'type' => PARAM_RAW,
                 'default' => null,
             ],
-            'icalurl' => [
-                'type' => PARAM_URL,
-                'default' => null,
-            ],
         );
     }
 }
index 916ed6e..5b9d05e 100644 (file)
@@ -1318,77 +1318,6 @@ function calendar_get_starting_weekday() {
     return $calendartype->get_starting_weekday();
 }
 
-/**
- * Gets the calendar upcoming event.
- *
- * @param array $courses array of courses
- * @param array|int|bool $groups array of groups, group id or boolean for all/no group events
- * @param array|int|bool $users array of users, user id or boolean for all/no user events
- * @param int $daysinfuture number of days in the future we 'll look
- * @param int $maxevents maximum number of events
- * @param int $fromtime start time
- * @return array $output array of upcoming events
- */
-function calendar_get_upcoming($courses, $groups, $users, $daysinfuture, $maxevents, $fromtime=0) {
-    global $COURSE;
-
-    $display = new \stdClass;
-    $display->range = $daysinfuture; // How many days in the future we 'll look.
-    $display->maxevents = $maxevents;
-
-    $output = array();
-
-    $processed = 0;
-    $now = time(); // We 'll need this later.
-    $usermidnighttoday = usergetmidnight($now);
-
-    if ($fromtime) {
-        $display->tstart = $fromtime;
-    } else {
-        $display->tstart = $usermidnighttoday;
-    }
-
-    // This works correctly with respect to the user's DST, but it is accurate
-    // only because $fromtime is always the exact midnight of some day!
-    $display->tend = usergetmidnight($display->tstart + DAYSECS * $display->range + 3 * HOURSECS) - 1;
-
-    // Get the events matching our criteria.
-    $events = calendar_get_legacy_events($display->tstart, $display->tend, $users, $groups, $courses);
-
-    // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after
-    // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month
-    // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra
-    // arguments to this function.
-    $hrefparams = array();
-    if (!empty($courses)) {
-        $courses = array_diff($courses, array(SITEID));
-        if (count($courses) == 1) {
-            $hrefparams['course'] = reset($courses);
-        }
-    }
-
-    if ($events !== false) {
-        foreach ($events as $event) {
-            if (!empty($event->modulename)) {
-                $instances = get_fast_modinfo($event->courseid)->get_instances_of($event->modulename);
-                if (empty($instances[$event->instance]->uservisible)) {
-                    continue;
-                }
-            }
-
-            if ($processed >= $display->maxevents) {
-                break;
-            }
-
-            $event->time = calendar_format_event_time($event, $now, $hrefparams);
-            $output[] = $event;
-            $processed++;
-        }
-    }
-
-    return $output;
-}
-
 /**
  * Get a HTML link to a course.
  *
@@ -3147,7 +3076,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
     if ($view === 'day') {
         $tstart = $type->convert_to_timestamp($date['year'], $date['mon'], $date['mday']);
         $tend = $tstart + DAYSECS - 1;
-    } else if ($view === 'upcoming') {
+    } else if ($view === 'upcoming' || $view === 'upcoming_mini') {
         if (isset($CFG->calendar_lookahead)) {
             $defaultlookahead = intval($CFG->calendar_lookahead);
         } else {
@@ -3234,10 +3163,15 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
         $day = new \core_calendar\external\calendar_day_exporter($calendar, $related);
         $data = $day->export($renderer);
         $template = 'core_calendar/calendar_day';
-    } else if ($view == "upcoming") {
+    } else if ($view == "upcoming" || $view == "upcoming_mini") {
         $upcoming = new \core_calendar\external\calendar_upcoming_exporter($calendar, $related);
         $data = $upcoming->export($renderer);
-        $template = 'core_calendar/calendar_upcoming';
+
+        if ($view == "upcoming") {
+            $template = 'core_calendar/calendar_upcoming';
+        } else if ($view == "upcoming_mini") {
+            $template = 'core_calendar/upcoming_mini';
+        }
     }
 
     return [$data, $template];
index d9769b2..427919e 100644 (file)
@@ -22,8 +22,7 @@
     Example context (json):
     {
         "exportcalendarbutton": "<button class='btn btn-secondary'>Export calendar</button>",
-        "managesubscriptionbutton": "<button class='btn btn-secondary'>Manage subscriptions</button>",
-        "icalurl": "http://example.com/"
+        "managesubscriptionbutton": "<button class='btn btn-secondary'>Manage subscriptions</button>"
     }
 }}
 <div class="bottom">
@@ -33,7 +32,4 @@
     {{#managesubscriptionbutton}}
         {{> core/single_button }}
     {{/managesubscriptionbutton}}
-    {{#icalurl}}
-        <a href="{{icalurl}}" title="{{#str}} quickdownloadcalendar, calendar {{/str}}" class="ical-link m-l-1">iCal</a>
-    {{/icalurl}}
 </div>
diff --git a/calendar/templates/upcoming_mini.mustache b/calendar/templates/upcoming_mini.mustache
new file mode 100644 (file)
index 0000000..8d02953
--- /dev/null
@@ -0,0 +1,43 @@
+{{!
+    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/>.
+}}
+{{!
+    @template calendar/upcoming_mini
+
+    Calendar upcoming view for blocks.
+
+    The purpose of this template is to render the upcoming view for blocks.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Example context (json):
+    {
+    }
+}}
+<div class="card-text content">
+    {{#events}}
+        <div class="event">
+            <span>{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}</span>
+            <a href="{{viewurl}}">{{{name}}}</a>
+            <div class="date">{{{formattedtime}}}</div>
+        </div>
+        <hr>
+    {{/events}}
+</div>
index 7979e61..142496b 100644 (file)
@@ -114,6 +114,86 @@ class core_completion_external extends external_api {
         );
     }
 
+    /**
+     * Describes the parameters for override_activity_completion_status.
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 3.4
+     */
+    public static function override_activity_completion_status_parameters() {
+        return new external_function_parameters (
+            array(
+                'userid' => new external_value(PARAM_INT, 'user id'),
+                'cmid' => new external_value(PARAM_INT, 'course module id'),
+                'newstate' => new external_value(PARAM_INT, 'the new activity completion state'),
+            )
+        );
+    }
+
+    /**
+     * Update completion status for a user in an activity.
+     * @param  int $userid    User id
+     * @param  int $cmid      Course module id
+     * @param  int $newstate  Activity completion
+     * @return array          Array containing the current (updated) completion status.
+     * @since Moodle 3.4
+     * @throws moodle_exception
+     */
+    public static function override_activity_completion_status($userid, $cmid, $newstate) {
+        // Validate and normalize parameters.
+        $params = self::validate_parameters(self::override_activity_completion_status_parameters(),
+            array('userid' => $userid, 'cmid' => $cmid, 'newstate' => $newstate));
+        $userid = $params['userid'];
+        $cmid = $params['cmid'];
+        $newstate = $params['newstate'];
+
+        $context = context_module::instance($cmid);
+        self::validate_context($context);
+
+        list($course, $cm) = get_course_and_cm_from_cmid($cmid);
+
+        // Set up completion object and check it is enabled.
+        $completion = new completion_info($course);
+        if (!$completion->is_enabled()) {
+            throw new moodle_exception('completionnotenabled', 'completion');
+        }
+
+        // Update completion state and get the new state back.
+        $completion->update_state($cm, $newstate, $userid, true);
+        $completiondata = $completion->get_data($cm, false, $userid);
+
+        // Return the current state of completion.
+        return [
+            'cmid' => $completiondata->coursemoduleid,
+            'userid' => $completiondata->userid,
+            'state' => $completiondata->completionstate,
+            'timecompleted' => $completiondata->timemodified,
+            'overrideby' => $completiondata->overrideby,
+            'tracking' => $completion->is_enabled($cm)
+        ];
+    }
+
+    /**
+     * Describes the override_activity_completion_status return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.4
+     */
+    public static function override_activity_completion_status_returns() {
+
+        return new external_single_structure(
+            array(
+                'cmid' => new external_value(PARAM_INT, 'The course module id'),
+                'userid' => new external_value(PARAM_INT, 'The user id to which the completion info belongs'),
+                'state'   => new external_value(PARAM_INT, 'The current completion state.'),
+                'timecompleted' => new external_value(PARAM_INT, 'time of completion'),
+                'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the status, or null'),
+                'tracking'      => new external_value(PARAM_INT, 'type of tracking:
+                                                                    0 means none, 1 manual, 2 automatic'),
+            )
+        );
+    }
+
     /**
      * Returns description of method parameters
      *
index 61ef537..0a5ec14 100644 (file)
@@ -186,6 +186,83 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
         $this->assertCount(3, $result['statuses']);
     }
 
+    /**
+     * Test override_activity_completion_status
+     */
+    public function test_override_activity_completion_status() {
+        global $DB, $CFG;
+        $this->resetAfterTest(true);
+
+        // Create course with teacher and student enrolled.
+        $CFG->enablecompletion = true;
+        $course  = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
+        $student = $this->getDataGenerator()->create_user();
+        $teacher = $this->getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
+        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
+        $coursecontext = context_course::instance($course->id);
+
+        // Create 2 activities, one with manual completion (data), one with automatic completion triggered by viewiung it (forum).
+        $data    = $this->getDataGenerator()->create_module('data', ['course' => $course->id], ['completion' => 1]);
+        $forum   = $this->getDataGenerator()->create_module('forum',  ['course' => $course->id],
+                                                            ['completion' => 2, 'completionview' => 1]);
+        $cmdata = get_coursemodule_from_id('data', $data->cmid);
+        $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
+
+        // Manually complete the data activity as the student.
+        $this->setUser($student);
+        $completion = new completion_info($course);
+        $completion->update_state($cmdata, COMPLETION_COMPLETE);
+
+        // Test overriding the status of the manual-completion-activity 'incomplete'.
+        $this->setUser($teacher);
+        $result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_INCOMPLETE);
+        $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
+        $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
+        $completiondata = $completion->get_data($cmdata, false, $student->id);
+        $this->assertEquals(COMPLETION_INCOMPLETE, $completiondata->completionstate);
+
+        // Test overriding the status of the manual-completion-activity back to 'complete'.
+        $result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_COMPLETE);
+        $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
+        $this->assertEquals($result['state'], COMPLETION_COMPLETE);
+        $completiondata = $completion->get_data($cmdata, false, $student->id);
+        $this->assertEquals(COMPLETION_COMPLETE, $completiondata->completionstate);
+
+        // Test overriding the status of the auto-completion-activity to 'complete'.
+        $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
+        $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
+        $this->assertEquals($result['state'], COMPLETION_COMPLETE);
+        $completionforum = $completion->get_data($cmforum, false, $student->id);
+        $this->assertEquals(COMPLETION_COMPLETE, $completionforum->completionstate);
+
+        // Test overriding the status of the auto-completion-activity to 'incomplete'.
+        $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_INCOMPLETE);
+        $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
+        $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
+        $completionforum = $completion->get_data($cmforum, false, $student->id);
+        $this->assertEquals(COMPLETION_INCOMPLETE, $completionforum->completionstate);
+
+        // Test overriding the status of the auto-completion-activity to an invalid state. It should remain incomplete.
+        $this->expectException('moodle_exception');
+        $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, 3);
+        $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
+        $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
+        $completionforum = $completion->get_data($cmforum, false, $student->id);
+        $this->assertEquals(COMPLETION_INCOMPLETE, $completionforum->completionstate);
+
+        // Test overriding the status of the auto-completion-activity for a user without capabilities. It should remain incomplete.
+        $this->expectException('moodle_exception');
+        unassign_capability('moodle/course:overridecompletion', $teacherrole->id, $coursecontext);
+        $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, 1);
+        $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
+        $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
+        $completionforum = $completion->get_data($cmforum, false, $student->id);
+        $this->assertEquals(COMPLETION_INCOMPLETE, $completionforum->completionstate);
+    }
+
     /**
      * Test get_course_completion_status
      */
index dcb07cf..a656ec1 100644 (file)
@@ -7,7 +7,7 @@
     "require-dev": {
         "phpunit/phpunit": "5.5.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "3.34.0",
+        "moodlehq/behat-extension": "3.34.1",
         "mikey179/vfsStream": "^1.6"
     }
 }
index cb64acd..1150efe 100644 (file)
@@ -4,8 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "87cf286828dd74f76aa6021b4cf7ecd5",
-    "content-hash": "ce905d6cf20a164ed747648b85732e8d",
+    "content-hash": "1906bd3ac810927fb8084fe4e2967d36",
     "packages": [],
     "packages-dev": [
         {
                 "symfony",
                 "testing"
             ],
-            "time": "2017-05-15 16:49:16"
+            "time": "2017-05-15T16:49:16+00:00"
         },
         {
             "name": "behat/gherkin",
-            "version": "v4.4.5",
+            "version": "v4.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Gherkin.git",
-                "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74"
+                "reference": "74ac03d52c5e23ad8abd5c5cce4ab0e8dc1b530a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74",
-                "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74",
+                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/74ac03d52c5e23ad8abd5c5cce4ab0e8dc1b530a",
+                "reference": "74ac03d52c5e23ad8abd5c5cce4ab0e8dc1b530a",
                 "shasum": ""
             },
             "require": {
                 "gherkin",
                 "parser"
             ],
-            "time": "2016-10-30 11:50:56"
+            "time": "2017-08-30T11:04:43+00:00"
         },
         {
             "name": "behat/mink",
                 "testing",
                 "web"
             ],
-            "time": "2016-03-05 08:26:18"
+            "time": "2016-03-05T08:26:18+00:00"
         },
         {
             "name": "behat/mink-browserkit-driver",
                 "browser",
                 "testing"
             ],
-            "time": "2016-03-05 08:59:47"
+            "time": "2016-03-05T08:59:47+00:00"
         },
         {
             "name": "behat/mink-extension",
                 "test",
                 "web"
             ],
-            "time": "2016-02-15 07:55:18"
+            "time": "2016-02-15T07:55:18+00:00"
         },
         {
             "name": "behat/mink-goutte-driver",
                 "headless",
                 "testing"
             ],
-            "time": "2016-03-05 09:04:22"
+            "time": "2016-03-05T09:04:22+00:00"
         },
         {
             "name": "behat/mink-selenium2-driver",
                 "testing",
                 "webdriver"
             ],
-            "time": "2016-03-05 09:10:18"
+            "time": "2016-03-05T09:10:18+00:00"
         },
         {
             "name": "behat/transliterator",
                 "slug",
                 "transliterator"
             ],
-            "time": "2017-04-04 11:38:05"
+            "time": "2017-04-04T11:38:05+00:00"
         },
         {
             "name": "container-interop/container-interop",
             ],
             "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
             "homepage": "https://github.com/container-interop/container-interop",
-            "time": "2017-02-14 19:40:03"
+            "time": "2017-02-14T19:40:03+00:00"
         },
         {
             "name": "doctrine/instantiator",
                 "constructor",
                 "instantiate"
             ],
-            "time": "2015-06-14 21:17:01"
+            "time": "2015-06-14T21:17:01+00:00"
         },
         {
             "name": "fabpot/goutte",
             "keywords": [
                 "scraper"
             ],
-            "time": "2017-01-03 13:21:43"
+            "time": "2017-01-03T13:21:43+00:00"
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "6.2.3",
+            "version": "6.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006"
+                "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006",
-                "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
+                "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "ext-curl": "*",
-                "phpunit/phpunit": "^4.0",
+                "phpunit/phpunit": "^4.0 || ^5.0",
                 "psr/log": "^1.0"
             },
+            "suggest": {
+                "psr/log": "Required for using the Log middleware"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
                 "rest",
                 "web service"
             ],
-            "time": "2017-02-28 22:50:30"
+            "time": "2017-06-22T18:50:49+00:00"
         },
         {
             "name": "guzzlehttp/promises",
             "keywords": [
                 "promise"
             ],
-            "time": "2016-12-20 10:07:11"
+            "time": "2016-12-20T10:07:11+00:00"
         },
         {
             "name": "guzzlehttp/psr7",
                 "uri",
                 "url"
             ],
-            "time": "2017-03-20 17:10:46"
+            "time": "2017-03-20T17:10:46+00:00"
         },
         {
             "name": "instaclick/php-webdriver",
-            "version": "1.4.3",
+            "version": "1.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/instaclick/php-webdriver.git",
-                "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb"
+                "reference": "6fa959452e774dcaed543faad3a9d1a37d803327"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
-                "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
+                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/6fa959452e774dcaed543faad3a9d1a37d803327",
+                "reference": "6fa959452e774dcaed543faad3a9d1a37d803327",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.2"
             },
             "require-dev": {
-                "satooshi/php-coveralls": "dev-master"
+                "phpunit/phpunit": "^4.8",
+                "satooshi/php-coveralls": "^1.0||^2.0"
             },
             "type": "library",
             "extra": {
                 {
                     "name": "Anthon Pang",
                     "email": "apang@softwaredevelopment.ca",
-                    "role": "Fork maintainer"
+                    "role": "Fork Maintainer"
                 }
             ],
             "description": "PHP WebDriver for Selenium 2",
                 "webdriver",
                 "webtest"
             ],
-            "time": "2015-06-15 20:19:33"
+            "time": "2017-06-30T04:02:48+00:00"
         },
         {
             "name": "mikey179/vfsStream",
-            "version": "v1.6.4",
+            "version": "v1.6.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/mikey179/vfsStream.git",
-                "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592"
+                "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592",
-                "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592",
+                "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145",
+                "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Virtual file system to mock the real file system in unit tests.",
             "homepage": "http://vfs.bovigo.org/",
-            "time": "2016-07-18 14:02:57"
+            "time": "2017-08-01T08:02:14+00:00"
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.34.0",
+            "version": "v3.34.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "a1f956fb13ef4c430ceb37c6c1ffcd355d956a22"
+                "reference": "8d0c4248b1efe6bc141fc7dc17d16fed1df017a5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/a1f956fb13ef4c430ceb37c6c1ffcd355d956a22",
-                "reference": "a1f956fb13ef4c430ceb37c6c1ffcd355d956a22",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/8d0c4248b1efe6bc141fc7dc17d16fed1df017a5",
+                "reference": "8d0c4248b1efe6bc141fc7dc17d16fed1df017a5",
                 "shasum": ""
             },
             "require": {
                 "behat/mink-extension": "~2.2",
                 "behat/mink-goutte-driver": "~1.2",
                 "behat/mink-selenium2-driver": "~1.3",
+                "guzzlehttp/guzzle": "^6.3",
                 "php": ">=5.4.4",
                 "symfony/process": "2.8.*"
             },
                 "Behat",
                 "moodle"
             ],
-            "time": "2017-01-20 02:48:22"
+            "time": "2017-09-29T18:10:58+00:00"
         },
         {
             "name": "myclabs/deep-copy",
                 "object",
                 "object graph"
             ],
-            "time": "2017-04-12 18:52:22"
+            "time": "2017-04-12T18:52:22+00:00"
         },
         {
             "name": "phpdocumentor/reflection-common",
-            "version": "1.0",
+            "version": "1.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
-                "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
+                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
-                "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
                 "shasum": ""
             },
             "require": {
                 "reflection",
                 "static analysis"
             ],
-            "time": "2015-12-27 11:43:31"
+            "time": "2017-09-11T18:02:19+00:00"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "3.1.1",
+            "version": "4.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
+                "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
-                "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2",
+                "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5",
+                "php": "^7.0",
                 "phpdocumentor/reflection-common": "^1.0@dev",
-                "phpdocumentor/type-resolver": "^0.2.0",
+                "phpdocumentor/type-resolver": "^0.4.0",
                 "webmozart/assert": "^1.0"
             },
             "require-dev": {
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2016-09-30 07:12:33"
+            "time": "2017-08-30T18:51:59+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
-            "version": "0.2.1",
+            "version": "0.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
+                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
-                "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5",
+                "php": "^5.5 || ^7.0",
                 "phpdocumentor/reflection-common": "^1.0"
             },
             "require-dev": {
                     "email": "me@mikevanriel.com"
                 }
             ],
-            "time": "2016-11-25 06:54:22"
+            "time": "2017-07-14T14:27:02+00:00"
         },
         {
             "name": "phpspec/prophecy",
-            "version": "v1.7.0",
+            "version": "v1.7.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
+                "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
-                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
+                "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
                 "shasum": ""
             },
             "require": {
                 "doctrine/instantiator": "^1.0.2",
                 "php": "^5.3|^7.0",
-                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
                 "sebastian/comparator": "^1.1|^2.0",
                 "sebastian/recursion-context": "^1.0|^2.0|^3.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.6.x-dev"
+                    "dev-master": "1.7.x-dev"
                 }
             },
             "autoload": {
                 "spy",
                 "stub"
             ],
-            "time": "2017-03-02 20:05:34"
+            "time": "2017-09-04T11:05:03+00:00"
         },
         {
             "name": "phpunit/dbunit",
                 "testing",
                 "xunit"
             ],
-            "time": "2015-08-07 04:57:38"
+            "time": "2015-08-07T04:57:38+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
                 "testing",
                 "xunit"
             ],
-            "time": "2017-04-02 07:44:40"
+            "time": "2017-04-02T07:44:40+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
                 "filesystem",
                 "iterator"
             ],
-            "time": "2016-10-03 07:40:28"
+            "time": "2016-10-03T07:40:28+00:00"
         },
         {
             "name": "phpunit/php-text-template",
             "keywords": [
                 "template"
             ],
-            "time": "2015-06-21 13:50:34"
+            "time": "2015-06-21T13:50:34+00:00"
         },
         {
             "name": "phpunit/php-timer",
             "keywords": [
                 "timer"
             ],
-            "time": "2017-02-26 11:10:40"
+            "time": "2017-02-26T11:10:40+00:00"
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "1.4.11",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
+                "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
-                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0",
+                "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0",
                 "shasum": ""
             },
             "require": {
                 "ext-tokenizer": "*",
-                "php": ">=5.3.3"
+                "php": "^7.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.2"
+                "phpunit/phpunit": "^6.2.4"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2017-02-27 10:12:30"
+            "time": "2017-08-20T05:47:52+00:00"
         },
         {
             "name": "phpunit/phpunit",
                 "testing",
                 "xunit"
             ],
-            "time": "2016-10-03 13:04:15"
+            "time": "2016-10-03T13:04:15+00:00"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
-            "version": "3.4.3",
+            "version": "3.4.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24"
+                "reference": "a23b761686d50a560cc56233b9ecf49597cc9118"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
-                "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118",
+                "reference": "a23b761686d50a560cc56233b9ecf49597cc9118",
                 "shasum": ""
             },
             "require": {
                 "mock",
                 "xunit"
             ],
-            "time": "2016-12-08 20:27:08"
+            "time": "2017-06-30T09:13:00+00:00"
         },
         {
             "name": "psr/container",
                 "container-interop",
                 "psr"
             ],
-            "time": "2017-02-14 16:28:37"
+            "time": "2017-02-14T16:28:37+00:00"
         },
         {
             "name": "psr/http-message",
                 "request",
                 "response"
             ],
-            "time": "2016-08-06 14:39:51"
+            "time": "2016-08-06T14:39:51+00:00"
         },
         {
             "name": "psr/log",
                 "psr",
                 "psr-3"
             ],
-            "time": "2016-10-10 12:19:37"
+            "time": "2016-10-10T12:19:37+00:00"
         },
         {
             "name": "sebastian/code-unit-reverse-lookup",
             ],
             "description": "Looks up which function or method a line of code belongs to",
             "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
-            "time": "2017-03-04 06:30:41"
+            "time": "2017-03-04T06:30:41+00:00"
         },
         {
             "name": "sebastian/comparator",
                 "compare",
                 "equality"
             ],
-            "time": "2017-01-29 09:50:25"
+            "time": "2017-01-29T09:50:25+00:00"
         },
         {
             "name": "sebastian/diff",
             "keywords": [
                 "diff"
             ],
-            "time": "2017-05-22 07:24:03"
+            "time": "2017-05-22T07:24:03+00:00"
         },
         {
             "name": "sebastian/environment",
                 "environment",
                 "hhvm"
             ],
-            "time": "2016-11-26 07:53:53"
+            "time": "2016-11-26T07:53:53+00:00"
         },
         {
             "name": "sebastian/exporter",
                 "export",
                 "exporter"
             ],
-            "time": "2016-06-17 09:04:28"
+            "time": "2016-06-17T09:04:28+00:00"
         },
         {
             "name": "sebastian/global-state",
             "keywords": [
                 "global state"
             ],
-            "time": "2015-10-12 03:26:01"
+            "time": "2015-10-12T03:26:01+00:00"
         },
         {
             "name": "sebastian/object-enumerator",
             ],
             "description": "Traverses array structures and object graphs to enumerate all referenced objects",
             "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
-            "time": "2016-01-28 13:25:10"
+            "time": "2016-01-28T13:25:10+00:00"
         },
         {
             "name": "sebastian/recursion-context",
             ],
             "description": "Provides functionality to recursively process PHP variables",
             "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
-            "time": "2016-10-03 07:41:43"
+            "time": "2016-10-03T07:41:43+00:00"
         },
         {
             "name": "sebastian/resource-operations",
             ],
             "description": "Provides a list of PHP built-in functions that operate on resources",
             "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
-            "time": "2015-07-28 20:34:47"
+            "time": "2015-07-28T20:34:47+00:00"
         },
         {
             "name": "sebastian/version",
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
             "homepage": "https://github.com/sebastianbergmann/version",
-            "time": "2016-10-03 07:35:21"
+            "time": "2016-10-03T07:35:21+00:00"
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1"
+                "reference": "aee7120b058c268363e606ff5fe8271da849a1b5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1",
-                "reference": "c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/aee7120b058c268363e606ff5fe8271da849a1b5",
+                "reference": "aee7120b058c268363e606ff5fe8271da849a1b5",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "symfony/dom-crawler": "~2.8|~3.0"
             },
             "require-dev": {
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2017-04-12 14:14:56"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "symfony/class-loader",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
-                "reference": "b0aff75bf18e4bbf37209235227e6e50a5aec8f5"
+                "reference": "9c69968ce57924e9e93550895cd2b0477edf0e19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/b0aff75bf18e4bbf37209235227e6e50a5aec8f5",
-                "reference": "b0aff75bf18e4bbf37209235227e6e50a5aec8f5",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/9c69968ce57924e9e93550895cd2b0477edf0e19",
+                "reference": "9c69968ce57924e9e93550895cd2b0477edf0e19",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "require-dev": {
                 "symfony/finder": "~2.8|~3.0",
             ],
             "description": "Symfony ClassLoader Component",
             "homepage": "https://symfony.com",
-            "time": "2017-04-12 14:14:56"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "symfony/config",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "79f86253ba482ca7f17718e886e6d164e5ba6d45"
+                "reference": "f9f19a39ee178f61bb2190f51ff7c517c2159315"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/79f86253ba482ca7f17718e886e6d164e5ba6d45",
-                "reference": "79f86253ba482ca7f17718e886e6d164e5ba6d45",
+                "url": "https://api.github.com/repos/symfony/config/zipball/f9f19a39ee178f61bb2190f51ff7c517c2159315",
+                "reference": "f9f19a39ee178f61bb2190f51ff7c517c2159315",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "symfony/filesystem": "~2.8|~3.0"
             },
             "conflict": {
-                "symfony/dependency-injection": "<3.3"
+                "symfony/dependency-injection": "<3.3",
+                "symfony/finder": "<3.3"
             },
             "require-dev": {
                 "symfony/dependency-injection": "~3.3",
+                "symfony/finder": "~3.3",
                 "symfony/yaml": "~3.0"
             },
             "suggest": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-29 18:41:32"
+            "time": "2017-09-04T16:28:07+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "c80e63f3f5e3a331bfc25e6e9332b10422eb9b05"
+                "reference": "a1e1b01293a090cb9ae2ddd221a3251a4a7e4abf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/c80e63f3f5e3a331bfc25e6e9332b10422eb9b05",
-                "reference": "c80e63f3f5e3a331bfc25e6e9332b10422eb9b05",
+                "url": "https://api.github.com/repos/symfony/console/zipball/a1e1b01293a090cb9ae2ddd221a3251a4a7e4abf",
+                "reference": "a1e1b01293a090cb9ae2ddd221a3251a4a7e4abf",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "symfony/debug": "~2.8|~3.0",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             },
             "require-dev": {
                 "psr/log": "~1.0",
+                "symfony/config": "~3.3",
                 "symfony/dependency-injection": "~3.3",
                 "symfony/event-dispatcher": "~2.8|~3.0",
                 "symfony/filesystem": "~2.8|~3.0",
-                "symfony/http-kernel": "~2.8|~3.0",
                 "symfony/process": "~2.8|~3.0"
             },
             "suggest": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-28 14:08:56"
+            "time": "2017-09-06T16:40:18+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "4d882dced7b995d5274293039370148e291808f2"
+                "reference": "c5f5263ed231f164c58368efbce959137c7d9488"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/4d882dced7b995d5274293039370148e291808f2",
-                "reference": "4d882dced7b995d5274293039370148e291808f2",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/c5f5263ed231f164c58368efbce959137c7d9488",
+                "reference": "c5f5263ed231f164c58368efbce959137c7d9488",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "type": "library",
             "extra": {
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-01 15:01:29"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "ef5f19a7a68075a0bd05969a329ead3b0776fb7a"
+                "reference": "8beb24eec70b345c313640962df933499373a944"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/ef5f19a7a68075a0bd05969a329ead3b0776fb7a",
-                "reference": "ef5f19a7a68075a0bd05969a329ead3b0776fb7a",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/8beb24eec70b345c313640962df933499373a944",
+                "reference": "8beb24eec70b345c313640962df933499373a944",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "psr/log": "~1.0"
             },
             "conflict": {
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-27 16:02:27"
+            "time": "2017-09-01T13:23:39+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "988c7bd6ec880690792ccf2a1e5ca05401c2a63d"
+                "reference": "e593f06dd90a81c7b70ac1c49862a061b0ec06d2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/988c7bd6ec880690792ccf2a1e5ca05401c2a63d",
-                "reference": "988c7bd6ec880690792ccf2a1e5ca05401c2a63d",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e593f06dd90a81c7b70ac1c49862a061b0ec06d2",
+                "reference": "e593f06dd90a81c7b70ac1c49862a061b0ec06d2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "psr/container": "^1.0"
             },
             "conflict": {
-                "symfony/config": "<=3.3-beta1",
+                "symfony/config": "<3.3.1",
                 "symfony/finder": "<3.3",
                 "symfony/yaml": "<3.3"
             },
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-25 23:10:31"
+            "time": "2017-09-05T20:39:38+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1"
+                "reference": "6b511d7329b203a620f09a2288818d27dcc915ae"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1",
-                "reference": "fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/6b511d7329b203a620f09a2288818d27dcc915ae",
+                "reference": "6b511d7329b203a620f09a2288818d27dcc915ae",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-25 23:10:31"
+            "time": "2017-09-11T15:55:22+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "a9f8b02b0ef07302eca92cd4bba73200b7980e9c"
+                "reference": "54ca9520a00386f83bca145819ad3b619aaa2485"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a9f8b02b0ef07302eca92cd4bba73200b7980e9c",
-                "reference": "a9f8b02b0ef07302eca92cd4bba73200b7980e9c",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54ca9520a00386f83bca145819ad3b619aaa2485",
+                "reference": "54ca9520a00386f83bca145819ad3b619aaa2485",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "conflict": {
                 "symfony/dependency-injection": "<3.3"
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-04 12:23:07"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "c709670bf64721202ddbe4162846f250735842c0"
+                "reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0",
-                "reference": "c709670bf64721202ddbe4162846f250735842c0",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/b32a0e5f928d0fa3d1dd03c78d020777e50c10cb",
+                "reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "type": "library",
             "extra": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-28 14:08:56"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.3.0",
+            "version": "v1.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
+                "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
-                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
+                "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.3-dev"
+                    "dev-master": "1.5-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2016-11-14 01:06:16"
+            "time": "2017-06-14T15:44:48+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.21",
+            "version": "v2.8.27",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "d54232f5682fda2f8bbebff7c81b864646867ab9"
+                "reference": "57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/d54232f5682fda2f8bbebff7c81b864646867ab9",
-                "reference": "d54232f5682fda2f8bbebff7c81b864646867ab9",
+                "url": "https://api.github.com/repos/symfony/process/zipball/57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8",
+                "reference": "57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-08 01:19:21"
+            "time": "2017-07-03T08:04:30+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543"
+                "reference": "add53753d978f635492dfe8cd6953f6a7361ef90"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543",
-                "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/add53753d978f635492dfe8cd6953f6a7361ef90",
+                "reference": "add53753d978f635492dfe8cd6953f6a7361ef90",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9",
+                "php": "^5.5.9|>=7.0.8",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "conflict": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-22 07:42:36"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.3.0",
+            "version": "v3.3.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "885db865f6b2b918404a1fae28f9ac640f71f994"
+                "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/885db865f6b2b918404a1fae28f9ac640f71f994",
-                "reference": "885db865f6b2b918404a1fae28f9ac640f71f994",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/1d8c2a99c80862bdc3af94c1781bf70f86bccac0",
+                "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.9"
+                "php": "^5.5.9|>=7.0.8"
             },
             "require-dev": {
                 "symfony/console": "~2.8|~3.0"
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2017-05-28 10:56:20"
+            "time": "2017-07-29T21:54:42+00:00"
         },
         {
             "name": "webmozart/assert",
                 "check",
                 "validate"
             ],
-            "time": "2016-11-23 20:04:58"
+            "time": "2016-11-23T20:04:58+00:00"
         }
     ],
     "aliases": [],
index 3919365..73e4b80 100644 (file)
@@ -3135,6 +3135,7 @@ class core_course_external extends external_api {
      */
     public static function check_updates($courseid, $tocheck, $filter = array()) {
         global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
 
         $params = self::validate_parameters(
             self::check_updates_parameters(),
index b38c641..c688791 100644 (file)
@@ -4266,3 +4266,32 @@ function course_require_view_participants($context) {
         throw new required_capability_exception($context, $viewparticipantscap, 'nopermissions', '');
     }
 }
+
+/**
+ * Return whether the user can download from the specified backup file area in the given context.
+ *
+ * @param string $filearea the backup file area. E.g. 'course', 'backup' or 'automated'.
+ * @param \context $context
+ * @param stdClass $user the user object. If not provided, the current user will be checked.
+ * @return bool true if the user is allowed to download in the context, false otherwise.
+ */
+function can_download_from_backup_filearea($filearea, \context $context, stdClass $user = null) {
+    $candownload = false;
+    switch ($filearea) {
+        case 'course':
+        case 'backup':
+            $candownload = has_capability('moodle/backup:downloadfile', $context, $user);
+            break;
+        case 'automated':
+            // Given the automated backups may contain userinfo, we restrict access such that only users who are able to
+            // restore with userinfo are able to download the file. Users can't create these backups, so checking 'backup:userinfo'
+            // doesn't make sense here.
+            $candownload = has_capability('moodle/backup:downloadfile', $context, $user) &&
+                           has_capability('moodle/restore:userinfo', $context, $user);
+            break;
+        default:
+            break;
+
+    }
+    return $candownload;
+}
index b1ef9e9..64b2575 100644 (file)
@@ -456,7 +456,7 @@ class core_course_renderer extends plugin_renderer_base {
      * @return string
      */
     public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) {
-        global $CFG;
+        global $CFG, $DB;
         $output = '';
         if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) {
             return $output;
@@ -485,16 +485,20 @@ class core_course_renderer extends plugin_renderer_base {
         } else if ($completion == COMPLETION_TRACKING_MANUAL) {
             switch($completiondata->completionstate) {
                 case COMPLETION_INCOMPLETE:
-                    $completionicon = 'manual-n'; break;
+                    $completionicon = 'manual-n' . ($completiondata->overrideby ? '-override' : '');
+                    break;
                 case COMPLETION_COMPLETE:
-                    $completionicon = 'manual-y'; break;
+                    $completionicon = 'manual-y' . ($completiondata->overrideby ? '-override' : '');
+                    break;
             }
         } else { // Automatic
             switch($completiondata->completionstate) {
                 case COMPLETION_INCOMPLETE:
-                    $completionicon = 'auto-n'; break;
+                    $completionicon = 'auto-n' . ($completiondata->overrideby ? '-override' : '');
+                    break;
                 case COMPLETION_COMPLETE:
-                    $completionicon = 'auto-y'; break;
+                    $completionicon = 'auto-y' . ($completiondata->overrideby ? '-override' : '');
+                    break;
                 case COMPLETION_COMPLETE_PASS:
                     $completionicon = 'auto-pass'; break;
                 case COMPLETION_COMPLETE_FAIL:
@@ -503,7 +507,15 @@ class core_course_renderer extends plugin_renderer_base {
         }
         if ($completionicon) {
             $formattedname = $mod->get_formatted_name();
-            $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
+            if ($completiondata->overrideby) {
+                $args = new stdClass();
+                $args->modname = $formattedname;
+                $overridebyuser = \core_user::get_user($completiondata->overrideby, '*', MUST_EXIST);
+                $args->overrideuser = fullname($overridebyuser);
+                $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $args);
+            } else {
+                $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
+            }
 
             if ($this->page->user_is_editing()) {
                 // When editing, the icon is just an image.
@@ -512,7 +524,6 @@ class core_course_renderer extends plugin_renderer_base {
                 $output .= html_writer::tag('span', $this->output->render($completionpixicon),
                         array('class' => 'autocompletion'));
             } else if ($completion == COMPLETION_TRACKING_MANUAL) {
-                $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname);
                 $newstate =
                     $completiondata->completionstate == COMPLETION_COMPLETE
                     ? COMPLETION_INCOMPLETE
index 6d53ba5..78f1934 100644 (file)
@@ -4074,4 +4074,54 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->expectException('required_capability_exception');
         course_require_view_participants(context_system::instance());
     }
+
+    /**
+     *  Testing the can_download_from_backup_filearea fn.
+     */
+    public function test_can_download_from_backup_filearea() {
+        global $DB;
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = $this->getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
+
+        // The 'automated' backup area. Downloading from this area requires two capabilities.
+        // If the user has only the 'backup:downloadfile' capability.
+        unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
+        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
+        $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
+
+        // If the user has only the 'restore:userinfo' capability.
+        unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
+        assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
+        $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
+
+        // If the user has both capabilities.
+        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
+        assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
+        $this->assertTrue(can_download_from_backup_filearea('automated', $context, $user));
+
+        // Is the user has neither of the capabilities.
+        unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
+        unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
+        $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
+
+        // The 'course ' and 'backup' backup file areas. These are governed by the same download capability.
+        // User has the capability.
+        unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
+        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
+        $this->assertTrue(can_download_from_backup_filearea('course', $context, $user));
+        $this->assertTrue(can_download_from_backup_filearea('backup', $context, $user));
+
+        // User doesn't have the capability.
+        unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
+        $this->assertFalse(can_download_from_backup_filearea('course', $context, $user));
+        $this->assertFalse(can_download_from_backup_filearea('backup', $context, $user));
+
+        // A file area that doesn't exist. No permissions, regardless of capabilities.
+        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
+        $this->assertFalse(can_download_from_backup_filearea('testing', $context, $user));
+    }
 }
index 9e16980..a44b813 100644 (file)
@@ -221,28 +221,6 @@ class enrol_cohort_plugin extends enrol_plugin {
         return false;
     }
 
-    /**
-     * Gets an array of the user enrolment actions.
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_unenrol_user($instance, $ue) && has_capability('enrol/cohort:unenrol', $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * Restore instance and map settings.
      *
index 3744c7c..080f0dd 100644 (file)
@@ -83,4 +83,56 @@ class enrol_cohort_lib_testcase extends advanced_testcase {
         $this->assertEquals($cohort->name . ' cohort (3)', $groupinfo->name);
 
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'cohort';
+
+        // Only enable the cohort enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        // Enable this enrol plugin for the course.
+        $plugin->add_instance($course);
+
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        // Teachers don't have enrol/cohort:unenrol capability by default. Login as admin for simplicity.
+        $this->setAdminUser();
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Cohort-sync has no enrol actions for active students.
+        $this->assertCount(0, $actions);
+
+        // Enrol actions for a suspended student.
+        // Suspend the student.
+        $ue->status = ENROL_USER_SUSPENDED;
+
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Cohort-sync has enrol actions for suspended students -- unenrol.
+        $this->assertCount(1, $actions);
+    }
 }
index c9b3686..1ecb250 100644 (file)
@@ -82,28 +82,6 @@ class enrol_database_plugin extends enrol_plugin {
         return false;
     }
 
-    /**
-     * Gets an array of the user enrolment actions.
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_unenrol_user($instance, $ue) && has_capability('enrol/database:unenrol', $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * Forces synchronisation of user enrolments with external database,
      * does not create new courses.
diff --git a/enrol/database/tests/lib_test.php b/enrol/database/tests/lib_test.php
new file mode 100644 (file)
index 0000000..ee80a91
--- /dev/null
@@ -0,0 +1,86 @@
+<?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/>.
+
+/**
+ * Database enrolment tests.
+ *
+ * @package    enrol_database
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Database enrolment tests.
+ *
+ * @package    enrol_database
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class enrol_database_lib_testcase extends advanced_testcase {
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'database';
+
+        // Only enable the database enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        // Enable this enrol plugin for the course.
+        $plugin->add_instance($course);
+
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        // Teachers don't have enrol/database:unenrol capability by default. Login as admin for simplicity.
+        $this->setAdminUser();
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Database enrol has 0 enrol actions for active users.
+        $this->assertCount(0, $actions);
+
+        // Enrol actions for a suspended student.
+        // Suspend the student.
+        $ue->status = ENROL_USER_SUSPENDED;
+
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Database enrol has enrol actions for suspended students -- unenrol.
+        $this->assertCount(1, $actions);
+    }
+}
index e7bcab9..2c2e6f7 100644 (file)
@@ -117,34 +117,6 @@ class enrol_flatfile_plugin extends enrol_plugin {
         return has_capability('enrol/flatfile:manage', $context);
     }
 
-    /**
-     * Gets an array of the user enrolment actions.
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_manage($instance) && has_capability("enrol/flatfile:manage", $context)) {
-            $url = new moodle_url('/enrol/editenrolment.php', $params);
-            $actionparams = array('class' => 'editenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_EDIT);
-            $actions[] = new user_enrolment_action(new pix_icon('t/edit', get_string('editenrolment', 'enrol')),
-                get_string('editenrolment', 'enrol'), $url, $actionparams);
-        }
-        if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/flatfile:unenrol", $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * Enrol user into course via enrol instance.
      *
index 0ab21c9..8f48e37 100644 (file)
@@ -498,4 +498,49 @@ class enrol_flatfile_testcase extends advanced_testcase {
 
         $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid' => $studentrole->id)));
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'flatfile';
+
+        // Only enable the flatfile enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        // Enable this enrol plugin for the course.
+        $plugin->add_instance($course);
+
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        // Teachers don't have enrol/flatfile:manage and enrol/flatfile:unenrol capabilities by default.
+        // Login as admin for simplicity.
+        $this->setAdminUser();
+
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Flatfile enrolment has 2 enrol actions for active users -- edit and unenrol.
+        $this->assertCount(2, $actions);
+    }
 }
index 64356fa..7556419 100644 (file)
@@ -38,7 +38,7 @@ class course_enrolment_manager {
 
     /**
      * The course context
-     * @var stdClass
+     * @var context
      */
     protected $context;
     /**
@@ -933,7 +933,7 @@ class course_enrolment_manager {
     /**
      * Returns the course context
      *
-     * @return stdClass
+     * @return context
      */
     public function get_context() {
         return $this->context;
index 6f5ac9e..9442c26 100644 (file)
@@ -377,34 +377,6 @@ class enrol_lti_plugin extends enrol_plugin {
         return $errors;
     }
 
-    /**
-     * Gets an array of the user enrolment actions.
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_manage($instance) && has_capability("enrol/lti:manage", $context)) {
-            $url = new moodle_url('/enrol/editenrolment.php', $params);
-            $actionparams = array('class' => 'editenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_EDIT);
-            $actions[] = new user_enrolment_action(new pix_icon('t/edit', get_string('editenrolment', 'enrol')),
-                get_string('editenrolment', 'enrol'), $url, $actionparams);
-        }
-        if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/lti:unenrol", $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * Restore instance and map settings.
      *
index 823be1a..3e47866 100644 (file)
@@ -117,4 +117,53 @@ class enrol_lti_testcase extends advanced_testcase {
         $this->assertFalse($DB->record_exists('enrol_lti_tools', [ 'id' => $tool->id ]));
         $this->assertFalse($DB->record_exists('enrol', [ 'id' => $instance->id ]));
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $DB, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'lti';
+
+        // Only enable the lti enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        $context = context_course::instance($course->id);
+        $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
+        $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
+
+        // Enable this enrol plugin for the course.
+        $fields = ['contextid' => $context->id, 'roleinstructor' => $teacherroleid, 'rolelearner' => $studentroleid];
+        $plugin->add_instance($course, $fields);
+
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        // Teachers don't have enrol/lti:unenrol capability by default. Login as admin for simplicity.
+        $this->setAdminUser();
+
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // LTI enrolment has 1 enrol actions for active users -- unenrol.
+        $this->assertCount(1, $actions);
+    }
 }
index 01de8e3..d0ff65a 100644 (file)
@@ -361,34 +361,6 @@ class enrol_manual_plugin extends enrol_plugin {
         return $this->lasternoller;
     }
 
-    /**
-     * Gets an array of the user enrolment actions.
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_manage($instance) && has_capability("enrol/manual:manage", $context)) {
-            $url = new moodle_url('/enrol/editenrolment.php', $params);
-            $actionparams = array('class' => 'editenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_EDIT);
-            $actions[] = new user_enrolment_action(new pix_icon('t/edit', get_string('editenrolment', 'enrol')),
-                get_string('editenrolment', 'enrol'), $url, $actionparams);
-        }
-        if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/manual:unenrol", $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * The manual plugin has several bulk operations that can be performed.
      * @param course_enrolment_manager $manager
index 4f4c6d2..9233c40 100644 (file)
@@ -493,4 +493,51 @@ class enrol_manual_lib_testcase extends advanced_testcase {
         $manualplugin->send_expiry_notifications($trace);
         $this->assertEquals(6, $sink->count());
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'manual';
+
+        // Only enable the manual enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        // Enable this enrol plugin for the course.
+        $plugin->add_instance($course);
+
+        // Create a teacher.
+        $teacher = $generator->create_user();
+        // Enrol the teacher to the course.
+        $generator->enrol_user($teacher->id, $course->id, 'editingteacher', $pluginname);
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        // Login as the teacher.
+        $this->setUser($teacher);
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Manual enrol has 2 enrol actions -- edit and unenrol.
+        $this->assertCount(2, $actions);
+    }
 }
index 3aafd8a..3d9e09e 100644 (file)
@@ -95,28 +95,6 @@ class enrol_meta_plugin extends enrol_plugin {
         return false;
     }
 
-    /**
-     * Gets an array of the user enrolment actions
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_unenrol_user($instance, $ue) && has_capability('enrol/meta:unenrol', $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * Called after updating/inserting course.
      *
index 5f292e5..c51ba22 100644 (file)
@@ -870,4 +870,56 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
         $this->assertEquals($expectedenrolments, $enrolments);
         $sink->close();
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'meta';
+
+        // Only enable the meta enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        // Enable this enrol plugin for the course.
+        $plugin->add_instance($course);
+
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        // Teachers don't have enrol/meta:unenrol capability by default. Login as admin for simplicity.
+        $this->setAdminUser();
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Meta-link enrolment has no enrol actions for active students.
+        $this->assertCount(0, $actions);
+
+        // Enrol actions for a suspended student.
+        // Suspend the student.
+        $ue->status = ENROL_USER_SUSPENDED;
+
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Meta-link enrolment has enrol actions for suspended students -- unenrol.
+        $this->assertCount(1, $actions);
+    }
 }
index 8041a7e..7c0fbf7 100644 (file)
@@ -276,34 +276,6 @@ class enrol_paypal_plugin extends enrol_plugin {
         $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
     }
 
-    /**
-     * Gets an array of the user enrolment actions
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_manage($instance) && has_capability("enrol/paypal:manage", $context)) {
-            $url = new moodle_url('/enrol/editenrolment.php', $params);
-            $actionparams = array('class' => 'editenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_EDIT);
-            $actions[] = new user_enrolment_action(new pix_icon('t/edit', get_string('editenrolment', 'enrol')),
-                get_string('editenrolment', 'enrol'), $url, $actionparams);
-        }
-        if ($this->allow_unenrol($instance) && has_capability("enrol/paypal:unenrol", $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     public function cron() {
         $trace = new text_progress_trace();
         $this->process_expirations($trace);
index d31b75b..0fcefe5 100644 (file)
@@ -168,4 +168,60 @@ class enrol_paypal_testcase extends advanced_testcase {
         $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
         $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'paypal';
+
+        // Only enable the paypal enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+        // Enable this enrol plugin for the course.
+        $plugin->add_instance($course);
+
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+
+        // Login as admin to see all enrol actions.
+        $this->setAdminUser();
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+
+        // Paypal enrolment has 2 enrol actions for active users when logged in as admin: edit and unenrol.
+        $this->assertCount(2, $actions);
+
+        // Enrol actions when viewing as a teacher.
+        // Create a teacher.
+        $teacher = $generator->create_user();
+        // Enrol the teacher to the course.
+        $generator->enrol_user($teacher->id, $course->id, 'editingteacher', $pluginname);
+        // Login as the teacher.
+        $this->setUser($teacher);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Teachers don't have the enrol/paypal:unenrol capability by default, but have enrol/paypal:manage.
+        $this->assertCount(1, $actions);
+    }
 }
index e612891..170da24 100644 (file)
@@ -521,34 +521,6 @@ class enrol_self_plugin extends enrol_plugin {
         return $this->lasternoller;
     }
 
-    /**
-     * Gets an array of the user enrolment actions.
-     *
-     * @param course_enrolment_manager $manager
-     * @param stdClass $ue A user enrolment object
-     * @return array An array of user_enrolment_actions
-     */
-    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
-        $actions = array();
-        $context = $manager->get_context();
-        $instance = $ue->enrolmentinstance;
-        $params = $manager->get_moodlepage()->url->params();
-        $params['ue'] = $ue->id;
-        if ($this->allow_manage($instance) && has_capability("enrol/self:manage", $context)) {
-            $url = new moodle_url('/enrol/editenrolment.php', $params);
-            $actionparams = array('class' => 'editenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_EDIT);
-            $actions[] = new user_enrolment_action(new pix_icon('t/edit', get_string('editenrolment', 'enrol')),
-                get_string('editenrolment', 'enrol'), $url, $actionparams);
-        }
-        if ($this->allow_unenrol($instance) && has_capability("enrol/self:unenrol", $context)) {
-            $url = new moodle_url('/enrol/unenroluser.php', $params);
-            $actionparams = array('class' => 'unenrollink', 'rel' => $ue->id, 'data-action' => ENROL_ACTION_UNENROL);
-            $actions[] = new user_enrolment_action(new pix_icon('t/delete', get_string('unenrol', 'enrol')),
-                get_string('unenrol', 'enrol'), $url, $actionparams);
-        }
-        return $actions;
-    }
-
     /**
      * Restore instance and map settings.
      *
index dd8c247..1cf5509 100644 (file)
@@ -726,4 +726,51 @@ class enrol_self_testcase extends advanced_testcase {
         $contact = $selfplugin->get_welcome_email_contact(ENROL_SEND_EMAIL_FROM_NOREPLY, $context);
         $this->assertEquals($noreplyuser, $contact);
     }
+
+    /**
+     * Test for getting user enrolment actions.
+     */
+    public function test_get_user_enrolment_actions() {
+        global $CFG, $DB, $PAGE;
+        $this->resetAfterTest();
+
+        // Set page URL to prevent debugging messages.
+        $PAGE->set_url('/enrol/editinstance.php');
+
+        $pluginname = 'self';
+
+        // Only enable the self enrol plugin.
+        $CFG->enrol_plugins_enabled = $pluginname;
+
+        $generator = $this->getDataGenerator();
+
+        // Get the enrol plugin.
+        $plugin = enrol_get_plugin($pluginname);
+
+        // Create a course.
+        $course = $generator->create_course();
+
+        // Create a teacher.
+        $teacher = $generator->create_user();
+        // Enrol the teacher to the course.
+        $enrolresult = $generator->enrol_user($teacher->id, $course->id, 'editingteacher', $pluginname);
+        $this->assertTrue($enrolresult);
+        // Create a student.
+        $student = $generator->create_user();
+        // Enrol the student to the course.
+        $enrolresult = $generator->enrol_user($student->id, $course->id, 'student', $pluginname);
+        $this->assertTrue($enrolresult);
+
+        // Login as the teacher.
+        $this->setUser($teacher);
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($PAGE, $course);
+        $userenrolments = $manager->get_user_enrolments($student->id);
+        $this->assertCount(1, $userenrolments);
+
+        $ue = reset($userenrolments);
+        $actions = $plugin->get_user_enrolment_actions($manager, $ue);
+        // Self enrol has 2 enrol actions -- edit and unenrol.
+        $this->assertCount(2, $actions);
+    }
 }
index 13e1118..40ebdf5 100644 (file)
@@ -64,6 +64,8 @@ $string['invalidanalysablefortimesplitting'] = 'It cannot be analysed using {$a}
 $string['nocourses'] = 'No courses to analyse';
 $string['modeloutputdir'] = 'Models output directory';
 $string['modeloutputdirinfo'] = 'Directory where prediction processors store all evaluation info. Useful for debugging and research.';
+$string['modeltimelimit'] = 'Analysis time limit per model';
+$string['modeltimelimitinfo'] = 'This setting limits the time each model spends analysing the site contents.';
 $string['noevaluationbasedassumptions'] = 'Models based on assumptions cannot be evaluated.';
 $string['nodata'] = 'No data to analyse';
 $string['noinsightsmodel'] = 'This model does not generate insights';
index 92cce03..01dcd4f 100644 (file)
@@ -154,7 +154,6 @@ $string['eventtypeuser'] = 'user';
 $string['hideeventtype'] = 'Hide {$a} events';
 $string['showeventtype'] = 'Show {$a} events';
 $string['hourly'] = 'Hourly';
-$string['ical'] = 'iCal';
 $string['importcalendar'] = 'Import calendar';
 $string['importcalendarheading'] = 'Import calendar...';
 $string['importcalendarfrom'] = 'Import from';
@@ -198,7 +197,6 @@ $string['pref_startwday'] = 'First day of week';
 $string['pref_startwday_help'] = 'Calendar weeks will be shown as starting on the day that you select here.';
 $string['pref_timeformat'] = 'Time display format';
 $string['pref_timeformat_help'] = 'You can choose to see times in either 12 or 24 hour format. If you choose "default", then the format will be automatically chosen according to the language you use in the site.';
-$string['quickdownloadcalendar'] = 'Quick download / subscribe to calendar';
 $string['recentupcoming'] = 'Recent and next 60 days';
 $string['repeatedevents'] = 'Repeated events';
 $string['repeateditall'] = 'Also apply changes to the other {$a} events in this repeat series';
@@ -259,6 +257,8 @@ $string['youcandeleteallrepeats'] = 'This event is part of a repeating event ser
 $string['for'] = 'for';
 
 // Deprecated since Moodle 3.4.
+$string['quickdownloadcalendar'] = 'Quick download / subscribe to calendar';
+$string['ical'] = 'iCal';
 $string['tt_hidecourse'] = 'Course events are shown (click to hide)';
 $string['tt_hideglobal'] = 'Global events are shown (click to hide)';
 $string['tt_hidegroups'] = 'Group events are shown (click to hide)';
index 4bd4742..5d303c6 100644 (file)
@@ -39,6 +39,7 @@ $string['aggregationmethod'] = 'Aggregation method';
 $string['all'] = 'All';
 $string['any'] = 'Any';
 $string['approval'] = 'Approval';
+$string['areyousureoverridecompletion'] = 'Are you sure you want to override the current completion state of this activity for this user and mark it "{$a}"?';
 $string['badautocompletion'] = 'When you select automatic completion, you must also enable at least one requirement (below).';
 $string['bulkactivitycompletion'] = 'Bulk edit activity completion';
 $string['bulkactivitydetail'] = 'Select the activities you wish to bulk edit.';
@@ -60,15 +61,21 @@ $string['completion'] = 'Completion tracking';
 $string['completion-alt-auto-enabled'] = 'The system marks this item complete according to conditions: {$a}';
 $string['completion-alt-auto-fail'] = 'Completed: {$a} (did not achieve pass grade)';
 $string['completion-alt-auto-n'] = 'Not completed: {$a}';
+$string['completion-alt-auto-n-override'] = 'Not completed: {$a->modname} (set by {$a->overrideuser})';
 $string['completion-alt-auto-pass'] = 'Completed: {$a} (achieved pass grade)';
 $string['completion-alt-auto-y'] = 'Completed: {$a}';
+$string['completion-alt-auto-y-override'] = 'Completed: {$a->modname} (set by {$a->overrideuser})';
 $string['completion-alt-manual-enabled'] = 'Students can manually mark this item complete: {$a}';
 $string['completion-alt-manual-n'] = 'Not completed: {$a}. Select to mark as complete.';
+$string['completion-alt-manual-n-override'] = 'Not completed: {$a->modname} (set by {$a->overrideuser}). Select to mark as complete.';
 $string['completion-alt-manual-y'] = 'Completed: {$a}. Select to mark as not complete.';
+$string['completion-alt-manual-y-override'] = 'Completed: {$a->modname} (set by {$a->overrideuser}). Select to mark as not complete.';
 $string['completion-fail'] = 'Completed (did not achieve pass grade)';
 $string['completion-n'] = 'Not completed';
+$string['completion-n-override'] = 'Not completed (set by {$a})';
 $string['completion-pass'] = 'Completed (achieved pass grade)';
 $string['completion-y'] = 'Completed';
+$string['completion-y-override'] = 'Completed (set by {$a})';
 $string['completion_automatic'] = 'Show activity as complete when conditions are met';
 $string['completion_help'] = 'If enabled, activity completion is tracked, either manually or automatically, based on certain conditions. Multiple conditions may be set if desired. If so, the activity will only be considered complete when ALL conditions are met.
 
index 173128f..6ab1d4a 100644 (file)
@@ -152,3 +152,5 @@ xmlrpcdisabledpublish,core_hub
 xmlrpcdisabledregistration,core_hub
 moodleorghubname,core_admin
 hubs,core_admin
+quickdownloadcalendar,core_calendar
+ical,core_calendar
index 7af4a20..fa5980b 100644 (file)
@@ -172,6 +172,7 @@ $string['course:managegroups'] = 'Manage groups';
 $string['course:managescales'] = 'Manage scales';
 $string['course:markcomplete'] = 'Mark users as complete in course completion';
 $string['course:movesections'] = 'Move sections';
+$string['course:overridecompletion'] = 'Override activity completion status';
 $string['course:publish'] = 'Publish a course';
 $string['course:renameroles'] = 'Rename roles';
 $string['course:request'] = 'Request new courses';
@@ -352,7 +353,7 @@ $string['restore:restoretargetimport'] = 'Restore from files targeted as import'
 $string['restore:rolldates'] = 'Allowed to roll activity configuration dates on restore';
 $string['restore:uploadfile'] = 'Upload files to backup areas';
 $string['restore:userinfo'] = 'Restore user data';
-$string['restore:viewautomatedfilearea'] = 'View automated backup area';
+$string['restore:viewautomatedfilearea'] = 'Restore courses from automated backups';
 $string['risks'] = 'Risks';
 $string['roleallowheader'] = 'Allow role:';
 $string['roleallowinfo'] = 'Select a role to be added to the list of allowed roles in context "{$a->context}", capability "{$a->cap}":';
index 587a61f..bd400e4 100644 (file)
@@ -3784,7 +3784,9 @@ function count_role_users($roleid, context $context, $parent = false) {
 
 /**
  * This function gets the list of courses that this user has a particular capability in.
- * It is still not very efficient.
+ *
+ * It is now reasonably efficient, but bear in mind that if there are users who have the capability
+ * everywhere, it may return an array of all courses.
  *
  * @param string $capability Capability in question
  * @param int $userid User ID or null for current user
@@ -3799,15 +3801,50 @@ function count_role_users($roleid, context $context, $parent = false) {
  */
 function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '',
         $limit = 0) {
-    global $DB;
+    global $DB, $USER;
+
+    // Default to current user.
+    if (!$userid) {
+        $userid = $USER->id;
+    }
+
+    if ($doanything && is_siteadmin($userid)) {
+        // If the user is a site admin and $doanything is enabled then there is no need to restrict
+        // the list of courses.
+        $contextlimitsql = '';
+        $contextlimitparams = [];
+    } else {
+        // Gets SQL to limit contexts ('x' table) to those where the user has this capability.
+        list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
+                $userid, $capability);
+        if (!$contextlimitsql) {
+            // If the does not have this capability in any context, return false without querying.
+            return false;
+        }
+
+        $contextlimitsql = 'WHERE' . $contextlimitsql;
+    }
 
     // Convert fields list and ordering
     $fieldlist = '';
     if ($fieldsexceptid) {
         $fields = array_map('trim', explode(',', $fieldsexceptid));
         foreach($fields as $field) {
-            // Context fields have a different alias and are added further down.
-            if (strpos($field, 'ctx') !== 0) {
+            // Context fields have a different alias.
+            if (strpos($field, 'ctx') === 0) {
+                switch($field) {
+                    case 'ctxlevel' :
+                        $realfield = 'contextlevel';
+                        break;
+                    case 'ctxinstance' :
+                        $realfield = 'instanceid';
+                        break;
+                    default:
+                        $realfield = substr($field, 3);
+                        break;
+                }
+                $fieldlist .= ',x.' . $realfield . ' AS ' . $field;
+            } else {
                 $fieldlist .= ',c.'.$field;
             }
         }
@@ -3824,43 +3861,18 @@ function get_user_capability_course($capability, $userid = null, $doanything = t
         $orderby = 'ORDER BY '.$orderby;
     }
 
-    // Obtain a list of everything relevant about all courses including context.
-    // Note the result can be used directly as a context (we are going to), the course
-    // fields are just appended.
-
-    $contextpreload = context_helper::get_preload_record_columns_sql('x');
-
-    $contextlist = ['ctxid', 'ctxpath', 'ctxdepth', 'ctxlevel', 'ctxinstance'];
-    $unsetitems = $contextlist;
-    if ($fieldsexceptid) {
-        $coursefields = array_map('trim', explode(',', $fieldsexceptid));
-        $unsetitems = array_diff($contextlist, $coursefields);
-    }
-
     $courses = array();
-    $rs = $DB->get_recordset_sql("SELECT c.id $fieldlist, $contextpreload
-                                    FROM {course} c
-                                    JOIN {context} x ON (c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE.")
-                                $orderby");
-    // Check capability for each course in turn
+    $rs = $DB->get_recordset_sql("
+            SELECT c.id $fieldlist
+              FROM {course} c
+              JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
+            $contextlimitsql
+            $orderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
     foreach ($rs as $course) {
-        // The preload_from_record() unsets the context related fields, but it is possible that our caller may
-        // want them returned too (so they can use them for their own context preloading). For that reason we
-        // pass a clone.
-        context_helper::preload_from_record(clone($course));
-        $context = context_course::instance($course->id);
-        if (has_capability($capability, $context, $userid, $doanything)) {
-            // Unset context fields if they were not asked for.
-            foreach ($unsetitems as $item) {
-                unset($course->$item);
-            }
-            // We've got the capability. Make the record look like a course record
-            // and store it
-            $courses[] = $course;
-            $limit--;
-            if ($limit == 0) {
-                break;
-            }
+        $courses[] = $course;
+        $limit--;
+        if ($limit == 0) {
+            break;
         }
     }
     $rs->close();
index ae58564..a7f0adf 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
 
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Latest version is available at http://adodb.sourceforge.net
index f7b5179..7f553f1 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
 
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Latest version is available at http://adodb.sourceforge.net
index bded3ac..bfd8d9b 100644 (file)
@@ -8,7 +8,7 @@ $ADODB_INCLUDED_CSV = 1;
 
 /*
 
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index f2b29a6..b2a0837 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -520,7 +520,7 @@ class ADODB_DataDict {
                        list($lines,$pkey,$idxs) = $this->_GenFields($flds);
                        // genfields can return FALSE at times
                        if ($lines == null) $lines = array();
-                       list(,$first) = each($lines);
+                       $first  = current($lines);
                        list(,$column_def) = preg_split("/[\t ]+/",$first,2);
                }
                return array(sprintf($this->renameColumn,$tabname,$this->NameQuote($oldcolumn),$this->NameQuote($newcolumn),$column_def));
index 3f2ab90..18f944b 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.7  20-Sep-2016
+ * @version   v5.20.9  21-Dec-2016
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
@@ -111,7 +111,7 @@ function adodb_error_pg($errormsg)
                        'could not serialize access due to'   => DB_ERROR_SERIALIZATION_FAILURE
                );
        reset($error_regexps);
-       while (list($regexp,$code) = each($error_regexps)) {
+       foreach ($error_regexps as $regexp => $code) {
                if (preg_match("/$regexp/mi", $errormsg)) {
                        return $code;
                }
index e8dcaab..7f36ba1 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.7  20-Sep-2016
+ * @version   v5.20.9  21-Dec-2016
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index db3e2a2..474d6d5 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.7  20-Sep-2016
+ * @version   v5.20.9  21-Dec-2016
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index b3ffc38..9c66ac3 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * @version   v5.20.7  20-Sep-2016
+ * @version   v5.20.9  21-Dec-2016
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index 4c8dffc..cfc067b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 503bb15..6028530 100644 (file)
@@ -6,7 +6,7 @@ global $ADODB_INCLUDED_LIB;
 $ADODB_INCLUDED_LIB = 1;
 
 /*
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 4dde732..42d2be6 100644 (file)
@@ -11,7 +11,7 @@ if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
 
 /*
 
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 0221975..fa77d55 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-       @version   v5.20.7  20-Sep-2016
+       @version   v5.20.9  21-Dec-2016
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
          Released under both BSD license and Lesser GPL library license.
index 237dcfe..c8f0933 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.7  20-Sep-2016
+ * @version   v5.20.9  21-Dec-2016
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index 3dfaab4..69218c6 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 50963f0..132f25d 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index df718cf..4d2016c 100644 (file)
@@ -3,7 +3,7 @@
 ADOdb Date Library, part of the ADOdb abstraction library
 Download: http://adodb.sourceforge.net/#download
 
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index 4967f6a..b01b0af 100644 (file)
@@ -14,7 +14,7 @@
 /**
        \mainpage
 
-       @version   v5.20.7  20-Sep-2016
+       @version   v5.20.9  21-Dec-2016
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
@@ -233,7 +233,7 @@ if (!defined('_ADODB_LAYER')) {
                /**
                 * ADODB version as a string.
                 */
-               $ADODB_vers = 'v5.20.7  20-Sep-2016';
+               $ADODB_vers = 'v5.20.9  21-Dec-2016';
 
                /**
                 * Determines whether recordset->RecordCount() is used.
@@ -1164,8 +1164,7 @@ if (!defined('_ADODB_LAYER')) {
 
                                foreach($inputarr as $arr) {
                                        $sql = ''; $i = 0;
-                                       //Use each() instead of foreach to reduce memory usage -mikefedyk
-                                       while(list(, $v) = each($arr)) {
+                                       foreach ($arr as $v) {
                                                $sql .= $sqlarr[$i];
                                                // from Ron Baldwin <ron.baldwin#sourceprose.com>
                                                // Only quote string types
index 25a72a8..c145915 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index a3fa783..7d201ff 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 8f85f89..f247c8d 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 0521cc6..e2e6909 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index c6629d1..495a722 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 8ab2b6e..25726f4 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 1eae3af..2c496de 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 8c88404..36d5fca 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -146,19 +146,69 @@ class ADODB2_mssqlnative extends ADODB_DataDict {
                return $sql;
        }
 
-       /*
-       function AlterColumnSQL($tabname, $flds, $tableflds='', $tableoptions='')
+       function DefaultConstraintname($tabname, $colname)
+       {
+               $constraintname = false;
+               $rs = $this->connection->Execute(
+                       "SELECT name FROM sys.default_constraints
+                       WHERE object_name(parent_object_id) = '$tabname'
+                       AND col_name(parent_object_id, parent_column_id) = '$colname'"
+               );
+               if ( is_object($rs) ) {
+                       $row = $rs->FetchRow();
+                       $constraintname = $row['name'];
+               }
+               return $constraintname;
+       }
+  
+       function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
        {
                $tabname = $this->TableName ($tabname);
                $sql = array();
-               list($lines,$pkey) = $this->_GenFields($flds);
+
+               list($lines,$pkey,$idxs) = $this->_GenFields($flds);
+               $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' ';
                foreach($lines as $v) {
-                       $sql[] = "ALTER TABLE $tabname $this->alterCol $v";
+                       $not_null = false;
+                       if ($not_null = preg_match('/NOT NULL/i',$v)) {
+                               $v = preg_replace('/NOT NULL/i','',$v);
+                       }
+                       if (preg_match('/^([^ ]+) .*DEFAULT (\'[^\']+\'|\"[^\"]+\"|[^ ]+)/',$v,$matches)) {
+                               list(,$colname,$default) = $matches;
+                               $v = preg_replace('/^' . preg_quote($colname) . '\s/', '', $v);
+                               $t = trim(str_replace('DEFAULT '.$default,'',$v));
+                               if ( $constraintname = $this->DefaultConstraintname($tabname,$colname) ) {
+                                       $sql[] = 'ALTER TABLE '.$tabname.' DROP CONSTRAINT '. $constraintname;
+                               }
+                               if ($not_null) {
+                                       $sql[] = $alter . $colname . ' ' . $t  . ' NOT NULL';
+                               } else {
+                                       $sql[] = $alter . $colname . ' ' . $t ;
+                               }
+                               $sql[] = 'ALTER TABLE ' . $tabname
+                                       . ' ADD CONSTRAINT DF__' . $tabname . '__' .  $colname .  '__' . dechex(rand())
+                                       . ' DEFAULT ' . $default . ' FOR ' . $colname;
+                       } else {
+                               $colname = strtok($v," ");
+                               if ( $constraintname = $this->DefaultConstraintname($tabname,$colname) ) {
+                                       $sql[] = 'ALTER TABLE '.$tabname.' DROP CONSTRAINT '. $constraintname;
+                               }
+                               if ($not_null) {
+                                       $sql[] = $alter . $v  . ' NOT NULL';
+                               } else {
+                                       $sql[] = $alter . $v;
+                               }
+                       }
+               }
+               if (is_array($idxs)) {
+                       foreach($idxs as $idx => $idxdef) {
+                               $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
+                               $sql = array_merge($sql, $sql_idxs);
+                       }
                }
-
                return $sql;
        }
-       */
+
 
        /**
         * Drop a column, syntax is ALTER TABLE table DROP COLUMN column,column
@@ -176,10 +226,12 @@ class ADODB2_mssqlnative extends ADODB_DataDict {
                if (!is_array($flds))
                        $flds = explode(',',$flds);
                $f = array();
-               $s = 'ALTER TABLE ' . $tabname . ' DROP COLUMN ';
+               $s = 'ALTER TABLE ' . $tabname;
                foreach($flds as $v) {
-                       //$f[] = "\n$this->dropCol ".$this->NameQuote($v);
-                       $f[] = $this->NameQuote($v);
+                       if ( $constraintname = $this->DefaultConstraintname($tabname,$v) ) {
+                               $sql[] = 'ALTER TABLE ' . $tabname . ' DROP CONSTRAINT ' . $constraintname;
+                       }
+                       $f[] = ' DROP COLUMN ' . $this->NameQuote($v);
                }
                $s .= implode(', ',$f);
                $sql[] = $s;
index 0148915..00a43a2 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b758ede..57cf0af 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index be2700e..99a5641 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 9a7671b..fbf931c 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 8ec2927..86b1b04 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index d0e84f6..d4e5f05 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b020d36..3a5a8ed 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index bcfc899..04b7dc8 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -225,7 +225,7 @@ class ADODB_ado extends ADOConnection {
 
       // Map by http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ado270/htm/mdmthcreateparam.asp
       // Check issue http://bugs.php.net/bug.php?id=40664 !!!
-                       while(list(, $val) = each($inputarr)) {
+                       foreach ($inputarr as $val) {
                                $type = gettype($val);
                                $len=strlen($val);
                                if ($type == 'boolean')
index 0205c93..73c4b50 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -248,7 +248,7 @@ class ADODB_ado extends ADOConnection {
                        $oCmd->CommandText = $sql;
                        $oCmd->CommandType = 1;
 
-                       while(list(, $val) = each($inputarr)) {
+                       foreach ($inputarr as $val) {
                                $type = gettype($val);
                                $len=strlen($val);
                                if ($type == 'boolean')
index fa1629b..c167ce6 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 Released under both BSD license and Lesser GPL library license.
index d6993ed..57eacc9 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index ad5f61b..d3de2ca 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index be0f1bd..fd47784 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 67c19ee..e7b9dbd 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index 0d1d226..91d61af 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index ee5b960..1261689 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 7ac6dd1..9a2440c 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.7  20-Sep-2016
+ @version   v5.20.9  21-Dec-2016
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  Released under both BSD license and Lesser GPL library license.
index 84f1af5..415f66f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index a095e58..c4f0cbd 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 951f4ef..801451e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-* @version   v5.20.7  20-Sep-2016
+* @version   v5.20.9  21-Dec-2016
 * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 * Released under both BSD license and Lesser GPL library license.
index e1cf814..58f233a 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index bfebe48..633705e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
    Released under both BSD license and Lesser GPL library license.
index d710d4a..068dffc 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 64726b0..23eb2c0 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -125,6 +125,7 @@ class ADODB_mssqlnative extends ADOConnection {
        var $_bindInputArray = true;
        var $_dropSeqSQL = "drop table %s";
        var $connectionInfo = array();
+       var $cachedSchemaFlush = false;
        var $sequences = false;
        var $mssql_version = '';
 
@@ -143,25 +144,22 @@ class ADODB_mssqlnative extends ADOConnection {
                        sqlsrv_configure('WarningsReturnAsErrors', 0);
                }
        }
+
+       /**
+        * Initializes the SQL Server version.
+        * Dies if connected to a non-supported version (2000 and older)
+        */
        function ServerVersion() {
                $data = $this->ServerInfo();
-               if (preg_match('/^09/',$data['version'])){
-                       /*
-                        * SQL Server 2005
-                        */
-                       $this->mssql_version = 9;
-               } elseif (preg_match('/^10/',$data['version'])){
-                       /*
-                        * SQL Server 2008
-                        */
-                       $this->mssql_version = 10;
-               } elseif (preg_match('/^11/',$data['version'])){
-                       /*
-                        * SQL Server 2012
-                        */
-                       $this->mssql_version = 11;
-               } else
+               preg_match('/^\d{2}/', $data['version'], $matches);
+               $version = (int)reset($matches);
+
+               // We only support SQL Server 2005 and up
+               if($version < 9) {
                        die("SQL SERVER VERSION {$data['version']} NOT SUPPORTED IN mssqlnative DRIVER");
+               }
+
+               $this->mssql_version = $version;
        }
 
        function ServerInfo() {
@@ -211,26 +209,26 @@ class ADODB_mssqlnative extends ADOConnection {
                switch($this->mssql_version){
                case 9:
                case 10:
-                       return $this->GenID2008();
+                       return $this->GenID2008($seq, $start);
                        break;
-               case 11:
-                       return $this->GenID2012();
+               default:
+                       return $this->GenID2012($seq, $start);
                        break;
                }
        }
 
        function CreateSequence($seq='adodbseq',$start=1)
        {
-               if (!$this->mssql_vesion)
+               if (!$this->mssql_version)
                        $this->ServerVersion();
 
                switch($this->mssql_version){
                case 9:
                case 10:
-                       return $this->CreateSequence2008();
+                       return $this->CreateSequence2008($seq, $start);
                        break;
-               case 11:
-                       return $this->CreateSequence2012();
+               default:
+                       return $this->CreateSequence2012($seq, $start);
                        break;
                }
 
@@ -258,7 +256,7 @@ class ADODB_mssqlnative extends ADOConnection {
        /**
         * Proper Sequences Only available to Server 2012 and up
         */
-       function CreateSequence2012($seq='adodb',$start=1){
+       function CreateSequence2012($seq='adodbseq',$start=1){
                if (!$this->sequences){
                        $sql = "SELECT name FROM sys.sequences";
                        $this->sequences = $this->GetCol($sql);
@@ -289,7 +287,7 @@ class ADODB_mssqlnative extends ADOConnection {
                }
                $num = $this->GetOne("select id from $seq");
                sqlsrv_commit($this->_connectionID);
-               return true;
+               return $num;
        }
        /**
         * Only available to Server 2012 and up
@@ -313,7 +311,7 @@ class ADODB_mssqlnative extends ADOConnection {
                }
                if (!is_array($this->sequences)
                || is_array($this->sequences) && !in_array($seq,$this->sequences)){
-                       $this->CreateSequence2012($seq='adodbseq',$start=1);
+                       $this->CreateSequence2012($seq, $start);
 
                }
                $num = $this->GetOne("SELECT NEXT VALUE FOR $seq");
@@ -468,10 +466,9 @@ class ADODB_mssqlnative extends ADOConnection {
 
        function ErrorNo()
        {
-               if ($this->_logsql && $this->_errorCode !== false) return $this->_errorCode;
                $err = sqlsrv_errors(SQLSRV_ERR_ALL);
                if($err[0]) return $err[0]['code'];
-               else return -1;
+               else return 0;
        }
 
        // returns true or false
@@ -569,7 +566,7 @@ class ADODB_mssqlnative extends ADOConnection {
 
                $insert = false;
                // handle native driver flaw for retrieving the last insert ID
-               if(preg_match('/^\W*insert\s(?:(?:(?:\'\')*\'[^\']+\'(?:\'\')*)|[^;\'])*;?$/i', $sql)) {
+               if(preg_match('/^\W*insert[\s\w()",.]+values\s*\((?:[^;\']|\'\'|(?:(?:\'\')*\'[^\']+\'(?:\'\')*))*;?$/i', $sql)) {
                        $insert = true;
                        $sql .= '; '.$this->identitySQL; // select scope_identity()
                }
index e99f31e..cd6a285 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-* @version   v5.20.7  20-Sep-2016
+* @version   v5.20.9  21-Dec-2016
 * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 * Released under both BSD license and Lesser GPL library license.
index 1dc0dcb..2d999c6 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 3df1b10..188efc9 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index fe0213b..26b354a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index efc9333..79c9376 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index c0f476a..af3a1a1 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index 33d19a8..928d1b8 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
 
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim. All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
@@ -703,9 +703,19 @@ END;
         * This implementation does not appear to work with oracle 8.0.5 or earlier.
         * Comment out this function then, and the slower SelectLimit() in the base
         * class will be used.
+        *
+        * Note: FIRST_ROWS hinting is only used if $sql is a string; when
+        * processing a prepared statement's handle, no hinting is performed.
         */
        function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
        {
+               // Since the methods used to limit the number of returned rows rely
+               // on modifying the provided SQL query, we can't work with prepared
+               // statements so we just extract the SQL string.
+               if(is_array($sql)) {
+                       $sql = $sql[0];
+               }
+
                // seems that oracle only supports 1 hint comment in 8i
                if ($this->firstrows) {
                        if ($nrows > 500 && $nrows < 1000) {
@@ -731,20 +741,13 @@ END;
                                if ($offset > 0) {
                                        $nrows += $offset;
                                }
-                               //$inputarr['adodb_rownum'] = $nrows;
-                               if ($this->databaseType == 'oci8po') {
-                                       $sql = "select * from (".$sql.") where rownum <= ?";
-                               } else {
-                                       $sql = "select * from (".$sql.") where rownum <= :adodb_offset";
-                               }
+                               $sql = "select * from (".$sql.") where rownum <= :adodb_offset";
                                $inputarr['adodb_offset'] = $nrows;
                                $nrows = -1;
                        }
                        // note that $nrows = 0 still has to work ==> no rows returned
 
-                       $rs = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
-                       return $rs;
-
+                       return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
                } else {
                        // Algorithm by Tomas V V Cox, from PEAR DB oci8.php
 
@@ -758,13 +761,19 @@ END;
 
                        if (is_array($inputarr)) {
                                foreach($inputarr as $k => $v) {
+                                       $i=0;
+                                       if ($this->databaseType == 'oci8po') {
+                                               $bv_name = ":".$i++;
+                                       } else {
+                                               $bv_name = ":".$k;
+                                       }
                                        if (is_array($v)) {
                                                // suggested by g.giunta@libero.
                                                if (sizeof($v) == 2) {
-                                                       oci_bind_by_name($stmt,":$k",$inputarr[$k][0],$v[1]);
+                                                       oci_bind_by_name($stmt,$bv_name,$inputarr[$k][0],$v[1]);
                                                }
                                                else {
-                                                       oci_bind_by_name($stmt,":$k",$inputarr[$k][0],$v[1],$v[2]);
+                                                       oci_bind_by_name($stmt,$bv_name,$inputarr[$k][0],$v[1],$v[2]);
                                                }
                                        } else {
                                                $len = -1;
@@ -774,7 +783,7 @@ END;
                                                if (isset($bindarr)) {  // is prepared sql, so no need to oci_bind_by_name again
                                                        $bindarr[$k] = $v;
                                                } else {                                // dynamic sql, so rebind every time
-                                                       oci_bind_by_name($stmt,":$k",$inputarr[$k],$len);
+                                                       oci_bind_by_name($stmt,$bv_name,$inputarr[$k],$len);
                                                }
                                        }
                                }
@@ -801,24 +810,19 @@ END;
                        }
                        $offset += 1; // in Oracle rownum starts at 1
 
-                       if ($this->databaseType == 'oci8po') {
-                                       $sql = "SELECT $hint $fields FROM".
-                                               "(SELECT rownum as adodb_rownum, $fields FROM".
-                                               " ($sql) WHERE rownum <= ?".
-                                               ") WHERE adodb_rownum >= ?";
-                               } else {
-                                       $sql = "SELECT $hint $fields FROM".
-                                               "(SELECT rownum as adodb_rownum, $fields FROM".
-                                               " ($sql) WHERE rownum <= :adodb_nrows".
-                                               ") WHERE adodb_rownum >= :adodb_offset";
-                               }
-                               $inputarr['adodb_nrows'] = $nrows;
-                               $inputarr['adodb_offset'] = $offset;
+                       $sql = "SELECT $hint $fields FROM".
+                               "(SELECT rownum as adodb_rownum, $fields FROM".
+                               " ($sql) WHERE rownum <= :adodb_nrows".
+                               ") WHERE adodb_rownum >= :adodb_offset";
+                       $inputarr['adodb_nrows'] = $nrows;
+                       $inputarr['adodb_offset'] = $offset;
 
                        if ($secs2cache > 0) {
                                $rs = $this->CacheExecute($secs2cache, $sql,$inputarr);
                        }
-                       else $rs = $this->Execute($sql,$inputarr);
+                       else {
+                               $rs = $this->Execute($sql, $inputarr);
+                       }
                        return $rs;
                }
        }
index e2979f5..112e9ec 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.7  20-Sep-2016
+ * @version   v5.20.9  21-Dec-2016
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index 0013467..6b939b0 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -56,6 +56,21 @@ class ADODB_oci8po extends ADODB_oci8 {
                return ADOConnection::Execute($sql,$inputarr);
        }
 
+       /**
+        * The optimizations performed by ADODB_oci8::SelectLimit() are not
+        * compatible with the oci8po driver, so we rely on the slower method
+        * from the base class.
+        * We can't properly handle prepared statements either due to preprocessing
+        * of query parameters, so we treat them as regular SQL statements.
+        */
+       function SelectLimit($sql, $nrows=-1, $offset=-1, $inputarr=false, $secs2cache=0)
+       {
+               if(is_array($sql)) {
+//                     $sql = $sql[0];
+               }
+               return ADOConnection::SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
+       }
+
        // emulate handling of parameters ? ?, replacing with :bind0 :bind1
        function _query($sql,$inputarr=false)
        {
@@ -74,11 +89,14 @@ class ADODB_oci8po extends ADODB_oci8 {
                                        $sql = str_replace($qmMatch, $qmReplace, $sql);
                                }
 
+                               // Replace parameters if any were found
                                $sqlarr = explode('?',$sql);
-                               $sql = $sqlarr[0];
+                               if(count($sqlarr) > 1) {
+                                       $sql = $sqlarr[0];
 
-                               foreach($inputarr as $k => $v) {
-                                       $sql .=  ":$k" . $sqlarr[++$i];
+                                       foreach ($inputarr as $k => $v) {
+                                               $sql .= ":$k" . $sqlarr[++$i];
+                                       }
                                }
 
                                $sql = str_replace('-QUESTIONMARK-', '?', $sql);
index 341e2fc..1940e80 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b6b0556..efaa5bb 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index ca2b63c..fa6d8b8 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b14618c..aa64316 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
diff --git a/lib/adodb/drivers/adodb-odbc_mssql2012.inc.php b/lib/adodb/drivers/adodb-odbc_mssql2012.inc.php
deleted file mode 100644 (file)
index a3b7f69..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-/*
- @version   v5.21.0-dev  ??-???-2016
- @copyright (c) 2015      Damien Regad, Mark Newnham and the ADOdb community
-  Released under both BSD license and Lesser GPL library license.
-  Whenever there is any discrepancy between the two licenses,
-  the BSD license will take precedence.
-  Set tabs to 4.
-
-  Microsoft SQL Server 2012 via ODBC
-*/
-
-if (!defined('ADODB_DIR')) 
-       die();
-
-include_once(ADODB_DIR."/drivers/adodb-odbc_mssql.inc.php");
-
-class  ADODB_odbc_mssql2012 extends ADODB_odbc_mssql
-{
-       /*
-       * Makes behavior similar to prior versions of SQL Server
-       */
-       var $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL ON';
-}
-
-class  ADORecordSet_odbc_mssql2012 extends ADORecordSet_odbc_mssql
-{
-}
\ No newline at end of file
index 42dc1b2..dfa851c 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 740e9bf..ec374a5 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.7  20-Sep-2016
+  @version   v5.20.9  21-Dec-2016
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 9ddd408..5ca03e7 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-       @version   v5.20.7  20-Sep-2016
+       @version   v5.20.9  21-Dec-2016
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index f5dae53..ca737d6 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.7  20-Sep-2016
+@version   v5.20.9  21-Dec-2016
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 179268b..b7b5577 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-       @version   v5.20.7  20-Sep-2016
+       @version   v5.20.9  21-Dec-2016
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community