Merge branch 'MDL-59811-master' of git://github.com/junpataleta/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 9 Oct 2017 17:52:56 +0000 (19:52 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 9 Oct 2017 17:52:56 +0000 (19:52 +0200)
13 files changed:
composer.json
composer.lock
lib/accesslib.php
lib/classes/access/get_user_capability_course_helper.php [new file with mode: 0644]
lib/db/install.xml
lib/db/upgrade.php
lib/form/form.js
lib/tests/accesslib_test.php
lib/webdavlib.php
mod/quiz/report/overview/report.php
mod/quiz/report/overview/tests/report_test.php
mod/quiz/report/responses/report.php
version.php

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 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();
diff --git a/lib/classes/access/get_user_capability_course_helper.php b/lib/classes/access/get_user_capability_course_helper.php
new file mode 100644 (file)
index 0000000..80b5b45
--- /dev/null
@@ -0,0 +1,370 @@
+<?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/>.
+
+/**
+ * Helper functions to implement the complex get_user_capability_course function.
+ *
+ * @package core
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\access;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Helper functions to implement the complex get_user_capability_course function.
+ *
+ * @package core
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class get_user_capability_course_helper {
+    /**
+     * Based on the given user's access data (roles) and system role definitions, works out
+     * an array of capability values at each relevant context for the given user and capability.
+     *
+     * This is organised by the effective context path (the one at which the capability takes
+     * effect) and then by role id.
+     *
+     * @param int $userid User id
+     * @param string $capability Capability e.g. 'moodle/course:view'
+     * @return array Array of capability constants, indexed by context path and role id
+     */
+    protected static function get_capability_info_at_each_context($userid, $capability) {
+        // Get access data for user.
+        $accessdata = get_user_accessdata($userid);
+
+        // Get list of roles for user (any location) and information about these roles.
+        $roleids = [];
+        foreach ($accessdata['ra'] as $path => $roles) {
+            foreach ($roles as $roleid) {
+                $roleids[$roleid] = true;
+            }
+        }
+        $rdefs = get_role_definitions(array_keys($roleids));
+
+        // Get data for required capability at each context path where the user has a role that can
+        // affect it.
+        $systemcontext = \context_system::instance();
+        $pathroleperms = [];
+        foreach ($accessdata['ra'] as $userpath => $roles) {
+            foreach ($roles as $roleid) {
+                // Get role definition for that role.
+                foreach ($rdefs[$roleid] as $rolepath => $caps) {
+                    // Ignore if this override/definition doesn't refer to the relevant cap.
+                    if (!array_key_exists($capability, $caps)) {
+                        continue;
+                    }
+
+                    // Check path is /1 or matches a path the user has.
+                    if ($rolepath === '/' . $systemcontext->id) {
+                        // Note /1 is listed first in the array so this entry will be overridden
+                        // if there is an override for the role on this actual level.
+                        $effectivepath = $userpath;
+                    } else if (preg_match('~^' . $userpath . '($|/)~', $rolepath)) {
+                        $effectivepath = $rolepath;
+                    } else {
+                        // Not inside an area where the user has the role, so ignore.
+                        continue;
+                    }
+
+                    if (!array_key_exists($effectivepath, $pathroleperms)) {
+                        $pathroleperms[$effectivepath] = [];
+                    }
+                    $pathroleperms[$effectivepath][$roleid] = $caps[$capability];
+                }
+            }
+        }
+
+        return $pathroleperms;
+    }
+
+    /**
+     * Calculates a permission tree based on an array of information about role permissions.
+     *
+     * The input parameter must be in the format returned by get_capability_info_at_each_context.
+     *
+     * The output is the root of a tree of stdClass objects with the fields 'path' (a context path),
+     * 'allow' (true or false), and 'children' (an array of similar objects).
+     *
+     * @param array $pathroleperms Array of permissions
+     * @return \stdClass Root object of permission tree
+     */
+    protected static function calculate_permission_tree(array $pathroleperms) {
+        // Considering each discovered context path as an inflection point, evaluate the user's
+        // permission (based on all roles) at each point.
+        $pathallows = [];
+        $mindepth = 1000;
+        $maxdepth = 0;
+        foreach ($pathroleperms as $path => $roles) {
+            $evaluatedroleperms = [];
+
+            // Walk up the tree starting from this path.
+            $innerpath = $path;
+            while ($innerpath !== '') {
+                $roles = $pathroleperms[$innerpath];
+
+                // Evaluate roles at this path level.
+                foreach ($roles as $roleid => $perm) {
+                    if (!array_key_exists($roleid, $evaluatedroleperms)) {
+                        $evaluatedroleperms[$roleid] = $perm;
+                    } else {
+                        // The existing one is at a more specific level so it takes precedence
+                        // UNLESS this is a prohibit.
+                        if ($perm == CAP_PROHIBIT) {
+                            $evaluatedroleperms[$roleid] = $perm;
+                        }
+                    }
+                }
+
+                // Go up to next path level (if any).
+                do {
+                    $innerpath = substr($innerpath, 0, strrpos($innerpath, '/'));
+                    if ($innerpath === '') {
+                        // No higher level data.
+                        break;
+                    }
+                } while (!array_key_exists($innerpath, $pathroleperms));
+            }
+
+            // If we have an allow from any role, and no prohibits, then user can access this path,
+            // else not.
+            $allow = false;
+            foreach ($evaluatedroleperms as $perm) {
+                if ($perm == CAP_ALLOW) {
+                    $allow = true;
+                } else if ($perm == CAP_PROHIBIT) {
+                    $allow = false;
+                    break;
+                }
+            }
+
+            // Store the result based on path and depth so that we can process in depth order in
+            // the next step.
+            $depth = strlen(preg_replace('~[^/]~', '', $path));
+            $mindepth = min($depth, $mindepth);
+            $maxdepth = max($depth, $maxdepth);
+            $pathallows[$depth][$path] = $allow;
+        }
+
+        // Organise into a tree structure, processing in depth order so that we have ancestors
+        // set up before we encounter their children.
+        $root = (object)['allow' => false, 'path' => null, 'children' => []];
+        $nodesbypath = [];
+        for ($depth = $mindepth; $depth <= $maxdepth; $depth++) {
+            // Skip any missing depth levels.
+            if (!array_key_exists($depth, $pathallows)) {
+                continue;
+            }
+            foreach ($pathallows[$depth] as $path => $allow) {
+                // Value for new tree node.
+                $leaf = (object)['allow' => $allow, 'path' => $path, 'children' => []];
+
+                // Try to find a place to join it on if there is one.
+                $ancestorpath = $path;
+                $found = false;
+                while ($ancestorpath) {
+                    $ancestorpath = substr($ancestorpath, 0, strrpos($ancestorpath, '/'));
+                    if (array_key_exists($ancestorpath, $nodesbypath)) {
+                        $found = true;
+                        break;
+                    }
+                }
+
+                if ($found) {
+                    $nodesbypath[$ancestorpath]->children[] = $leaf;
+                } else {
+                    $root->children[] = $leaf;
+                }
+                $nodesbypath[$path] = $leaf;
+            }
+        }
+
+        return $root;
+    }
+
+    /**
+     * Given a permission tree (in calculate_permission_tree format), removes any subtrees that
+     * are negative from the root. For example, if a top-level node of the permission tree has
+     * 'false' permission then it is meaningless because the default permission is already false;
+     * this function will remove it. However, if there is a child within that node that is positive,
+     * then that will need to be kept.
+     *
+     * @param \stdClass $root Root object
+     * @return \stdClass Filtered tree root
+     */
+    protected static function remove_negative_subtrees($root) {
+        // If a node 'starts' negative, we don't need it (as negative is the default) - extract only
+        // subtrees that start with a positive value.
+        $positiveroot = (object)['allow' => false, 'path' => null, 'children' => []];
+        $consider = [$root];
+        while ($consider) {
+            $first = array_shift($consider);
+            foreach ($first->children as $node) {
+                if ($node->allow) {
+                    // Add directly to new root.
+                    $positiveroot->children[] = $node;
+                } else {
+                    // Consider its children for adding to root (if there are any positive ones).
+                    $consider[] = $node;
+                }
+            }
+        }
+        return $positiveroot;
+    }
+
+    /**
+     * Removes duplicate nodes of a tree - where a child node has the same permission as its
+     * parent.
+     *
+     * @param \stdClass $parent Tree root node
+     */
+    protected static function remove_duplicate_nodes($parent) {
+        $length = count($parent->children);
+        $index = 0;
+        while ($index < $length) {
+            $child = $parent->children[$index];
+            if ($child->allow === $parent->allow) {
+                // Remove child node, but add its children to this node instead.
+                array_splice($parent->children, $index, 1);
+                $length--;
+                $index--;
+                foreach ($child->children as $grandchild) {
+                    $parent->children[] = $grandchild;
+                    $length++;
+                }
+            } else {
+                // Keep child node, but recurse to remove its unnecessary children.
+                self::remove_duplicate_nodes($child);
+            }
+            $index++;
+        }
+    }
+
+    /**
+     * Gets a permission tree for the given user and capability, representing the value of that
+     * capability at different contexts across the system. The tree will be simplified as far as
+     * possible.
+     *
+     * The output is the root of a tree of stdClass objects with the fields 'path' (a context path),
+     * 'allow' (true or false), and 'children' (an array of similar objects).
+     *
+     * @param int $userid User id
+     * @param string $capability Capability e.g. 'moodle/course:view'
+     * @return \stdClass Root node of tree
+     */
+    protected static function get_tree($userid, $capability) {
+        // Extract raw capability data for this user and capability.
+        $pathroleperms = self::get_capability_info_at_each_context($userid, $capability);
+
+        // Convert the raw data into a permission tree based on context.
+        $root = self::calculate_permission_tree($pathroleperms);
+        unset($pathroleperms);
+
+        // Simplify the permission tree by removing unnecessary nodes.
+        $root = self::remove_negative_subtrees($root);
+        self::remove_duplicate_nodes($root);
+
+        // Return the tree.
+        return $root;
+    }
+
+    /**
+     * Creates SQL suitable for restricting by contexts listed in the given permission tree.
+     *
+     * This function relies on the permission tree being in the format created by get_tree.
+     * Specifically, all the children of the root element must be set to 'allow' permission,
+     * children of those children must be 'not allow', children of those grandchildren 'allow', etc.
+     *
+     * @param \stdClass $parent Root node of permission tree
+     * @return array Two-element array of SQL (containing ? placeholders) and then a params array
+     */
+    protected static function create_sql($parent) {
+        global $DB;
+
+        $sql = '';
+        $params = [];
+        if ($parent->path !== null) {
+            // Except for the root element, create the condition that it applies to the context of
+            // this element (or anything within it).
+            $sql = ' (x.path = ? OR ' . $DB->sql_like('x.path', '?') .')';
+            $params[] = $parent->path;
+            $params[] = $parent->path . '/%';
+            if ($parent->children) {
+                // When there are children, these are assumed to have the opposite sign i.e. if we
+                // are allowing the parent, we are not allowing the children, and vice versa. So
+                // the 'OR' clause for children will be inside this 'AND NOT'.
+                $sql .= ' AND NOT (';
+            }
+        } else if (count($parent->children) > 1) {
+            // Place brackets in the query when it is going to be an OR of multiple conditions.
+            $sql .= ' (';
+        }
+        if ($parent->children) {
+            $first = true;
+            foreach ($parent->children as $child) {
+                if ($first) {
+                    $first = false;
+                } else {
+                    $sql  .= ' OR';
+                }
+
+                // Recuse to get the child requirements - this will be the check that the context
+                // is within the child, plus possibly and 'AND NOT' for any different contexts
+                // within the child.
+                list ($childsql, $childparams) = self::create_sql($child);
+                $sql .= $childsql;
+                $params = array_merge($params, $childparams);
+            }
+            // Close brackets if opened above.
+            if ($parent->path !== null || count($parent->children) > 1) {
+                $sql .= ')';
+            }
+        }
+        return [$sql, $params];
+    }
+
+    /**
+     * Gets SQL to restrict a query to contexts in which the user has a capability.
+     *
+     * This returns an array with two elements (SQL containing ? placeholders, and a params array).
+     * The SQL is intended to be used as part of a WHERE clause. It relies on the prefix 'x' being
+     * used for the Moodle context table.
+     *
+     * If the user does not have the permission anywhere at all (so that there is no point doing
+     * the query) then the two returned values will both be false.
+     *
+     * @param int $userid User id
+     * @param string $capability Capability e.g. 'moodle/course:view'
+     * @return array Two-element array of SQL (containing ? placeholders) and then a params array
+     */
+    public static function get_sql($userid, $capability) {
+        // Get a tree of capability permission at various contexts for current user.
+        $root = self::get_tree($userid, $capability);
+
+        // The root node always has permission false. If there are no child nodes then the user
+        // cannot access anything.
+        if (!$root->children) {
+            return [false, false];
+        }
+
+        // Get SQL to limit contexts based on the permission tree.
+        return self::create_sql($root);
+
+    }
+}
index bd36ac7..e172216 100644 (file)
       </KEYS>
       <INDEXES>
         <INDEX NAME="action" UNIQUE="false" FIELDS="action" COMMENT="insert/update/delete"/>
+        <INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="stats_daily" COMMENT="to accumulate daily stats">
       </KEYS>
       <INDEXES>
         <INDEX NAME="action" UNIQUE="false" FIELDS="action" COMMENT="insert/update/delete"/>
+        <INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="grade_categories_history" COMMENT="History of grade_categories">
       </KEYS>
       <INDEXES>
         <INDEX NAME="action" UNIQUE="false" FIELDS="action" COMMENT="insert/update/delete"/>
+        <INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="grade_items_history" COMMENT="History of grade_items">
       </KEYS>
       <INDEXES>
         <INDEX NAME="action" UNIQUE="false" FIELDS="action" COMMENT="insert/update/delete"/>
+        <INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="grade_grades_history" COMMENT="History table">
       </INDEXES>
     </TABLE>
   </TABLES>
-</XMLDB>
\ No newline at end of file
+</XMLDB>
index d0fb99f..ad75b0c 100644 (file)
@@ -2601,5 +2601,40 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2017092900.00);
     }
 
+    if ($oldversion < 2017100900.00) {
+        // Add index on time modified to grade_outcomes_history, grade_categories_history,
+        // grade_items_history, and scale_history.
+        $table = new xmldb_table('grade_outcomes_history');
+        $index = new xmldb_index('timemodified', XMLDB_INDEX_NOTUNIQUE, array('timemodified'));
+
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        $table = new xmldb_table('grade_items_history');
+        $index = new xmldb_index('timemodified', XMLDB_INDEX_NOTUNIQUE, array('timemodified'));
+
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        $table = new xmldb_table('grade_categories_history');
+        $index = new xmldb_index('timemodified', XMLDB_INDEX_NOTUNIQUE, array('timemodified'));
+
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        $table = new xmldb_table('scale_history');
+        $index = new xmldb_index('timemodified', XMLDB_INDEX_NOTUNIQUE, array('timemodified'));
+
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2017100900.00);
+    }
+
     return true;
 }
index 4fc331f..2eaea03 100644 (file)
@@ -62,16 +62,19 @@ if (typeof M.form.dependencyManager === 'undefined') {
          * Initializes the mapping from element name to YUI NodeList
          */
         initElementsByName: function() {
-            var names = {};
+            var names = {}; // Form elements with a given name.
+            var allnames = {}; // Form elements AND outer elements for groups with a given name.
 
             // Collect element names.
             Y.Object.each(this.get('dependencies'), function(conditions, i) {
                 names[i] = new Y.NodeList();
+                allnames[i] = new Y.NodeList();
                 for (var condition in conditions) {
                     for (var value in conditions[condition]) {
                         for (var hide in conditions[condition][value]) {
                             for (var ei in conditions[condition][value][hide]) {
                                 names[conditions[condition][value][hide][ei]] = new Y.NodeList();
+                                allnames[conditions[condition][value][hide][ei]] = new Y.NodeList();
                             }
                         }
                     }
@@ -83,16 +86,17 @@ if (typeof M.form.dependencyManager === 'undefined') {
                 var name = node.getAttribute('name');
                 if (({}).hasOwnProperty.call(names, name)) {
                     names[name].push(node);
+                    allnames[name].push(node);
                 }
             });
             // Locate any groups with the given name.
             this.get('form').all('.fitem').each(function(node) {
                 var name = node.getData('groupname');
-                if (name && ({}).hasOwnProperty.call(names, name)) {
-                    names[name].push(node);
+                if (name && ({}).hasOwnProperty.call(allnames, name)) {
+                    allnames[name].push(node);
                 }
             });
-            this._nameCollections = names;
+            this._nameCollections = {names: names, allnames: allnames};
         },
 
         /**
@@ -100,16 +104,22 @@ if (typeof M.form.dependencyManager === 'undefined') {
          * a YUI NodeList
          *
          * @param {String} name The form element name.
+         * @param {Boolean} includeGroups (optional - default false) Should the outer element for groups be included?
          * @return {Y.NodeList}
          */
-        elementsByName: function(name) {
+        elementsByName: function(name, includeGroups) {
+            if (includeGroups === undefined) {
+                includeGroups = false;
+            }
+            var collection = (includeGroups ? 'allnames' : 'names');
+
             if (!this._nameCollections) {
                 this.initElementsByName();
             }
-            if (!({}).hasOwnProperty.call(this._nameCollections, name)) {
+            if (!({}).hasOwnProperty.call(this._nameCollections[collection], name)) {
                 return new Y.NodeList();
             }
-            return this._nameCollections[name];
+            return this._nameCollections[collection][name];
         },
 
         /**
@@ -269,7 +279,7 @@ if (typeof M.form.dependencyManager === 'undefined') {
          * @param {Boolean} hidden True to hide, false to show.
          */
         _hideElement: function(name, hidden) {
-            var els = this.elementsByName(name);
+            var els = this.elementsByName(name, true);
             els.each(function(node) {
                 var e = node.ancestor('.fitem', true);
                 if (e) {
index f192822..9e27b4b 100644 (file)
@@ -1696,6 +1696,165 @@ class core_accesslib_testcase extends advanced_testcase {
         $this->assertFalse(has_all_capabilities($sca, $coursecontext, 0));
     }
 
+    /**
+     * Tests get_user_capability_course() which checks a capability across all courses.
+     */
+    public function test_get_user_capability_course() {
+        global $CFG, $USER;
+
+        $this->resetAfterTest();
+
+        $generator = $this->getDataGenerator();
+        $cap = 'moodle/course:view';
+
+        // Create a role which allows course:view and one that prohibits it, and one neither.
+        $allowroleid = $generator->create_role();
+        $prohibitroleid = $generator->create_role();
+        $emptyroleid = $generator->create_role();
+        $systemcontext = context_system::instance();
+        assign_capability($cap, CAP_ALLOW, $allowroleid, $systemcontext->id);
+        assign_capability($cap, CAP_PROHIBIT, $prohibitroleid, $systemcontext->id);
+
+        // Create two categories (nested).
+        $cat1 = $generator->create_category();
+        $cat2 = $generator->create_category(['parent' => $cat1->id]);
+
+        // Create six courses - two in cat1, two in cat2, and two in default category.
+        $c1 = $generator->create_course(['category' => $cat1->id, 'shortname' => 'Z']);
+        $c2 = $generator->create_course(['category' => $cat1->id, 'shortname' => 'Y']);
+        $c3 = $generator->create_course(['category' => $cat2->id, 'shortname' => 'X']);
+        $c4 = $generator->create_course(['category' => $cat2->id]);
+        $c5 = $generator->create_course();
+        $c6 = $generator->create_course();
+
+        // Category overrides: in cat 1, empty role is allowed; in cat 2, empty role is prevented.
+        assign_capability($cap, CAP_ALLOW, $emptyroleid,
+                context_coursecat::instance($cat1->id)->id);
+        assign_capability($cap, CAP_PREVENT, $emptyroleid,
+                context_coursecat::instance($cat2->id)->id);
+
+        // Course overrides: in C5, allow role is prevented; in C6, empty role is prohibited; in
+        // C3, empty role is allowed.
+        assign_capability($cap, CAP_PREVENT, $allowroleid,
+                context_course::instance($c5->id)->id);
+        assign_capability($cap, CAP_PROHIBIT, $emptyroleid,
+                context_course::instance($c6->id)->id);
+        assign_capability($cap, CAP_ALLOW, $emptyroleid,
+                context_course::instance($c3->id)->id);
+
+        // User 1 has no roles except default user role.
+        $u1 = $generator->create_user();
+
+        // It returns false (annoyingly) if there are no courses.
+        $this->assertFalse(get_user_capability_course($cap, $u1->id, true, '', 'id'));
+
+        // Final override: in C1, default user role is allowed.
+        assign_capability($cap, CAP_ALLOW, $CFG->defaultuserroleid,
+                context_course::instance($c1->id)->id);
+
+        // Should now get C1 only.
+        $courses = get_user_capability_course($cap, $u1->id, true, '', 'id');
+        $this->assert_course_ids([$c1->id], $courses);
+
+        // User 2 has allow role (system wide).
+        $u2 = $generator->create_user();
+        role_assign($allowroleid, $u2->id, $systemcontext->id);
+
+        // Should get everything except C5.
+        $courses = get_user_capability_course($cap, $u2->id, true, '', 'id');
+        $this->assert_course_ids([SITEID, $c1->id, $c2->id, $c3->id, $c4->id, $c6->id], $courses);
+
+        // User 3 has empty role (system wide).
+        $u3 = $generator->create_user();
+        role_assign($emptyroleid, $u3->id, $systemcontext->id);
+
+        // Should get cat 1 courses but not cat2, except C3.
+        $courses = get_user_capability_course($cap, $u3->id, true, '', 'id');
+        $this->assert_course_ids([$c1->id, $c2->id, $c3->id], $courses);
+
+        // User 4 has allow and empty role (system wide).
+        $u4 = $generator->create_user();
+        role_assign($allowroleid, $u4->id, $systemcontext->id);
+        role_assign($emptyroleid, $u4->id, $systemcontext->id);
+
+        // Should get everything except C5 and C6.
+        $courses = get_user_capability_course($cap, $u4->id, true, '', 'id');
+        $this->assert_course_ids([SITEID, $c1->id, $c2->id, $c3->id, $c4->id], $courses);
+
+        // User 5 has allow role in default category only.
+        $u5 = $generator->create_user();
+        role_assign($allowroleid, $u5->id, context_coursecat::instance($c5->category)->id);
+
+        // Should get C1 and the default category courses but not C5.
+        $courses = get_user_capability_course($cap, $u5->id, true, '', 'id');
+        $this->assert_course_ids([$c1->id, $c6->id], $courses);
+
+        // User 6 has a bunch of course roles: prohibit role in C1, empty role in C3, allow role in
+        // C6.
+        $u6 = $generator->create_user();
+        role_assign($prohibitroleid, $u6->id, context_course::instance($c1->id)->id);
+        role_assign($emptyroleid, $u6->id, context_course::instance($c3->id)->id);
+        role_assign($allowroleid, $u6->id, context_course::instance($c5->id)->id);
+
+        // Should get C3 only because the allow role is prevented in C5.
+        $courses = get_user_capability_course($cap, $u6->id, true, '', 'id');
+        $this->assert_course_ids([$c3->id], $courses);
+
+        // Admin user gets everything....
+        $courses = get_user_capability_course($cap, get_admin()->id, true, '', 'id');
+        $this->assert_course_ids([SITEID, $c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c6->id],
+                $courses);
+
+        // Unless you turn off doanything, when it only has the things a user with no role does.
+        $courses = get_user_capability_course($cap, get_admin()->id, false, '', 'id');
+        $this->assert_course_ids([$c1->id], $courses);
+
+        // Using u3 as an example, test the limit feature.
+        $courses = get_user_capability_course($cap, $u3->id, true, '', 'id', 2);
+        $this->assert_course_ids([$c1->id, $c2->id], $courses);
+
+        // Check sorting.
+        $courses = get_user_capability_course($cap, $u3->id, true, '', 'shortname');
+        $this->assert_course_ids([$c3->id, $c2->id, $c1->id], $courses);
+
+        // Check returned fields - default.
+        $courses = get_user_capability_course($cap, $u3->id, true, '', 'id');
+        $this->assertEquals((object)['id' => $c1->id], $courses[0]);
+
+        // Add a selection of fields, including the context ones with special handling.
+        $courses = get_user_capability_course($cap, $u3->id, true, 'shortname, ctxlevel, ctxdepth, ctxinstance', 'id');
+        $this->assertEquals((object)['id' => $c1->id, 'shortname' => 'Z', 'ctxlevel' => 50,
+                'ctxdepth' => 3, 'ctxinstance' => $c1->id], $courses[0]);
+
+        // Test front page role - user 1 has no roles, but if we change the front page role
+        // definition so that it has our capability, then they should see the front page course.
+        // as well as C1.
+        assign_capability($cap, CAP_ALLOW, $CFG->defaultfrontpageroleid, $systemcontext->id);
+        $courses = get_user_capability_course($cap, $u1->id, true, '', 'id');
+        $this->assert_course_ids([SITEID, $c1->id], $courses);
+
+        // Check that temporary guest access (in this case, given on course 2 for user 1)
+        // also is included, if it has this capability.
+        assign_capability($cap, CAP_ALLOW, $CFG->guestroleid, $systemcontext->id);
+        $this->setUser($u1);
+        load_temp_course_role(context_course::instance($c2->id), $CFG->guestroleid);
+        $courses = get_user_capability_course($cap, $USER->id, true, '', 'id');
+        $this->assert_course_ids([SITEID, $c1->id, $c2->id], $courses);
+    }
+
+    /**
+     * Extracts an array of course ids to make the above test script shorter.
+     *
+     * @param int[] $expected Array of expected course ids
+     * @param stdClass[] $courses Array of course objects
+     */
+    protected function assert_course_ids(array $expected, array $courses) {
+        $courseids = array_map(function($c) {
+            return $c->id;
+        }, $courses);
+        $this->assertEquals($expected, $courseids);
+    }
+
     /**
      * Test if course creator future capability lookup works.
      */
index 12441f2..f71c61a 100644 (file)
@@ -16,6 +16,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
+ * A Moodle-modified WebDAV client, based on
  * webdav_client v0.1.5, a php based webdav client class.
  * class webdav client. a php based nearly RFC 2518 conforming client.
  *
  * Please notice that all Filenames coming from or going to the webdav server should be UTF-8 encoded (see RFC 2518).
  * This class tries to convert all you filenames into utf-8 when it's needed.
  *
+ * Moodle modifications:
+ * * Moodle 3.4: Add support for OAuth 2 bearer token-based authentication
+ *
  * @package moodlecore
  * @author Christian Juerges <christian.juerges@xwave.ch>, Xwave GmbH, Josefstr. 92, 8005 Zuerich - Switzerland
  * @copyright (C) 2003/2004, Christian Juerges
  * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
- * @version 0.1.5
  */
 
 class webdav_client {
@@ -79,12 +82,24 @@ class webdav_client {
     private $_cnonce = '';
     private $_nc = 0;
 
+    /**
+     * OAuth token used for bearer auth.
+     * @var string
+     */
+    private $oauthtoken;
+
     /**#@-*/
 
     /**
      * Constructor - Initialise class variables
+     * @param string $server Hostname of the server to connect to
+     * @param string $user Username (for basic/digest auth, see $auth)
+     * @param string $pass Password (for basic/digest auth, see $auth)
+     * @param bool $auth Authentication type; one of ['basic', 'digest', 'bearer']
+     * @param string $socket Used protocol for fsockopen, usually: '' (empty) or 'ssl://'
+     * @param string $oauthtoken OAuth 2 bearer token (for bearer auth, see $auth)
      */
-    function __construct($server = '', $user = '', $pass = '', $auth = false, $socket = '') {
+    public function __construct($server = '', $user = '', $pass = '', $auth = false, $socket = '', $oauthtoken = '') {
         if (!empty($server)) {
             $this->_server = $server;
         }
@@ -94,6 +109,9 @@ class webdav_client {
         }
         $this->_auth = $auth;
         $this->_socket = $socket;
+        if ($auth == 'bearer') {
+            $this->oauthtoken = $oauthtoken;
+        }
     }
     public function __set($key, $value) {
         $property = '_' . $key;
@@ -1323,6 +1341,8 @@ EOD;
             if ($signature = $this->digest_signature($method)){
                 $this->header_add($signature);
             }
+        } else if ($this->_auth == 'bearer') {
+            $this->header_add(sprintf('Authorization: Bearer %s', $this->oauthtoken));
         }
     }
 
index f2beb68..9f6ebc2 100644 (file)
@@ -138,7 +138,9 @@ class quiz_overview_report extends quiz_attempts_report {
             // Construct the SQL.
             list($fields, $from, $where, $params) = $table->base_sql($allowedjoins);
 
-            $table->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp", $params);
+            // The WHERE clause is vital here, because some parts of tablelib.php will expect to
+            // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL.
+            $table->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params);
 
             // Test to see if there are any regraded attempts to be listed.
             $fields .= ", COALESCE((
index 79c1928..81aece2 100644 (file)
@@ -115,7 +115,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
         // Now do a minimal set-up of the table class.
         $table = new quiz_overview_table($quiz, $context, $qmsubselect, $reportoptions,
                 $empty, $studentsjoins, array(1), null);
-        $table->define_columns(array('attempt'));
+        $table->define_columns(array('fullname'));
         $table->sortable(true, 'uniqueid');
         $table->define_baseurl(new moodle_url('/mod/quiz/report.php'));
         $table->setup();
@@ -123,6 +123,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
         // Run the query.
         list($fields, $from, $where, $params) = $table->base_sql($studentsjoins);
         $table->set_sql($fields, $from, $where, $params);
+        $table->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params);
         $table->query_db(30, false);
 
         // Verify what was returned: Student 1's best and in progress attempts.
@@ -137,6 +138,25 @@ class quiz_overview_report_testcase extends advanced_testcase {
         $this->assertEquals(1, $table->rawdata[$student2->id . '#3']->gradedattempt);
         $this->assertArrayHasKey($student3->id . '#0', $table->rawdata);
         $this->assertEquals(0, $table->rawdata[$student3->id . '#0']->gradedattempt);
+
+        // Ensure that filtering by inital does not break it.
+        // This involves setting a private properly of the base class, which is
+        // only really possible using reflection :-(.
+        $reflectionobject = new ReflectionObject($table);
+        while ($parent = $reflectionobject->getParentClass()) {
+            $reflectionobject = $parent;
+        }
+        $prefsproperty = $reflectionobject->getProperty('prefs');
+        $prefsproperty->setAccessible(true);
+        $prefs = $prefsproperty->getValue($table);
+        $prefs['i_first'] = 'A';
+        $prefsproperty->setValue($table, $prefs);
+
+        list($fields, $from, $where, $params) = $table->base_sql($studentsjoins);
+        $table->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params);
+        $table->set_sql($fields, $from, $where, $params);
+        $table->query_db(30, false);
+        // Just verify that this does not cause a fatal error.
     }
 
     /**
index 2d41ef3..45f393a 100644 (file)
@@ -151,7 +151,9 @@ class quiz_responses_report extends quiz_attempts_report {
 
             list($fields, $from, $where, $params) = $table->base_sql($allowedjoins);
 
-            $table->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp", $params);
+            // The WHERE clause is vital here, because some parts of tablelib.php will expect to
+            // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL.
+            $table->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params);
 
             $table->set_sql($fields, $from, $where, $params);
 
index a8c58dd..4cb6579 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2017100600.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2017100900.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.