Merge branch 'wip-MDL-41788_master_3' of git://github.com/gjb2048/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 23 Dec 2013 23:52:22 +0000 (12:52 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 23 Dec 2013 23:52:22 +0000 (12:52 +1300)
158 files changed:
admin/cli/install.php
admin/cli/install_database.php
admin/environment.xml
admin/index.php
admin/tool/behat/cli/util.php
admin/tool/behat/tests/manager_test.php [moved from admin/tool/behat/tests/tool_behat_test.php with 95% similarity]
auth/mnet/auth.php
backup/cc/entities.class.php
badges/criteria/award_criteria.php
badges/criteria/award_criteria_activity.php
badges/criteria/award_criteria_course.php
badges/criteria/award_criteria_courseset.php
badges/criteria/award_criteria_manual.php
badges/criteria/award_criteria_overall.php
badges/criteria/award_criteria_profile.php
badges/tests/badgeslib_test.php
badges/upgrade.txt [new file with mode: 0644]
blocks/community/yui/comments/comments.js
blocks/community/yui/imagegallery/imagegallery.js
blocks/course_list/block_course_list.php
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js
blocks/navigation/yui/src/navigation/js/navigation.js
cache/classes/definition.php
cache/stores/memcache/lib.php
config-dist.php
course/lib.php
course/tests/behat/activities_edit_with_block_dock.feature [new file with mode: 0644]
course/tests/behat/behat_course.php
course/view.php
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js [new file with mode: 0644]
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js [new file with mode: 0644]
course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js [new file with mode: 0644]
course/yui/modchooser/modchooser.js [deleted file]
course/yui/src/modchooser/build.json [new file with mode: 0644]
course/yui/src/modchooser/js/modchooser.js [new file with mode: 0644]
course/yui/src/modchooser/meta/modchooser.json [new file with mode: 0644]
enrol/manual/yui/quickenrolment/quickenrolment.js
enrol/yui/rolemanager/rolemanager.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js
filter/glossary/yui/src/autolinker/js/autolinker.js
filter/urltolink/filter.php
filter/urltolink/tests/filter_test.php
install.php
install/lang/it/error.php
lang/en/moodle.php
lib/accesslib.php
lib/adodb/adodb.inc.php
lib/adodb/readme_moodle.txt
lib/ajax/ajaxlib.php
lib/ajax/getsiteadminbranch.php
lib/badgeslib.php
lib/behat/classes/behat_command.php
lib/behat/classes/behat_config_manager.php
lib/behat/classes/util.php
lib/behat/lib.php
lib/cronlib.php
lib/ddl/tests/ddl_test.php
lib/dml/moodle_database.php
lib/dml/moodle_temptables.php
lib/dml/mssql_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/pdo_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/pgsql_native_moodle_temptables.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/form/url.php
lib/grade/grade_category.php
lib/grade/tests/grade_category_test.php
lib/javascript-static.js
lib/medialib.php
lib/moodlelib.php
lib/outputrequirementslib.php
lib/setup.php
lib/statslib.php
lib/testing/classes/util.php
lib/testing/lib.php
lib/tests/accesslib_test.php
lib/tests/ajaxlib_test.php [new file with mode: 0644]
lib/tests/behat/behat_hooks.php
lib/upgrade.txt
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js
lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-debug.js
lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-min.js
lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-debug.js
lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-min.js
lib/yui/build/moodle-core-tooltip/moodle-core-tooltip.js
lib/yui/src/actionmenu/js/actionmenu.js
lib/yui/src/chooserdialogue/js/chooserdialogue.js
lib/yui/src/formchangechecker/js/formchangechecker.js
lib/yui/src/notification/js/dialogue.js
lib/yui/src/tooltip/js/tooltip.js
mod/assign/feedback/comments/lang/en/assignfeedback_comments.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/comments/settings.php
mod/assign/feedback/comments/version.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/commentsearch.js
mod/assign/feedback/editpdf/yui/src/editor/js/dropdown.js
mod/assign/locallib.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/submission/onlinetext/tests/events_test.php
mod/assign/tests/behat/allow_another_attempt.feature [new file with mode: 0644]
mod/assign/tests/behat/comment_inline.feature [new file with mode: 0644]
mod/assign/tests/behat/edit_previous_feedback.feature [new file with mode: 0644]
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assign/version.php
mod/assignment/lib.php
mod/assignment/type/upload/assignment.class.php
mod/assignment/type/uploadsingle/assignment.class.php
mod/assignment/view.php
mod/book/backup/moodle1/lib.php
mod/data/field/url/field.class.php
mod/scorm/tests/behat/add_scorm.feature
mod/url/classes/event/course_module_instance_list_viewed.php [new file with mode: 0644]
mod/url/classes/event/course_module_viewed.php [new file with mode: 0644]
mod/url/index.php
mod/url/view.php
mod/wiki/tests/events_test.php
portfolio/googledocs/lib.php
repository/dropbox/lib.php
repository/filepicker.js
repository/repository_ajax.php
theme/base/style/core.css
theme/base/style/course.css
theme/bootstrapbase/layout/columns1.php
theme/bootstrapbase/layout/columns2.php
theme/bootstrapbase/layout/columns3.php
theme/bootstrapbase/layout/popup.php
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/style/moodle.css
theme/clean/layout/columns1.php
theme/clean/layout/columns2.php
theme/clean/layout/columns3.php
theme/yui_combo.php
user/index.php
version.php

index a6c3873..df61269 100644 (file)
@@ -142,10 +142,10 @@ define('PHPUNIT_TEST', false);
 define('IGNORE_COMPONENT_CACHE', true);
 
 // Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.3.3") < 0) {
+if (version_compare(phpversion(), "5.4.4") < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN NOT move it after installib
-    fwrite(STDERR, "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).\n");
+    fwrite(STDERR, "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n");
     fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
     exit(1);
 }
index 3ab6279..91ac890 100644 (file)
@@ -62,10 +62,10 @@ Example:
 ";
 
 // Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.3.3") < 0) {
+if (version_compare(phpversion(), "5.4.4") < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN NOT move it after installib
-    fwrite(STDERR, "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).\n");
+    fwrite(STDERR, "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n");
     fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
     exit(1);
 }
index 1e937cd..4edfcd1 100644 (file)
       </PHP_SETTING>
     </PHP_SETTINGS>
   </MOODLE>
+  <MOODLE version="2.7" requires="2.2">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mariadb" version="5.5.31" />
+      <VENDOR name="mysql" version="5.5.31" />
+      <VENDOR name="postgres" version="9.1" />
+      <VENDOR name="mssql" version="10.0" />
+      <VENDOR name="oracle" version="10.2" />
+    </DATABASE>
+    <PHP version="5.4.4" level="required">
+    </PHP>
+    <PCREUNICODE level="optional">
+      <FEEDBACK>
+        <ON_CHECK message="pcreunicodewarning" />
+      </FEEDBACK>
+    </PCREUNICODE>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opensslrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zlib" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="gdrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="intlrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" level="required"/>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="96M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="opcache.enable" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opcacherecommended" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index a07104c..e42687a 100644 (file)
@@ -30,10 +30,10 @@ if (!file_exists('../config.php')) {
 }
 
 // Check that PHP is of a sufficient version as soon as possible
-if (version_compare(phpversion(), '5.3.3') < 0) {
+if (version_compare(phpversion(), '5.4.4') < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN NOT move it to later place
-    echo "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).<br />";
+    echo "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).<br />";
     echo "Please upgrade your server software or install older Moodle version.";
     die();
 }
index 28c933a..790406f 100644 (file)
@@ -78,85 +78,29 @@ if (!empty($options['help'])) {
     exit(0);
 }
 
-
-// Checking $CFG->behat_* vars and values.
+// Describe this script.
 define('BEHAT_UTIL', true);
 define('CLI_SCRIPT', true);
-define('ABORT_AFTER_CONFIG', true);
 define('NO_OUTPUT_BUFFERING', true);
 define('IGNORE_COMPONENT_CACHE', true);
 
-error_reporting(E_ALL | E_STRICT);
-ini_set('display_errors', '1');
-ini_set('log_errors', '1');
-
-// Getting $CFG data.
+// Only load CFG from config.php, stop ASAP in lib/setup.php.
+define('ABORT_AFTER_CONFIG', true);
 require_once(__DIR__ . '/../../../../config.php');
 
-// When we use the utilities we don't know how the site
-// will be accessed, so if neither $CFG->behat_switchcompletely or
-// $CFG->behat_wwwroot are set we must think that the site will
-// be accessed using the built-in server which is set by default
-// to localhost:8000. We need to do this to prevent uses of the production
-// wwwroot when the site is being installed / dropped...
-$CFG->behat_wwwroot = behat_get_wwwroot();
-
-// Checking the integrity of the provided $CFG->behat_* vars
-// to prevent conflicts with production and phpunit environments.
-behat_check_config_vars();
-
-// Create behat_dataroot if it doesn't exists.
-if (!file_exists($CFG->behat_dataroot)) {
-    if (!mkdir($CFG->behat_dataroot, $CFG->directorypermissions)) {
-        behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
-    }
-}
-if (!is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) {
-    behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory has no permissions or is not a directory');
-}
-
-// Check that the directory does not contains other things.
-if (!file_exists("$CFG->behat_dataroot/behattestdir.txt")) {
-    if ($dh = opendir($CFG->behat_dataroot)) {
-        while (($file = readdir($dh)) !== false) {
-            if ($file === 'behat' or $file === '.' or $file === '..' or $file === '.DS_Store') {
-                continue;
-            }
-            behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot directory is not empty, ensure this is the directory where you want to install behat test dataroot');
-        }
-        closedir($dh);
-        unset($dh);
-        unset($file);
-    }
-
-    // Now we create dataroot directory structure for behat tests.
-    testing_initdataroot($CFG->behat_dataroot, 'behat');
-}
-
-// Overrides vars with behat-test ones.
-$vars = array('wwwroot', 'prefix', 'dataroot');
-foreach ($vars as $var) {
-    $CFG->{$var} = $CFG->{'behat_' . $var};
-}
-
-// Clean $CFG extra values before performing any action.
-behat_clean_init_config();
-
-$CFG->noemailever = true;
-$CFG->passwordsaltmain = 'moodle';
-
-$CFG->themerev = 1;
-$CFG->jsrev = 1;
-
-// Unset cache and temp directories to reset them again with the new $CFG->dataroot.
-unset($CFG->cachedir);
-unset($CFG->localcachedir);
-unset($CFG->tempdir);
+// Remove error handling overrides done in config.php.
+$CFG->debug = (E_ALL | E_STRICT);
+$CFG->debugdisplay = 1;
+error_reporting($CFG->debug);
+ini_set('display_errors', '1');
+ini_set('log_errors', '1');
 
-// Continues setup.
+// Finish moodle init.
 define('ABORT_AFTER_CONFIG_CANCEL', true);
 require("$CFG->dirroot/lib/setup.php");
 
+raise_memory_limit(MEMORY_HUGE);
+
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/upgradelib.php');
 require_once($CFG->libdir.'/clilib.php');
@@ -185,7 +129,7 @@ if ($options['install']) {
     behat_util::start_test_mode();
     $runtestscommand = behat_command::get_behat_command() . ' --config '
         . $CFG->behat_dataroot . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
-    mtrace("Acceptance tests environment enabled, to run the tests use:\n " . $runtestscommand);
+    mtrace("Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:\n " . $runtestscommand);
 } else if ($options['disable']) {
     behat_util::stop_test_mode();
     mtrace("Acceptance tests environment disabled");
similarity index 95%
rename from admin/tool/behat/tests/tool_behat_test.php
rename to admin/tool/behat/tests/manager_test.php
index 09db5e6..1dc44ef 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Unit tests for admin/tool/behat.
+ * Unit tests for behat manager.
  *
  * @package   tool_behat
  * @copyright  2012 David Monllaó
@@ -30,45 +30,13 @@ require_once($CFG->libdir . '/behat/classes/util.php');
 require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
 
 /**
- * Allows access to internal methods without exposing them.
- *
- * @package    tool_behat
- * @copyright  2012 David Monllaó
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class testable_behat_config_manager extends behat_config_manager {
-
-    /**
-     * Allow access to protected method
-     * @see parent::merge_config()
-     * @param mixed $config
-     * @param mixed $localconfig
-     * @return mixed
-     */
-    public static function merge_config($config, $localconfig) {
-        return parent::merge_config($config, $localconfig);
-    }
-
-    /**
-     * Allow access to protected method
-     * @see parent::get_config_file_contents()
-     * @param array $features
-     * @param array $stepsdefinitions
-     * @return string
-     */
-    public static function get_config_file_contents($features, $stepsdefinitions) {
-        return parent::get_config_file_contents($features, $stepsdefinitions);
-    }
-}
-
-/**
- * Tool behat tests.
+ * Behat manager tests.
  *
  * @package    tool_behat
  * @copyright  2012 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class tool_behat_testcase extends advanced_testcase {
+class tool_behat_manager_testcase extends advanced_testcase {
 
     /**
      * behat_config_manager tests.
@@ -103,7 +71,7 @@ class tool_behat_testcase extends advanced_testcase {
 
         $array = testable_behat_config_manager::merge_config($array1, $array2);
 
-        // Overriddes are applied.
+        // Overrides are applied.
         $this->assertEquals('OVERRIDDEN1', $array['simple']);
         $this->assertEquals('OVERRIDDEN2', $array['array']['one']);
 
@@ -150,10 +118,8 @@ class tool_behat_testcase extends advanced_testcase {
             $this->markTestSkipped('Behat not installed.');
         }
 
-        // It is possible that it has no value.
-        if (empty($CFG->behat_wwwroot)) {
-            $CFG->behat_wwwroot = behat_get_wwwroot();
-        }
+        // Add some fake test url.
+        $CFG->behat_wwwroot = 'http://example.com/behat';
 
         // To avoid user value at config.php level.
         unset($CFG->behat_config);
@@ -194,3 +160,34 @@ class tool_behat_testcase extends advanced_testcase {
 
 }
 
+/**
+ * Allows access to internal methods without exposing them.
+ *
+ * @package    tool_behat
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_behat_config_manager extends behat_config_manager {
+
+    /**
+     * Allow access to protected method
+     * @see parent::merge_config()
+     * @param mixed $config
+     * @param mixed $localconfig
+     * @return mixed
+     */
+    public static function merge_config($config, $localconfig) {
+        return parent::merge_config($config, $localconfig);
+    }
+
+    /**
+     * Allow access to protected method
+     * @see parent::get_config_file_contents()
+     * @param array $features
+     * @param array $stepsdefinitions
+     * @return string
+     */
+    public static function get_config_file_contents($features, $stepsdefinitions) {
+        return parent::get_config_file_contents($features, $stepsdefinitions);
+    }
+}
index e995021..ff28703 100644 (file)
@@ -941,7 +941,6 @@ class auth_plugin_mnet extends auth_plugin_base {
         // run the keepalive client
         $this->keepalive_client();
 
-        // admin/cron.php should have run srand for us
         $random100 = rand(0,100);
         if ($random100 < 10) {     // Approximately 10% of the time.
             // nuke olden sessions
index c9e5526..4f441ba 100644 (file)
@@ -309,7 +309,6 @@ class entities {
             $source = str_split($source, 1);
 
             for ($i = 1; $i <= $length; $i++) {
-                mt_srand((double) microtime() * 1000000);
                 $num = mt_rand(1, count($source));
                 $response .= $source[$num - 1];
             }
index 96526c4..4287cdd 100644 (file)
@@ -236,9 +236,20 @@ abstract class award_criteria {
      * Review this criteria and decide if the user has completed
      *
      * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    abstract public function review($userid);
+    abstract public function review($userid, $filtered = false);
+
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    abstract public function get_completed_criteria_sql();
 
     /**
      * Mark this criteria as complete for a user
index ff9b92e..8f4d4b0 100644 (file)
@@ -37,13 +37,20 @@ class award_criteria_activity extends award_criteria {
     public $criteriatype = BADGE_CRITERIA_TYPE_ACTIVITY;
 
     private $courseid;
+    private $coursestartdate;
 
     public $required_param = 'module';
     public $optional_params = array('bydate');
 
     public function __construct($record) {
+        global $DB;
         parent::__construct($record);
-        $this->courseid = self::get_course();
+
+        $course = $DB->get_record_sql('SELECT b.courseid, c.startdate
+                        FROM {badge} b INNER JOIN {course} c ON b.courseid = c.id
+                        WHERE b.id = :badgeid ', array('badgeid' => $this->badgeid));
+        $this->courseid = $course->courseid;
+        $this->coursestartdate = $course->startdate;
     }
 
     /**
@@ -95,17 +102,6 @@ class award_criteria_activity extends award_criteria {
         }
     }
 
-    /**
-     * Return course ID for activities
-     *
-     * @return int
-     */
-    private function get_course() {
-        global $DB;
-        $courseid = $DB->get_field('badge', 'courseid', array('id' => $this->badgeid));
-        return $courseid;
-    }
-
     /**
      * Add appropriate new criteria options to the form
      *
@@ -184,14 +180,17 @@ class award_criteria_activity extends award_criteria {
      * Review this criteria and decide if it has been completed
      *
      * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    public function review($userid) {
-        global $DB;
+    public function review($userid, $filtered = false) {
         $completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS);
-        $course = $DB->get_record('course', array('id' => $this->courseid));
+        $course = new stdClass();
+        $course->id = $this->courseid;
 
-        if ($course->startdate > time()) {
+        if ($this->coursestartdate > time()) {
             return false;
         }
 
@@ -217,7 +216,7 @@ class award_criteria_activity extends award_criteria {
                 } else {
                     return false;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if (in_array($data->completionstate, $completionstates) && $check_date) {
                     return true;
                 } else {
@@ -229,4 +228,44 @@ class award_criteria_activity extends award_criteria {
 
         return $overall;
     }
+
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        $join = '';
+        $where = '';
+        $params = array();
+
+        if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            foreach ($this->params as $param) {
+                $moduledata[] = " cmc.coursemoduleid = :completedmodule{$param['module']} ";
+                $params["completedmodule{$param['module']}"] = $param['module'];
+            }
+            if (!empty($moduledata)) {
+                $extraon = implode(' OR ', $moduledata);
+                $join = " JOIN {course_modules_completion} cmc ON cmc.userid = u.id AND
+                          ( cmc.completionstate = :completionpass OR cmc.completionstate = :completioncomplete ) AND ({$extraon})";
+                $params["completionpass"] = COMPLETION_COMPLETE_PASS;
+                $params["completioncomplete"] = COMPLETION_COMPLETE;
+            }
+            return array($join, $where, $params);
+        } else {
+            foreach ($this->params as $param) {
+                $join .= " LEFT JOIN {course_modules_completion} cmc{$param['module']} ON
+                          cmc{$param['module']}.userid = u.id AND
+                          cmc{$param['module']}.coursemoduleid = :completedmodule{$param['module']} AND
+                          ( cmc{$param['module']}.completionstate = :completionpass{$param['module']} OR
+                            cmc{$param['module']}.completionstate = :completioncomplete{$param['module']} )";
+                $where .= " AND cmc{$param['module']}.coursemoduleid IS NOT NULL ";
+                $params["completedmodule{$param['module']}"] = $param['module'];
+                $params["completionpass{$param['module']}"] = COMPLETION_COMPLETE_PASS;
+                $params["completioncomplete{$param['module']}"] = COMPLETION_COMPLETE;
+            }
+            return array($join, $where, $params);
+        }
+    }
 }
index c6089a2..295d927 100644 (file)
@@ -38,9 +38,23 @@ class award_criteria_course extends award_criteria {
     /* @var int Criteria [BADGE_CRITERIA_TYPE_COURSE] */
     public $criteriatype = BADGE_CRITERIA_TYPE_COURSE;
 
+    private $courseid;
+    private $coursestartdate;
+
     public $required_param = 'course';
     public $optional_params = array('grade', 'bydate');
 
+    public function __construct($record) {
+        global $DB;
+        parent::__construct($record);
+
+        $course = $DB->get_record_sql('SELECT b.courseid, c.startdate
+                        FROM {badge} b INNER JOIN {course} c ON b.courseid = c.id
+                        WHERE b.id = :badgeid ', array('badgeid' => $this->badgeid));
+        $this->courseid = $course->courseid;
+        $this->coursestartdate = $course->startdate;
+    }
+
     /**
      * Add appropriate form elements to the criteria form
      *
@@ -151,18 +165,22 @@ class award_criteria_course extends award_criteria {
      * Review this criteria and decide if it has been completed
      *
      * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    public function review($userid) {
-        global $DB;
-        foreach ($this->params as $param) {
-            $course = $DB->get_record('course', array('id' => $param['course']));
+    public function review($userid, $filtered = false) {
+        $course = new stdClass();
+        $course->id = $this->courseid;
 
-            if ($course->startdate > time()) {
-                return false;
-            }
+        if ($this->coursestartdate > time()) {
+            return false;
+        }
 
-            $info = new completion_info($course);
+        $info = new completion_info($course);
+
+        foreach ($this->params as $param) {
             $check_grade = true;
             $check_date = true;
 
@@ -171,7 +189,7 @@ class award_criteria_course extends award_criteria {
                 $check_grade = ($grade->grade >= $param['grade']);
             }
 
-            if (isset($param['bydate'])) {
+            if (!$filtered && isset($param['bydate'])) {
                 $cparams = array(
                         'userid' => $userid,
                         'course' => $course->id,
@@ -188,4 +206,27 @@ class award_criteria_course extends award_criteria {
 
         return false;
     }
-}
\ No newline at end of file
+
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        // We have only one criterion here, so taking the first one.
+        $coursecriteria = reset($this->params);
+
+        $join = " LEFT JOIN {course_completions} cc ON cc.userid = u.id AND cc.timecompleted > 0";
+        $where = ' AND cc.course = :courseid ';
+        $params['courseid'] = $this->courseid;
+
+        // Add by date parameter.
+        if (isset($param['bydate'])) {
+            $where .= ' AND cc.timecompleted <= :completebydate';
+            $params['completebydate'] = $coursecriteria['bydate'];
+        }
+
+        return array($join, $where, $params);
+    }
+}
index a20e70b..5aabfe8 100644 (file)
@@ -202,12 +202,17 @@ class award_criteria_courseset extends award_criteria {
     /**
      * Review this criteria and decide if it has been completed
      *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    public function review($userid) {
-        global $DB;
+    public function review($userid, $filtered = false) {
         foreach ($this->params as $param) {
-            $course = $DB->get_record('course', array('id' => $param['course']));
+            $course =  new stdClass();
+            $course->id = $param['course'];
+
             $info = new completion_info($course);
             $check_grade = true;
             $check_date = true;
@@ -217,7 +222,7 @@ class award_criteria_courseset extends award_criteria {
                 $check_grade = ($grade->grade >= $param['grade']);
             }
 
-            if (isset($param['bydate'])) {
+            if (!$filtered && isset($param['bydate'])) {
                 $cparams = array(
                         'userid' => $userid,
                         'course' => $course->id,
@@ -235,7 +240,7 @@ class award_criteria_courseset extends award_criteria {
                 } else {
                     return false;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if ($info->is_course_complete($userid) && $check_grade && $check_date) {
                     return true;
                 } else {
@@ -247,4 +252,39 @@ class award_criteria_courseset extends award_criteria {
 
         return $overall;
     }
+
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        $join = '';
+        $where = '';
+        $params = array();
+
+        if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            foreach ($this->params as $param) {
+                $coursedata[] = " cc.course = :completedcourse{$param['course']} ";
+                $params["completedcourse{$param['course']}"] = $param['course'];
+            }
+            if (!empty($coursedata)) {
+                $extraon = implode(' OR ', $coursedata);
+                $join = " JOIN {course_completions} cc ON cc.userid = u.id AND
+                          cc.timecompleted > 0 AND ({$extraon})";
+            }
+            return array($join, $where, $params);
+        } else {
+            foreach ($this->params as $param) {
+                $join .= " LEFT JOIN {course_completions} cc{$param['course']} ON
+                          cc{$param['course']}.userid = u.id AND
+                          cc{$param['course']}.course = :completedcourse{$param['course']} AND
+                          cc{$param['course']}.timecompleted > 0 ";
+                $where .= " AND cc{$param['course']}.course IS NOT NULL ";
+                $params["completedcourse{$param['course']}"] = $param['course'];
+            }
+            return array($join, $where, $params);
+        }
+    }
 }
index 672c9f8..616ba97 100644 (file)
@@ -142,11 +142,19 @@ class award_criteria_manual extends award_criteria {
      * Review this criteria and decide if it has been completed
      *
      * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    public function review($userid) {
+    public function review($userid, $filtered = false) {
         global $DB;
 
+        // Users were already filtered by criteria completion.
+        if ($filtered) {
+            return true;
+        }
+
         $overall = false;
         foreach ($this->params as $param) {
             $crit = $DB->get_record('badge_manual_award', array('issuerrole' => $param['role'], 'recipientid' => $userid, 'badgeid' => $this->badgeid));
@@ -157,7 +165,7 @@ class award_criteria_manual extends award_criteria {
                     $overall = true;
                     continue;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if (!$crit) {
                     $overall = false;
                     continue;
@@ -169,6 +177,41 @@ class award_criteria_manual extends award_criteria {
         return $overall;
     }
 
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        $join = '';
+        $where = '';
+        $params = array();
+
+        if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            foreach ($this->params as $param) {
+                $roledata[] = " bma.issuerrole = :issuerrole{$param['role']} ";
+                $params["issuerrole{$param['role']}"] = $param['role'];
+            }
+            if (!empty($roledata)) {
+                $extraon = implode(' OR ', $roledata);
+                $join = " JOIN {badge_manual_award} bma ON bma.recipientid = u.id
+                          AND bma.badgeid = :badgeid{$this->badgeid} AND ({$extraon})";
+                $params["badgeid{$this->badgeid}"] = $this->badgeid;
+            }
+            return array($join, $where, $params);
+        } else {
+            foreach ($this->params as $param) {
+                $join .= " LEFT JOIN {badge_manual_award} bma{$param['role']} ON
+                          bma{$param['role']}.recipientid = u.id AND
+                          bma{$param['role']}.issuerrole = :issuerrole{$param['role']} ";
+                $where .= " AND bma{$param['role']}.issuerrole IS NOT NULL ";
+                $params["issuerrole{$param['role']}"] = $param['role'];
+            }
+            return array($join, $where, $params);
+        }
+    }
+
     /**
      * Delete this criterion
      *
index f04e3a4..32e8711 100644 (file)
@@ -86,9 +86,12 @@ class award_criteria_overall extends award_criteria {
      * Overall criteria review should be called only from other criteria handlers.
      *
      * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    public function review($userid) {
+    public function review($userid, $filtered = false) {
         global $DB;
 
         $sql = "SELECT bc.*, bcm.critid, bcm.userid, bcm.datemet
@@ -114,7 +117,7 @@ class award_criteria_overall extends award_criteria {
                     $overall = true;
                     continue;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if ($crit->datemet === null) {
                     $overall = false;
                     continue;
@@ -127,6 +130,16 @@ class award_criteria_overall extends award_criteria {
         return $overall;
     }
 
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        return array('', '', array());
+    }
+
     /**
      * Add appropriate criteria elements to the form
      *
index 9940e90..18915a0 100644 (file)
@@ -156,35 +156,86 @@ class award_criteria_profile extends award_criteria {
      * Review this criteria and decide if it has been completed
      *
      * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
      * @return bool Whether criteria is complete
      */
-    public function review($userid) {
+    public function review($userid, $filtered = false) {
         global $DB;
 
-        $overall = false;
+        // Users were already filtered by criteria completion, no checks required.
+        if ($filtered) {
+            return true;
+        }
+
+        $join = '';
+        $where = '';
+        $sqlparams = array();
+        $rule = ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) ? ' OR ' : ' AND ';
+
         foreach ($this->params as $param) {
             if (is_numeric($param['field'])) {
-                $crit = $DB->get_field('user_info_data', 'data', array('userid' => $userid, 'fieldid' => $param['field']));
+                $infodata[] = " uid.fieldid = :fieldid{$param['field']} ";
+                $sqlparams["fieldid{$param['field']}"] = $param['field'];
             } else {
-                $crit = $DB->get_field('user', $param['field'], array('id' => $userid));
+                $userdata[] = $DB->sql_isnotempty('u', "u.{$param['field']}", false, true);
             }
+        }
 
-            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
-                if (!$crit) {
-                    return false;
-                } else {
-                    $overall = true;
-                    continue;
-                }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
-                if (!$crit) {
-                    $overall = false;
-                    continue;
-                } else {
-                    return true;
-                }
-            }
+        // Add user custom field parameters if there are any.
+        if (!empty($infodata)) {
+            $extraon = implode($rule, $infodata);
+            $join = " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND ({$extraon})";
         }
+
+        // Add user table field parameters if there are any.
+        if (!empty($userdata)) {
+            $extraon = implode($rule, $userdata);
+            $where = " AND ({$extraon})";
+        }
+
+        $sqlparams['userid'] = $userid;
+        $sql = "SELECT u.* FROM {user} u " . $join . " WHERE u.id = :userid " . $where;
+        $overall = $DB->record_exists_sql($sql, $sqlparams);
+
         return $overall;
     }
+
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        global $DB;
+
+        $join = '';
+        $where = '';
+        $params = array();
+        $rule = ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) ? ' OR ' : ' AND ';
+
+        foreach ($this->params as $param) {
+            if (is_numeric($param['field'])) {
+                $infodata[] = " uid.fieldid = :fieldid{$param['field']} ";
+                $params["fieldid{$param['field']}"] = $param['field'];
+            } else {
+                $userdata[] = $DB->sql_isnotempty('u', "u.{$param['field']}", false, true);
+            }
+        }
+
+        // Add user custom fields if there are any.
+        if (!empty($infodata)) {
+            $extraon = implode($rule, $infodata);
+            $join = " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND ({$extraon})";
+        }
+
+        // Add user table fields if there are any.
+        if (!empty($userdata)) {
+            $extraon = implode($rule, $userdata);
+            $where = " AND ({$extraon})";
+        }
+        return array($join, $where, $params);
+    }
 }
index b70d377..97b3271 100644 (file)
@@ -292,9 +292,10 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
         $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
-        $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address'));
+        $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address', 'field_aim' => 'aim'));
 
         $this->user->address = 'Test address';
+        $this->user->aim = '999999999';
         $sink = $this->redirectEmails();
         user_update_user($this->user, false);
         $this->assertCount(1, $sink->get_messages());
diff --git a/badges/upgrade.txt b/badges/upgrade.txt
new file mode 100644 (file)
index 0000000..1a48ffd
--- /dev/null
@@ -0,0 +1,12 @@
+This files describes API changes in /badges/*,
+information provided here is intended especially for developers.
+
+=== 2.7 ===
+
+* get_completed_criteria_sql() - This method was added to award_criteria class and must be overriden
+  in all criteria classes. This method returns an array consisting of SQL JOIN statement, WHERE conditions,
+  and any parameters that might be required. The results are used in lib/badgeslib.php in review_all_criteria()
+  to reduce to the minimum the number of users to review and award badges.
+
+* New optional parameter $filtered in review() allows to indicate that some expensive checks can be skipped
+  if the list of users has been initially filtered based on met criteria.
index a591ad9..e720c18 100644 (file)
@@ -23,7 +23,7 @@ YUI.add('moodle-block_community-comments', function(Y) {
                         .append(Y.one('#commentoverlay-'+commentid+' .commenttitle').get('innerHTML')),
                     bodyContent:Y.one('#commentoverlay-'+commentid).get('innerHTML'),
                     visible: false, //by default it is not displayed
-                    lightbox : false,
+                    modal: false,
                     zIndex:100,
                     closeButtonTitle: this.get('closeButtonTitle')
                 });
index 7696f20..b48a204 100644 (file)
@@ -38,7 +38,7 @@ YUI.add('moodle-block_community-imagegallery', function(Y) {
                 headerContent:Y.one('#imagetitleoverlay').get('innerHTML'),
                 bodyContent:Y.one('#imageoverlay').get('innerHTML'),
                 visible: false, //by default it is not displayed
-                lightbox : false,
+                modal: false,
                 zIndex:100
             });
 
@@ -101,7 +101,7 @@ YUI.add('moodle-block_community-imagegallery', function(Y) {
                 + screennumber + ' / ' + this.imageidnumbers[imageid] + ' </h1></div>' + nextimagelink,
                 bodyContent:Y.one('#imageoverlay').get('innerHTML'),
                 visible: false, //by default it is not displayed
-                lightbox : false,
+                modal: false,
                 zIndex:100,
                 closeButtonTitle: this.get('closeButtonTitle')
             });
index d459982..d4b9f94 100644 (file)
@@ -128,8 +128,7 @@ class block_course_list extends block_list {
             $this->content->items[] = get_string('remotecourses','mnet');
             $this->content->icons[] = '';
             foreach ($courses as $course) {
-                $coursecontext = context_course::instance($course->id);
-                $this->content->items[]="<a title=\"" . format_string($course->shortname, true, array('context' => $coursecontext)) . "\" ".
+                $this->content->items[]="<a title=\"" . format_string($course->shortname, true) . "\" ".
                     "href=\"{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}\">"
                     .$icon. format_string(get_course_display_name_for_list($course)) . "</a>";
             }
index 7a116ee..cb86259 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js differ
index c48a7c3..e3f475d 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js differ
index 0ecd3b9..7e484e0 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js differ
index 7db6ca9..1ff285d 100644 (file)
@@ -648,7 +648,7 @@ BRANCH.prototype = {
         try {
             var object = Y.JSON.parse(outcome.responseText);
             if (object.error) {
-                Y.use('moodle-core-notification-ajaxException', function () {
+                Y.use('moodle-core-notification-ajaxexception', function () {
                     return new M.core.ajaxException(object).show();
                 });
                 return false;
index 4463e0a..e6e341a 100644 (file)
@@ -281,7 +281,7 @@ class cache_definition {
      * An array of identifiers provided to this cache when it was initialised.
      * @var array
      */
-    protected $identifiers = array();
+    protected $identifiers = null;
 
     /**
      * Key prefix for use with single key cache stores
@@ -654,6 +654,9 @@ class cache_definition {
      * @return array
      */
     public function get_identifiers() {
+        if (!isset($this->identifiers)) {
+            return array();
+        }
         return $this->identifiers;
     }
 
@@ -766,11 +769,22 @@ class cache_definition {
      * @throws coding_exception
      */
     public function set_identifiers(array $identifiers = array()) {
+        // If we are setting the exact same identifiers then just return as nothing really changed.
+        // We don't care about order as cache::make will use the same definition order all the time.
+        if ($identifiers === $this->identifiers) {
+            return;
+        }
+
         foreach ($this->requireidentifiers as $identifier) {
             if (!isset($identifiers[$identifier])) {
                 throw new coding_exception('Identifier required for cache has not been provided: '.$identifier);
             }
         }
+
+        if ($this->identifiers === null) {
+            // Initialize identifiers if they have not been.
+            $this->identifiers = array();
+        }
         foreach ($identifiers as $name => $value) {
             $this->identifiers[$name] = (string)$value;
         }
@@ -893,7 +907,7 @@ class cache_definition {
                 'area' => $this->area,
                 'siteidentifier' => $this->get_cache_identifier()
             );
-            if (!empty($this->identifiers)) {
+            if (isset($this->identifiers) && !empty($this->identifiers)) {
                 $identifiers = array();
                 foreach ($this->identifiers as $key => $value) {
                     $identifiers[] = htmlentities($key, ENT_QUOTES, 'UTF-8').'='.htmlentities($value, ENT_QUOTES, 'UTF-8');
index c71f108..1ee6e5e 100644 (file)
@@ -131,7 +131,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
 
         $this->connection = new Memcache;
         foreach ($this->servers as $server) {
-            $this->connection->addServer($server[0], $server[1], true, $server[2]);
+            $this->connection->addServer($server[0], (int) $server[1], true, (int) $server[2]);
         }
         // Test the connection to the pool of servers.
         $this->isready = @$this->connection->set($this->parse_key('ping'), 'ping', MEMCACHE_COMPRESSED, 1);
index e7f1631..451fa87 100644 (file)
@@ -584,22 +584,12 @@ $CFG->admin = 'admin';
 //=========================================================================
 // 11. BEHAT SUPPORT
 //=========================================================================
-// Behat needs a separate data directory and unique database prefix:
+// Behat test site needs a unique www root, data directory and database prefix:
 //
+// $CFG->behat_wwwroot = 'http://127.0.0.1/moodle';
 // $CFG->behat_prefix = 'bht_';
 // $CFG->behat_dataroot = '/home/example/bht_moodledata';
 //
-// To set a seperate wwwroot for Behat to use, use $CFG->behat_wwwroot; this is set automatically
-// to http://localhost:8000 as it is the proposed PHP built-in server URL. Instead of that you can,
-// for example, use an alias, add a host to /etc/hosts or add a new virtual host having a URL
-// poiting to your production site and another one poiting to your test site. Note that you need
-// to ensure that this URL is not accessible from the www as the behat test site uses "sugar"
-// credentials (admin/admin) and can be easily hackable.
-//
-// Example:
-//   $CFG->behat_wwwroot = 'http://192.168.1.250:8000';
-//   $CFG->behat_wwwroot = 'http://localhost/moodlesitetesting';
-//
 // You can override default Moodle configuration for Behat and add your own
 // params; here you can add more profiles, use different Mink drivers than Selenium...
 // These params would be merged with the default Moodle behat.yml, giving priority
@@ -644,16 +634,6 @@ $CFG->admin = 'admin';
 //       )
 //   );
 //
-// You can completely switch to test environment when "php admin/tool/behat/cli/util --enable",
-// this means that all the site accesses will be routed to the test environment instead of
-// the regular one, so NEVER USE THIS SETTING IN PRODUCTION SITES. This setting is useful
-// when working with cloud CI (continous integration) servers which requires public sites to run the
-// tests, or in testing/development installations when you are developing in a pre-PHP 5.4 server.
-// Note that with this setting enabled $CFG->behat_wwwroot is ignored and $CFG->behat_wwwroot
-// value will be the regular $CFG->wwwroot value.
-// Example:
-//   $CFG->behat_switchcompletely = true;
-//
 // You can force the browser session (not user's sessions) to restart after N seconds. This could
 // be useful if you are using a cloud-based service with time restrictions in the browser side.
 // Setting this value the browser session that Behat is using will be restarted. Set the time in
index 2f3ef23..d4e49b9 100644 (file)
@@ -43,15 +43,12 @@ define('COURSE_MAX_COURSES_PER_DROPDOWN', 1000);
 // Max users in log dropdown before switching to optional.
 define('COURSE_MAX_USERS_PER_DROPDOWN', 1000);
 define('FRONTPAGENEWS', '0');
-define('FRONTPAGECOURSELIST', '1'); // Not used. TODO MDL-38832 remove.
 define('FRONTPAGECATEGORYNAMES', '2');
-define('FRONTPAGETOPICONLY', '3'); // Not used. TODO MDL-38832 remove.
 define('FRONTPAGECATEGORYCOMBO', '4');
 define('FRONTPAGEENROLLEDCOURSELIST', '5');
 define('FRONTPAGEALLCOURSELIST', '6');
 define('FRONTPAGECOURSESEARCH', '7');
 // Important! Replaced with $CFG->frontpagecourselimit - maximum number of courses displayed on the frontpage.
-define('FRONTPAGECOURSELIMIT',    200); // TODO MDL-38832 remove.
 define('EXCELROWS', 65535);
 define('FIRSTUSEDEXCELROW', 3);
 
diff --git a/course/tests/behat/activities_edit_with_block_dock.feature b/course/tests/behat/activities_edit_with_block_dock.feature
new file mode 100644 (file)
index 0000000..89d1717
--- /dev/null
@@ -0,0 +1,29 @@
+@core @core_course
+Feature: Open the edit menu when a block is docked
+  In order to edit an activity with a block docked
+  As a teacher
+  I need to open the action menu
+
+  @javascript
+  Scenario: Open the action menu with a block docked
+    Given the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "courses" exists:
+      | fullname | shortname | format |
+      | Course 1 | C1 | weeks |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Glossary" to section "1" and I fill the form with:
+      | Name | Test glossary name |
+      | Description | Test glossary description |
+    And I dock "Navigation" block
+    When I open "Test glossary name" actions menu
+    Then "Test glossary name" actions menu should be open
+    And I reload the page
+    When I open "Test glossary name" actions menu
+    Then "Test glossary name" actions menu should be open
index d31c1b9..07b948f 100644 (file)
@@ -643,6 +643,29 @@ class behat_course extends behat_base {
         return new Given('I click on "a[role=\'menuitem\']" "css_element" in the "' . $this->escape($activityname) . '" activity');
     }
 
+    /**
+     * Checks that the specified activity's action menu is open.
+     *
+     * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should be open$/
+     * @throws DriverException The step is not available when Javascript is disabled
+     * @param string $activityname
+     */
+    public function actions_menu_should_be_open($activityname) {
+
+        if (!$this->running_javascript()) {
+            throw new DriverException('Activities actions menu not available when Javascript is disabled');
+        }
+
+        // If it is already closed we do nothing.
+        $activitynode = $this->get_activity_node($activityname);
+        $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
+        if (empty($classes['action-menu-shown'])) {
+            throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
+        }
+
+        return;
+    }
+
     /**
      * Indents to the right the activity or resource specified by it's name. Editing mode should be on.
      *
index 5430b6c..16b2ca0 100644 (file)
         $PAGE->set_button($buttons);
     }
 
-    $PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+    // If viewing a section, make the title more specific
+    if ($section and $section > 0 and course_format_uses_sections($course->format)) {
+        $sectionname = get_string('sectionname', "format_$course->format");
+        $sectiontitle = get_section_name($course, $section);
+        $PAGE->set_title(get_string('coursesectiontitle', 'moodle', array('course' => $course->fullname, 'sectiontitle' => $sectiontitle, 'sectionname' => $sectionname)));
+    } else {
+        $PAGE->set_title(get_string('coursetitle', 'moodle', array('course' => $course->fullname)));
+    }
+
     $PAGE->set_heading($course->fullname);
     echo $OUTPUT->header();
 
diff --git a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js
new file mode 100644 (file)
index 0000000..96126f3
Binary files /dev/null and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js differ
diff --git a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js
new file mode 100644 (file)
index 0000000..33f576f
Binary files /dev/null and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js differ
diff --git a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js
new file mode 100644 (file)
index 0000000..96126f3
Binary files /dev/null and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js differ
diff --git a/course/yui/modchooser/modchooser.js b/course/yui/modchooser/modchooser.js
deleted file mode 100644 (file)
index ce39f93..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-YUI.add('moodle-course-modchooser', function(Y) {
-    var CSS = {
-        PAGECONTENT : 'div#page-content',
-        SECTION : 'li.section',
-        SECTIONMODCHOOSER : 'span.section-modchooser-link',
-        SITEMENU : 'div.block_site_main_menu',
-        SITETOPIC : 'div.sitetopic'
-    };
-
-    var MODCHOOSERNAME = 'course-modchooser';
-
-    var MODCHOOSER = function() {
-        MODCHOOSER.superclass.constructor.apply(this, arguments);
-    }
-
-    Y.extend(MODCHOOSER, M.core.chooserdialogue, {
-        // The current section ID
-        sectionid : null,
-
-        // The hidden element holding the jump param
-        jumplink : null,
-
-        initializer : function(config) {
-            var dialogue = Y.one('.chooserdialoguebody');
-            var header = Y.one('.choosertitle');
-            var params = {};
-            this.setup_chooser_dialogue(dialogue, header, params);
-
-            // Initialize existing sections and register for dynamically created sections
-            this.setup_for_section();
-            M.course.coursebase.register_module(this);
-
-            // Catch the page toggle
-            Y.all('.block_settings #settingsnav .type_course .modchoosertoggle a').on('click', this.toggle_mod_chooser, this);
-        },
-        /**
-         * Update any section areas within the scope of the specified
-         * selector with AJAX equivalents
-         *
-         * @param baseselector The selector to limit scope to
-         * @return void
-         */
-        setup_for_section : function(baseselector) {
-            if (!baseselector) {
-                var baseselector = CSS.PAGECONTENT;
-            }
-
-            // Setup for site topics
-            Y.one(baseselector).all(CSS.SITETOPIC).each(function(section) {
-                this._setup_for_section(section);
-            }, this);
-
-            // Setup for standard course topics
-            Y.one(baseselector).all(CSS.SECTION).each(function(section) {
-                this._setup_for_section(section);
-            }, this);
-
-            // Setup for the block site menu
-            Y.one(baseselector).all(CSS.SITEMENU).each(function(section) {
-                this._setup_for_section(section);
-            }, this);
-        },
-        _setup_for_section : function(section, sectionid) {
-            var chooserspan = section.one(CSS.SECTIONMODCHOOSER);
-            if (!chooserspan) {
-                return;
-            }
-            var chooserlink = Y.Node.create("<a href='#' />");
-            chooserspan.get('children').each(function(node) {
-                chooserlink.appendChild(node);
-            });
-            chooserspan.insertBefore(chooserlink);
-            chooserlink.on('click', this.display_mod_chooser, this);
-        },
-        /**
-         * Display the module chooser
-         *
-         * @param e Event Triggering Event
-         * @param secitonid integer The ID of the section triggering the dialogue
-         * @return void
-         */
-        display_mod_chooser : function (e) {
-            // Set the section for this version of the dialogue
-            if (e.target.ancestor(CSS.SITETOPIC)) {
-                // The site topic has a sectionid of 1
-                this.sectionid = 1;
-            } else if (e.target.ancestor(CSS.SECTION)) {
-                var section = e.target.ancestor(CSS.SECTION);
-                this.sectionid = section.get('id').replace('section-', '');
-            } else if (e.target.ancestor(CSS.SITEMENU)) {
-                // The block site menu has a sectionid of 0
-                this.sectionid = 0;
-            }
-            this.display_chooser(e);
-        },
-        toggle_mod_chooser : function(e) {
-            // Get the add section link
-            var modchooserlinks = Y.all('div.addresourcemodchooser');
-
-            // Get the dropdowns
-            var dropdowns = Y.all('div.addresourcedropdown');
-
-            if (modchooserlinks.size() == 0) {
-                // Continue with non-js action if there are no modchoosers to add
-                return;
-            }
-
-            // We need to update the text and link
-            var togglelink = Y.one('.block_settings #settingsnav .type_course .modchoosertoggle a');
-
-            // The actual text is in the last child
-            var toggletext = togglelink.get('lastChild');
-
-            var usemodchooser;
-            // Determine whether they're currently hidden
-            if (modchooserlinks.item(0).hasClass('visibleifjs')) {
-                // The modchooser is currently visible, hide it
-                usemodchooser = 0;
-                modchooserlinks
-                    .removeClass('visibleifjs')
-                    .addClass('hiddenifjs');
-                dropdowns
-                    .addClass('visibleifjs')
-                    .removeClass('hiddenifjs');
-                toggletext.set('data', M.util.get_string('modchooserenable', 'moodle'));
-                togglelink.set('href', togglelink.get('href').replace('off', 'on'));
-            } else {
-                // The modchooser is currently not visible, show it
-                usemodchooser = 1;
-                modchooserlinks
-                    .addClass('visibleifjs')
-                    .removeClass('hiddenifjs');
-                dropdowns
-                    .removeClass('visibleifjs')
-                    .addClass('hiddenifjs');
-                toggletext.set('data', M.util.get_string('modchooserdisable', 'moodle'));
-                togglelink.set('href', togglelink.get('href').replace('on', 'off'));
-            }
-
-            M.util.set_user_preference('usemodchooser', usemodchooser);
-
-            // Prevent the page from reloading
-            e.preventDefault();
-        },
-        option_selected : function(thisoption) {
-            // Add the sectionid to the URL
-            this.jumplink.set('value', thisoption.get('value') + '&section=' + this.sectionid);
-        }
-    },
-    {
-        NAME : MODCHOOSERNAME,
-        ATTRS : {
-            maxheight : {
-                value : 800
-            }
-        }
-    });
-    M.course = M.course || {};
-    M.course.init_chooser = function(config) {
-        return new MODCHOOSER(config);
-    }
-},
-'@VERSION@', {
-    requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'moodle-course-coursebase']
-}
-);
diff --git a/course/yui/src/modchooser/build.json b/course/yui/src/modchooser/build.json
new file mode 100644 (file)
index 0000000..569ab7b
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "name": "moodle-course-modchooser",
+  "builds": {
+    "moodle-course-modchooser": {
+      "jsfiles": [
+        "modchooser.js"
+      ]
+    }
+  }
+}
diff --git a/course/yui/src/modchooser/js/modchooser.js b/course/yui/src/modchooser/js/modchooser.js
new file mode 100644 (file)
index 0000000..0bd9825
--- /dev/null
@@ -0,0 +1,173 @@
+/**
+ * The activity chooser dialogue for courses.
+ *
+ * @moodle-course-modchooser
+ */
+
+var CSS = {
+    PAGECONTENT : 'div#page-content',
+    SECTION : 'li.section',
+    SECTIONMODCHOOSER : 'span.section-modchooser-link',
+    SITEMENU : 'div.block_site_main_menu',
+    SITETOPIC : 'div.sitetopic'
+};
+
+var MODCHOOSERNAME = 'course-modchooser';
+
+/**
+ * The activity chooser dialogue for courses.
+ *
+ * @constructor
+ * @class M.course.modchooser
+ * @extends M.core.chooserdialogue
+ */
+var MODCHOOSER = function() {
+    MODCHOOSER.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(MODCHOOSER, M.core.chooserdialogue, {
+    // The current section ID
+    sectionid : null,
+
+    // The hidden element holding the jump param
+    jumplink : null,
+
+    initializer : function() {
+        var dialogue = Y.one('.chooserdialoguebody');
+        var header = Y.one('.choosertitle');
+        var params = {};
+        this.setup_chooser_dialogue(dialogue, header, params);
+
+        // Initialize existing sections and register for dynamically created sections
+        this.setup_for_section();
+        M.course.coursebase.register_module(this);
+
+        // Catch the page toggle
+        Y.all('.block_settings #settingsnav .type_course .modchoosertoggle a').on('click', this.toggle_mod_chooser, this);
+    },
+    /**
+     * Update any section areas within the scope of the specified
+     * selector with AJAX equivalents
+     *
+     * @param baseselector The selector to limit scope to
+     * @return void
+     */
+    setup_for_section : function(baseselector) {
+        if (!baseselector) {
+            baseselector = CSS.PAGECONTENT;
+        }
+
+        // Setup for site topics
+        Y.one(baseselector).all(CSS.SITETOPIC).each(function(section) {
+            this._setup_for_section(section);
+        }, this);
+
+        // Setup for standard course topics
+        Y.one(baseselector).all(CSS.SECTION).each(function(section) {
+            this._setup_for_section(section);
+        }, this);
+
+        // Setup for the block site menu
+        Y.one(baseselector).all(CSS.SITEMENU).each(function(section) {
+            this._setup_for_section(section);
+        }, this);
+    },
+    _setup_for_section : function(section) {
+        var chooserspan = section.one(CSS.SECTIONMODCHOOSER);
+        if (!chooserspan) {
+            return;
+        }
+        var chooserlink = Y.Node.create("<a href='#' />");
+        chooserspan.get('children').each(function(node) {
+            chooserlink.appendChild(node);
+        });
+        chooserspan.insertBefore(chooserlink);
+        chooserlink.on('click', this.display_mod_chooser, this);
+    },
+    /**
+        * Display the module chooser
+        *
+        * @param e Event Triggering Event
+        * @param secitonid integer The ID of the section triggering the dialogue
+        * @return void
+        */
+    display_mod_chooser : function (e) {
+        // Set the section for this version of the dialogue
+        if (e.target.ancestor(CSS.SITETOPIC)) {
+            // The site topic has a sectionid of 1
+            this.sectionid = 1;
+        } else if (e.target.ancestor(CSS.SECTION)) {
+            var section = e.target.ancestor(CSS.SECTION);
+            this.sectionid = section.get('id').replace('section-', '');
+        } else if (e.target.ancestor(CSS.SITEMENU)) {
+            // The block site menu has a sectionid of 0
+            this.sectionid = 0;
+        }
+        this.display_chooser(e);
+    },
+    toggle_mod_chooser : function(e) {
+        // Get the add section link
+        var modchooserlinks = Y.all('div.addresourcemodchooser');
+
+        // Get the dropdowns
+        var dropdowns = Y.all('div.addresourcedropdown');
+
+        if (modchooserlinks.size() === 0) {
+            // Continue with non-js action if there are no modchoosers to add
+            return;
+        }
+
+        // We need to update the text and link
+        var togglelink = Y.one('.block_settings #settingsnav .type_course .modchoosertoggle a');
+
+        // The actual text is in the last child
+        var toggletext = togglelink.get('lastChild');
+
+        var usemodchooser;
+        // Determine whether they're currently hidden
+        if (modchooserlinks.item(0).hasClass('visibleifjs')) {
+            // The modchooser is currently visible, hide it
+            usemodchooser = 0;
+            modchooserlinks
+                .removeClass('visibleifjs')
+                .addClass('hiddenifjs');
+            dropdowns
+                .addClass('visibleifjs')
+                .removeClass('hiddenifjs');
+            toggletext.set('data', M.util.get_string('modchooserenable', 'moodle'));
+            togglelink.set('href', togglelink.get('href').replace('off', 'on'));
+        } else {
+            // The modchooser is currently not visible, show it
+            usemodchooser = 1;
+            modchooserlinks
+                .addClass('visibleifjs')
+                .removeClass('hiddenifjs');
+            dropdowns
+                .removeClass('visibleifjs')
+                .addClass('hiddenifjs');
+            toggletext.set('data', M.util.get_string('modchooserdisable', 'moodle'));
+            togglelink.set('href', togglelink.get('href').replace('on', 'off'));
+        }
+
+        M.util.set_user_preference('usemodchooser', usemodchooser);
+
+        // Prevent the page from reloading
+        e.preventDefault();
+    },
+    option_selected : function(thisoption) {
+        // Add the sectionid to the URL
+        this.jumplink.set('value', thisoption.get('value') + '&section=' + this.sectionid);
+    }
+},
+{
+    NAME : MODCHOOSERNAME,
+    ATTRS : {
+        maxheight : {
+            value : 800
+        }
+    }
+});
+M.course = M.course || {};
+M.course.init_chooser = function(config) {
+    return new MODCHOOSER(config);
+};
diff --git a/course/yui/src/modchooser/meta/modchooser.json b/course/yui/src/modchooser/meta/modchooser.json
new file mode 100644 (file)
index 0000000..2ef461b
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "moodle-course-modchooser": {
+    "requires": [
+        "moodle-core-chooserdialogue",
+        "moodle-course-coursebase"
+    ]
+  }
+}
index 8362185..dee47d8 100644 (file)
@@ -156,11 +156,8 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             }
 
             this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url(collapsedimage, 'moodle'));
-            this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).once('click', function() {
-                // We want to do this just once, the first time the controls are shown.
-                this.populateStartDates();
-                this.populateDuration();
-            }, this);
+            this.populateStartDates();
+            this.populateDuration();
             this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).on('click', function(){
                 this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).toggleClass(CSS.ACTIVE);
                 this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEAREA).toggleClass(CSS.HIDDEN);
index 093a58a..cbde453 100644 (file)
@@ -113,7 +113,7 @@ YUI.add('moodle-enrol-rolemanager', function(Y) {
             var event = this.on('assignablerolesloaded', function(){
                 event.detach();
                 var s = M.str.role, confirmation = {
-                    lightbox :  true,
+                    modal:  true,
                     visible  :  true,
                     centered :  true,
                     title    :  s.confirmunassigntitle,
index c5beda0..0a18679 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js differ
index 605f8f5..5d6f83b 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js differ
index c5beda0..0a18679 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js differ
index efee206..3217f4a 100644 (file)
@@ -68,7 +68,7 @@ Y.extend(AUTOLINKER, Y.Base, {
                 for (key in data.entries) {
                     definition = data.entries[key].definition + data.entries[key].attachments;
                     alertpanel = new M.core.alert({title:data.entries[key].concept,
-                        message:definition, lightbox:false, yesLabel: M.util.get_string('ok', 'moodle')});
+                        message:definition, modal:false, yesLabel: M.util.get_string('ok', 'moodle')});
                     alertpanel.show();
                     Y.Node.one('#id_yuialertconfirm-' + alertpanel.get('COUNT')).focus();
                 }
index 290e7ed..538d03e 100644 (file)
@@ -132,8 +132,13 @@ class filter_urltolink extends moodle_text_filter {
         $querystring = '(?:\?(?:[\pL0-9\.!$&\'\(\)*+,;=_~:@/?-]|%[a-fA-F0-9]{2})*)';
         $fragment = '(?:\#(?:[\pL0-9\.!$&\'\(\)*+,;=_~:@/?-]|%[a-fA-F0-9]{2})*)';
 
-        $regex = "(?<!=[\"'])$urlstart((?:$domainsegment\.)+$domainsegment|$numericip)" .
-                "($port?$path$querystring?$fragment?)(?<![]),.;])";
+        // Lookbehind assertions.
+        // Is not HTML attribute or CSS URL property. Unfortunately legit text like "url(http://...)" will not be a link.
+        $lookbehindstart = "(?<!=[\"']|\burl\([\"' ]|\burl\()";
+        $lookbehindend = "(?<![]),.;])";
+
+        $regex = "$lookbehindstart$urlstart((?:$domainsegment\.)+$domainsegment|$numericip)" .
+                "($port?$path$querystring?$fragment?)$lookbehindend";
         if ($unicoderegexp) {
             $regex = '#' . $regex . '#ui';
         } else {
index e448d26..58bd4a3 100644 (file)
@@ -31,19 +31,6 @@ require_once($CFG->dirroot . '/filter/urltolink/filter.php'); // Include the cod
 
 class filter_urltolink_testcase extends basic_testcase {
 
-    /**
-     * Helper function that represents the legacy implementation
-     * of convert_urls_into_links()
-     */
-    protected function old_convert_urls_into_links(&$text) {
-        /// Make lone URLs into links.   eg http://moodle.com/
-        $text = preg_replace("%([[:space:]]|^|\(|\[)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])%i",
-            '$1<a href="$2://$3$4" target="_blank">$2://$3$4</a>', $text);
-        /// eg www.moodle.com
-        $text = preg_replace("%([[:space:]]|^|\(|\[)www\.([^[:space:]]*)([[:alnum:]#?/&=])%i",
-            '$1<a href="http://www.$2$3" target="_blank">www.$2$3</a>', $text);
-    }
-
     function get_convert_urls_into_links_test_cases() {
         $texts = array (
             //just a url
@@ -144,6 +131,11 @@ class filter_urltolink_testcase extends basic_testcase {
             '<td background="www.moodle.org">&nbsp;</td>' => '<td background="www.moodle.org">&nbsp;</td>',
             '<form name="input" action="http://moodle.org/submit.asp" method="get">'=>'<form name="input" action="http://moodle.org/submit.asp" method="get">',
             '<td background="https://www.moodle.org">&nbsp;</td>' => '<td background="https://www.moodle.org">&nbsp;</td>',
+            // CSS URLs.
+            '<table style="background-image: url(\'http://moodle.org/pic.jpg\');">' => '<table style="background-image: url(\'http://moodle.org/pic.jpg\');">',
+            '<table style="background-image: url(http://moodle.org/pic.jpg);">' => '<table style="background-image: url(http://moodle.org/pic.jpg);">',
+            '<table style="background-image: url("http://moodle.org/pic.jpg");">' => '<table style="background-image: url("http://moodle.org/pic.jpg");">',
+            '<table style="background-image: url( http://moodle.org/pic.jpg );">' => '<table style="background-image: url( http://moodle.org/pic.jpg );">',
             //partially escaped img tag
             'partially escaped img tag &lt;img src="http://moodle.org/logo/logo-240x60.gif" />' => 'partially escaped img tag &lt;img src="http://moodle.org/logo/logo-240x60.gif" />',
             //fully escaped img tag. Commented out as part of MDL-21183
@@ -181,32 +173,6 @@ class filter_urltolink_testcase extends basic_testcase {
         $this->assertEquals($correctresult, $text);
     }
 
-    function test_convert_urls_into_links_performance() {
-        $testablefilter = new testable_filter_urltolink();
-
-        $reps = 1000;
-        $text = file_get_contents(__DIR__ . '/fixtures/sample.txt');
-        $time_start = microtime(true);
-        for($i=0;$i<$reps;$i++) {
-            $testablefilter->convert_urls_into_links($text);
-        }
-        $time_end = microtime(true);
-        $new_time = $time_end - $time_start;
-
-        $time_start = microtime(true);
-        for($i=0;$i<$reps;$i++) {
-            $this->old_convert_urls_into_links($text);
-        }
-        $time_end = microtime(true);
-        $old_time = $time_end - $time_start;
-
-        $fast_enough = false;
-        if( $new_time < $old_time ) {
-            $fast_enough = true;
-        }
-
-        $this->assertEquals($fast_enough, true, 'Timing test: ' . $new_time . 'secs (new) < ' . $old_time . 'secs (old)');
-    }
 }
 
 
index 0c250a0..c7f4fc5 100644 (file)
@@ -60,10 +60,10 @@ if (function_exists('date_default_timezone_set') and function_exists('date_defau
 @ini_set('display_errors', '1');
 
 // Check that PHP is of a sufficient version.
-if (version_compare(phpversion(), '5.3.3') < 0) {
+if (version_compare(phpversion(), '5.4.4') < 0) {
     $phpversion = phpversion();
     // do NOT localise - lang strings would not work here and we CAN not move it after installib
-    echo "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).<br />";
+    echo "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).<br />";
     echo "Please upgrade your server software or install older Moodle version.";
     die;
 }
@@ -530,11 +530,10 @@ if ($config->stage == INSTALL_DATABASETYPE) {
 
 
 if ($config->stage == INSTALL_ENVIRONMENT or $config->stage == INSTALL_PATHS) {
-    $version_fail = (version_compare(phpversion(), "5.3.3") < 0);
     $curl_fail    = ($lang !== 'en' and !extension_loaded('curl')); // needed for lang pack download
     $zip_fail     = ($lang !== 'en' and !extension_loaded('zip'));  // needed for lang pack download
 
-    if ($version_fail or $curl_fail or $zip_fail) {
+    if ($curl_fail or $zip_fail) {
         $config->stage = INSTALL_ENVIRONMENT;
 
         install_print_header($config, get_string('environmenthead', 'install'),
@@ -542,10 +541,6 @@ if ($config->stage == INSTALL_ENVIRONMENT or $config->stage == INSTALL_PATHS) {
                                       get_string('environmentsub2', 'install'));
 
         echo '<div id="envresult"><dl>';
-        if ($version_fail) {
-            $a = (object)array('needed'=>'5.3.3', 'current'=>phpversion());
-            echo '<dt>'.get_string('phpversion', 'install').'</dt><dd>'.get_string('environmentrequireversion', 'admin', $a).'</dd>';
-        }
         if ($curl_fail) {
             echo '<dt>'.get_string('phpextension', 'install', 'cURL').'</dt><dd>'.get_string('environmentrequireinstall', 'admin').'</dd>';
         }
index 954c712..eba828b 100644 (file)
@@ -30,7 +30,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['cannotcreatedboninstall'] = '<p>Non è possibile creare il database </p> <p>Il database non esiste o l\'utente non è autorizzato a crearlo.</p>
+$string['cannotcreatedboninstall'] = '<p>Non è possibile creare il database </p> <p>Il database non esiste e l\'utente fornito non è autorizzato a crearlo.</p>
 <p>E\' necessario che l\'amministratore del sito  verifichi  la configurazione del database.</p>';
 $string['cannotcreatelangdir'] = 'Non è possibile creare la cartella lang';
 $string['cannotcreatetempdir'] = 'Non è possibile creare la cartella temp';
index f2f102a..0f537a4 100644 (file)
@@ -372,12 +372,14 @@ $string['courserequestsupport'] = 'Supporting information to help the administra
 $string['courserestore'] = 'Course restore';
 $string['courses'] = 'Courses';
 $string['coursesectionsummaries'] = 'Course section summaries';
+$string['coursesectiontitle'] = 'Course: {$a->course}, {$a->sectionname}: {$a->sectiontitle}';
 $string['coursesettings'] = 'Course default settings';
 $string['coursesmovedout'] = 'Courses moved out from {$a}';
 $string['coursespending'] = 'Courses pending approval';
 $string['coursestart'] = 'Course start';
 $string['coursesummary'] = 'Course summary';
 $string['coursesummary_help'] = 'The course summary is displayed in the list of courses. A course search searches course summary text in addition to course names.';
+$string['coursetitle'] = 'Course: {$a->course}';
 $string['courseupdates'] = 'Course updates';
 $string['create'] = 'Create';
 $string['createaccount'] = 'Create my new account';
index f035ae7..8e92faf 100644 (file)
@@ -5462,6 +5462,10 @@ abstract class context extends stdClass implements IteratorAggregate {
     public function delete() {
         global $DB;
 
+        if ($this->_contextlevel <= CONTEXT_SYSTEM) {
+            throw new coding_exception('Cannot delete system context');
+        }
+
         // double check the context still exists
         if (!$DB->record_exists('context', array('id'=>$this->_id))) {
             context::cache_remove($this);
@@ -5557,6 +5561,11 @@ abstract class context extends stdClass implements IteratorAggregate {
     public function get_child_contexts() {
         global $DB;
 
+        if (empty($this->_path) or empty($this->_depth)) {
+            debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
+            return array();
+        }
+
         $sql = "SELECT ctx.*
                   FROM {context} ctx
                  WHERE ctx.path LIKE ?";
@@ -6557,6 +6566,11 @@ class context_coursecat extends context {
     public function get_child_contexts() {
         global $DB;
 
+        if (empty($this->_path) or empty($this->_depth)) {
+            debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
+            return array();
+        }
+
         $sql = "SELECT ctx.*
                   FROM {context} ctx
                  WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)";
index d0c2695..11b5ef2 100644 (file)
                                die("Illegal path http:// or ftp://");
                }
                
-                       
-               // Initialize random number generator for randomizing cache flushes
-               // -- note Since PHP 4.2.0, the seed  becomes optional and defaults to a random value if omitted.
-                srand(((double)microtime())*1000000);
-               
                /**
                 * ADODB version as a string.
                 */
index aee9248..27ff1b5 100644 (file)
@@ -19,6 +19,6 @@ Added:
  * readme_moodle.txt - this file ;-)
 
 Our changes:
- * none
+ * Removed random seed initialization from lib/adodb/adodb.inc.php:172
 
 skodak, iarenaza, moodler, stronk7
index c57e6c0..c9f21f8 100644 (file)
@@ -67,3 +67,40 @@ function ajaxenabled(array $browsers = null) {
         return false;
     }
 }
+
+/**
+ * Starts capturing output whilst processing an AJAX request.
+ *
+ * This should be used in combination with ajax_check_captured_output to
+ * report any captured output to the user.
+ *
+ * @retrun Boolean Returns true on success or false on failure.
+ */
+function ajax_capture_output() {
+    // Start capturing output in case of broken plugins.
+    return ob_start();
+}
+
+/**
+ * Check captured output for content. If the site has a debug level of
+ * debugdeveloper set, and the content is non-empty, then throw a coding
+ * exception which can be captured by the Y.IO request and displayed to the
+ * user.
+ *
+ * @return Any output that was captured.
+ */
+function ajax_check_captured_output() {
+    global $CFG;
+
+    // Retrieve the output - there should be none.
+    $output = ob_get_contents();
+    ob_end_clean();
+
+    if ($CFG->debugdeveloper && !empty($output)) {
+        // Only throw an error if the site is in debugdeveloper.
+        throw new coding_exception('Unexpected output whilst processing AJAX request. ' .
+                'This could be caused by trailing whitespace. Output received: ' .
+                var_export($output, true));
+    }
+    return $output;
+}
index 9f7d940..459d1c3 100644 (file)
@@ -40,7 +40,7 @@ if ($branchtype !== navigation_node::TYPE_SITE_ADMIN) {
 }
 
 // Start capturing output in case of broken plugins.
-ob_start();
+ajax_capture_output();
 
 $PAGE->set_context(context_system::instance());
 $PAGE->set_url('/lib/ajax/getsiteadminbranch.php', array('type'=>$branchtype));
@@ -51,12 +51,5 @@ $sitenavigation = new settings_navigation_ajax($PAGE);
 $converter = new navigation_json();
 $branch = $sitenavigation->get('root');
 
-$output = ob_get_contents();
-ob_end_clean();
-if ($CFG->debugdeveloper && !empty($output)) {
-    throw new coding_exception('Unexpected output whilst building the administration tree. ' .
-            'This could be caused by trailing whitespace. Output received: ' .
-            var_export($output, true));
-} else {
-    echo $converter->convert($branch);
-}
+ajax_check_captured_output();
+echo $converter->convert($branch);
index 57c86fa..8d1bf9e 100644 (file)
@@ -429,51 +429,63 @@ class badge {
         core_php_time_limit::raise();
         raise_memory_limit(MEMORY_HUGE);
 
-        // For site level badges, get all active site users who can earn this badge and haven't got it yet.
-        if ($this->type == BADGE_TYPE_SITE) {
-            $sql = 'SELECT DISTINCT u.id, bi.badgeid
+        foreach ($this->criteria as $crit) {
+            // Overall criterion is decided when other criteria are reviewed.
+            if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) {
+                continue;
+            }
+
+            list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
+            // For site level badges, get all active site users who can earn this badge and haven't got it yet.
+            if ($this->type == BADGE_TYPE_SITE) {
+                $sql = "SELECT DISTINCT u.id, bi.badgeid
                         FROM {user} u
+                        {$extrajoin}
                         LEFT JOIN {badge_issued} bi
                             ON u.id = bi.userid AND bi.badgeid = :badgeid
-                        WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0';
-            $toearn = $DB->get_fieldset_sql($sql, array('badgeid' => $this->id, 'guestid' => $CFG->siteguest));
-        } else {
-            // For course level badges, get users who can earn this badge in the course.
-            // These are all enrolled users with capability moodle/badges:earnbadge.
-            $earned = $DB->get_fieldset_select('badge_issued', 'userid AS id', 'badgeid = :badgeid', array('badgeid' => $this->id));
-            $users = get_enrolled_users($this->get_context(), 'moodle/badges:earnbadge', 0, 'u.id');
-            $toearn = array_diff(array_keys($users), $earned);
-        }
+                        WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere;
+                $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams);
+                $toearn = $DB->get_fieldset_sql($sql, $params);
+            } else {
+                // For course level badges, get all users who already earned the badge in this course.
+                // Then find the ones who are enrolled in the course and don't have a badge yet.
+                $earned = $DB->get_fieldset_select('badge_issued', 'userid AS id', 'badgeid = :badgeid', array('badgeid' => $this->id));
+                $wheresql = '';
+                $earnedparams = array();
+                if (!empty($earned)) {
+                    list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false);
+                    $wheresql = ' WHERE u.id ' . $earnedsql;
+                }
+                list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
+                $sql = "SELECT u.id
+                        FROM {user} u
+                        {$extrajoin}
+                        JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
+                $params = array_merge($enrolledparams, $earnedparams, $extraparams);
+                $toearn = $DB->get_fieldset_sql($sql, $params);
+            }
 
-        foreach ($toearn as $uid) {
-            $toreview = false;
-            foreach ($this->criteria as $crit) {
-                if ($crit->criteriatype != BADGE_CRITERIA_TYPE_OVERALL) {
-                    if ($crit->review($uid)) {
-                        $crit->mark_complete($uid);
-                        if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
-                            $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
-                            $this->issue($uid);
-                            $awards++;
-                            break;
-                        } else {
-                            $toreview = true;
-                            continue;
-                        }
+            foreach ($toearn as $uid) {
+                $reviewoverall = false;
+                if ($crit->review($uid, true)) {
+                    $crit->mark_complete($uid);
+                    if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                        $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
+                        $this->issue($uid);
+                        $awards++;
                     } else {
-                        if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
-                            continue;
-                        } else {
-                            break;
-                        }
+                        $reviewoverall = true;
                     }
+                } else {
+                    // Will be reviewed some other time.
+                    $reviewoverall = false;
+                }
+                // Review overall if it is required.
+                if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
+                    $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
+                    $this->issue($uid);
+                    $awards++;
                 }
-            }
-            // Review overall if it is required.
-            if ($toreview && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
-                $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
-                $this->issue($uid);
-                $awards++;
             }
         }
 
index 20762ae..619dcd6 100644 (file)
@@ -100,18 +100,11 @@ class behat_command {
      * It checks behat dependencies have been installed and runs
      * the behat help command to ensure it works as expected
      *
-     * @param  bool $checkphp Extra check for the PHP version
      * @return int Error code or 0 if all ok
      */
-    public static function behat_setup_problem($checkphp = false) {
+    public static function behat_setup_problem() {
         global $CFG;
 
-        // We don't check the PHP version if $CFG->behat_switchcompletely has been enabled.
-        // Here we are in CLI.
-        if (empty($CFG->behat_switchcompletely) && empty($CFG->behat_wwwroot) && $checkphp && version_compare(PHP_VERSION, '5.4.0', '<')) {
-            behat_error(BEHAT_EXITCODE_REQUIREMENT, 'PHP 5.4 is required. See config-dist.php for possible alternatives');
-        }
-
         $clibehaterrorstr = "Behat dependencies not installed. Ensure you ran the composer installer. " . self::DOCS_URL . "#Installation\n";
 
         // Moodle setting.
@@ -148,7 +141,9 @@ class behat_command {
         }
 
         // Checking behat dataroot existence otherwise echo about admin/tool/behat/cli/init.php.
-        $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
+        if (!empty($CFG->behat_dataroot)) {
+            $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
+        }
         if (empty($CFG->behat_dataroot) || !is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) {
             self::output_msg(get_string('runclitool', 'tool_behat', 'php admin/tool/behat/cli/init.php'));
             return BEHAT_EXITCODE_CONFIG;
index d559ff4..a967cad 100644 (file)
@@ -183,6 +183,11 @@ class behat_config_manager {
         // We require here when we are sure behat dependencies are available.
         require_once($CFG->dirroot . '/vendor/autoload.php');
 
+        // It is possible that it has no value as we don't require a full behat setup to list the step definitions.
+        if (empty($CFG->behat_wwwroot)) {
+            $CFG->behat_wwwroot = 'http://itwillnotbeused.com';
+        }
+
         $basedir = $CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat';
         $config = array(
             'default' => array(
index c8504fe..ca16c26 100644 (file)
@@ -102,7 +102,10 @@ class behat_util extends testing_util {
 
         // Sets maximum debug level.
         set_config('debug', DEBUG_DEVELOPER);
-        set_config('debugdisplay', true);
+        set_config('debugdisplay', 1);
+
+        // Disable some settings that are not wanted on test sites.
+        set_config('noemailever', 1);
 
         // Keeps the current version of database and dataroot.
         self::store_versions_hash();
@@ -181,7 +184,7 @@ class behat_util extends testing_util {
      * features and steps definitions.
      *
      * Stores a file in dataroot/behat to allow Moodle to switch
-     * to the test environment when using cli-server (or $CFG->behat_switchcompletely)
+     * to the test environment when using cli-server.
      * @throws coding_exception
      * @return void
      */
@@ -193,7 +196,7 @@ class behat_util extends testing_util {
         }
 
         // Checks the behat set up and the PHP version.
-        if ($errorcode = behat_command::behat_setup_problem(true)) {
+        if ($errorcode = behat_command::behat_setup_problem()) {
             exit($errorcode);
         }
 
@@ -227,7 +230,7 @@ class behat_util extends testing_util {
         }
 
         // Checks the behat set up and the PHP version, returning an error code if something went wrong.
-        if ($errorcode = behat_command::behat_setup_problem(true)) {
+        if ($errorcode = behat_command::behat_setup_problem()) {
             return $errorcode;
         }
 
index 667125e..74d45cf 100644 (file)
@@ -153,9 +153,9 @@ function behat_clean_init_config() {
 
     $allowed = array_flip(array(
         'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
-        'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions',
-        'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass',
-        'theme'
+        'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
+        'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
+        'proxybypass', 'theme'
     ));
 
     // Add extra allowed settings.
@@ -180,56 +180,81 @@ function behat_clean_init_config() {
 function behat_check_config_vars() {
     global $CFG;
 
-    // CFG->behat_prefix must be set and with value different than CFG->prefix and phpunit_prefix.
-    if (empty($CFG->behat_prefix) ||
-           ($CFG->behat_prefix == $CFG->prefix) ||
-           (!empty($CFG->phpunit_prefix) && $CFG->behat_prefix == $CFG->phpunit_prefix)) {
+    // Verify prefix value.
+    if (empty($CFG->behat_prefix)) {
         behat_error(BEHAT_EXITCODE_CONFIG,
-            'Define $CFG->behat_prefix in config.php with a value different than $CFG->prefix and $CFG->phpunit_prefix');
+            'Define $CFG->behat_prefix in config.php');
     }
-
-    // $CFG->behat_wwwroot must be different than CFG->wwwroot if it is set, it may not be set as
-    // it can take the default value and we should also consider that will have the same value than
-    // $CFG->wwwroot if $CFG->behat_switchcompletely is set.
-    if (!empty($CFG->behat_wwwroot) && $CFG->behat_wwwroot == $CFG->wwwroot && empty($CFG->behat_switchcompletely)) {
+    if (!empty($CFG->prefix) and $CFG->behat_prefix == $CFG->prefix) {
+        behat_error(BEHAT_EXITCODE_CONFIG,
+            '$CFG->behat_prefix in config.php must be different from $CFG->prefix');
+    }
+    if (!empty($CFG->phpunit_prefix) and $CFG->behat_prefix == $CFG->phpunit_prefix) {
         behat_error(BEHAT_EXITCODE_CONFIG,
-            'Define $CFG->behat_wwwroot in config.php with a value different than $CFG->wwwroot');
+            '$CFG->behat_prefix in config.php must be different from $CFG->phpunit_prefix');
     }
 
-    // CFG->behat_dataroot must be set and with value different than CFG->dataroot and phpunit_dataroot.
-    $CFG->dataroot = realpath($CFG->dataroot);
-    if (!empty($CFG->behat_dataroot) && is_dir($CFG->behat_dataroot)) {
-        $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
+    // Verify behat wwwroot value.
+    if (empty($CFG->behat_wwwroot)) {
+        behat_error(BEHAT_EXITCODE_CONFIG,
+            'Define $CFG->behat_wwwroot in config.php');
     }
-    if (empty($CFG->behat_dataroot) ||
-           ($CFG->behat_dataroot == $CFG->dataroot) ||
-           (!empty($CFG->phpunit_dataroot) && is_dir($CFG->phpunit_dataroot)
-                && $CFG->behat_dataroot == realpath($CFG->phpunit_dataroot))) {
+    if (!empty($CFG->wwwroot) and $CFG->behat_wwwroot == $CFG->wwwroot) {
         behat_error(BEHAT_EXITCODE_CONFIG,
-            'Define $CFG->behat_dataroot in config.php with a value different than $CFG->dataroot and $CFG->phpunit_dataroot');
+            '$CFG->behat_wwwroot in config.php must be different from $CFG->wwwroot');
     }
 
+    // Verify behat dataroot value.
+    if (empty($CFG->behat_dataroot)) {
+        behat_error(BEHAT_EXITCODE_CONFIG,
+            'Define $CFG->behat_dataroot in config.php');
+    }
+    if (!file_exists($CFG->behat_dataroot)) {
+        $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
+        umask(0);
+        if (!mkdir($CFG->behat_dataroot, $permissions, true)) {
+            behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
+        }
+    }
+    $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
+    if (empty($CFG->behat_dataroot) or !is_dir($CFG->behat_dataroot) or !is_writable($CFG->behat_dataroot)) {
+        behat_error(BEHAT_EXITCODE_CONFIG,
+            '$CFG->behat_dataroot in config.php must point to an existing writable directory');
+    }
+    if (!empty($CFG->dataroot) and $CFG->behat_dataroot == realpath($CFG->dataroot)) {
+        behat_error(BEHAT_EXITCODE_CONFIG,
+            '$CFG->behat_dataroot in config.php must be different from $CFG->dataroot');
+    }
+    if (!empty($CFG->phpunit_dataroot) and $CFG->behat_dataroot == realpath($CFG->phpunit_dataroot)) {
+        behat_error(BEHAT_EXITCODE_CONFIG,
+            '$CFG->behat_dataroot in config.php must be different from $CFG->phpunit_dataroot');
+    }
 }
 
 /**
- * Returns a URL based on the priorities between $CFG->behat_* vars.
- *
- * 1.- Switch completely wins and overwrites behat_wwwroot
- * 2.- behat_wwwroot alternatively
- * 3.- http://localhost:8000 if there is nothing else
- *
- * @return string
+ * Should we switch to the test site data?
+ * @return bool
  */
-function behat_get_wwwroot() {
+function behat_is_test_site() {
     global $CFG;
 
-    if (!empty($CFG->behat_switchcompletely)) {
-        return $CFG->wwwroot;
-    } else if (!empty($CFG->behat_wwwroot)) {
-        return $CFG->behat_wwwroot;
+    if (defined('BEHAT_UTIL')) {
+        // This is the admin tool that installs/drops the test site install.
+        return true;
+    }
+    if (defined('BEHAT_TEST')) {
+        // This is the main vendor/bin/behat script.
+        return true;
+    }
+    if (empty($CFG->behat_wwwroot)) {
+        return false;
+    }
+    if (isset($_SERVER['REMOTE_ADDR']) and behat_is_requested_url($CFG->behat_wwwroot)) {
+        // Something is accessing the web server like a real browser.
+        return true;
     }
 
-    return 'http://localhost:8000';
+    return false;
 }
 
 /**
index 18a0e19..49830c7 100644 (file)
@@ -66,7 +66,6 @@ function cron_run() {
     // Run cleanup core cron jobs, but not every time since they aren't too important.
     // These don't have a timer to reduce load, so we'll use a random number
     // to randomly choose the percentage of times we should run these jobs.
-    srand ((double) microtime() * 10000000);
     $random100 = rand(0,100);
     if ($random100 < 20) {     // Approximately 20% of the time.
         mtrace("Running clean-up tasks...");
index 7644a23..d5e63a9 100644 (file)
@@ -1545,6 +1545,9 @@ class core_ddl_testcase extends database_driver_testcase {
         $this->assertSame($records[1]->secondname, $this->records['test_table1'][0]->secondname);
         $this->assertSame($records[2]->intro, $this->records['test_table1'][1]->intro);
 
+        // Collect statistics about the data in the temp table.
+        $DB->update_temp_table_stats();
+
         // Drop table1.
         $dbman->drop_table($table1);
         $this->assertFalse($dbman->table_exists('test_table1'));
@@ -1558,6 +1561,9 @@ class core_ddl_testcase extends database_driver_testcase {
             $this->assertInstanceOf('ddl_table_missing_exception', $e);
         }
 
+        // Collect statistics about the data in the temp table with less tables.
+        $DB->update_temp_table_stats();
+
         // Fill/modify/delete a few table0 records.
 
         // Drop table0.
index de17fc8..4828dcf 100644 (file)
@@ -2172,6 +2172,15 @@ abstract class moodle_database {
         }
     }
 
+    /**
+     * Analyze the data in temporary tables to force statistics collection after bulk data loads.
+     *
+     * @return void
+     */
+    public function update_temp_table_stats() {
+        $this->temptables->update_stats();
+    }
+
     /**
      * Checks and returns true if transactions are supported.
      *
index 19f0c20..8a586fe 100644 (file)
@@ -22,6 +22,7 @@
  *
  *   - databases not retrieving temp tables from information schema tables (mysql)
  *   - databases using a different name schema for temp tables (like mssql).
+ *   - databases that don't collect planner stats for temp tables (like PgSQL).
  *
  * Basically it works as a simple store of created temporary tables, providing
  * some simple getters/setters methods. Each database can extend it for its own
@@ -31,9 +32,6 @@
  * and the sql_generator, so both are able to use its facilities, with the final goal
  * of doing temporary tables support 100% cross-db and transparent within the DB API.
  *
- * Only drivers needing it will use this store. Neither moodle_database (abstract) or
- * databases like postgres need this, because they don't lack any temp functionality.
- *
  * @package    core_dml
  * @copyright  2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -119,6 +117,17 @@ class moodle_temptables {
         return null;
     }
 
+    /**
+     * Analyze the data in temporary tables to force statistics collection after bulk data loads.
+     * The database class detects all temporary tables and will automatically analyze all created tables
+     *
+     * @return void
+     */
+    public function update_stats() {
+        // By default most databases do automatic on temporary tables, PgSQL does not.
+        // As a result, update_stats call immediately return for non-interesting database types.
+    }
+
     /**
      * Dispose the temptables stuff, checking for wrong situations, informing and recovering from them
      */
index cc682e5..70bd6f3 100644 (file)
@@ -903,6 +903,9 @@ class mssql_native_moodle_database extends moodle_database {
         $dataobject = (array)$dataobject;
 
         $columns = $this->get_columns($table);
+        if (empty($columns)) {
+            throw new dml_exception('ddltablenotexist', $table);
+        }
         $cleaned = array();
 
         foreach ($dataobject as $field => $value) {
index 33d07b9..34320d9 100644 (file)
@@ -1106,6 +1106,10 @@ class mysqli_native_moodle_database extends moodle_database {
         $dataobject = (array)$dataobject;
 
         $columns = $this->get_columns($table);
+        if (empty($columns)) {
+            throw new dml_exception('ddltablenotexist', $table);
+        }
+
         $cleaned = array();
 
         foreach ($dataobject as $field=>$value) {
index 502a266..0c6f700 100644 (file)
@@ -1256,6 +1256,10 @@ class oci_native_moodle_database extends moodle_database {
         $dataobject = (array)$dataobject;
 
         $columns = $this->get_columns($table);
+        if (empty($columns)) {
+            throw new dml_exception('ddltablenotexist', $table);
+        }
+
         $cleaned = array();
 
         foreach ($dataobject as $field=>$value) {
index 58874bd..53fd8d5 100644 (file)
@@ -386,6 +386,10 @@ abstract class pdo_moodle_database extends moodle_database {
         $dataobject = (array)$dataobject;
 
         $columns = $this->get_columns($table);
+        if (empty($columns)) {
+            throw new dml_exception('ddltablenotexist', $table);
+        }
+
         $cleaned = array();
 
         foreach ($dataobject as $field=>$value) {
index f4cf21d..5c369a7 100644 (file)
@@ -876,6 +876,10 @@ class pgsql_native_moodle_database extends moodle_database {
         $dataobject = (array)$dataobject;
 
         $columns = $this->get_columns($table);
+        if (empty($columns)) {
+            throw new dml_exception('ddltablenotexist', $table);
+        }
+
         $cleaned = array();
         $blobs   = array();
 
index cd87ef2..4301fe2 100644 (file)
@@ -29,5 +29,16 @@ defined('MOODLE_INTERNAL') || die();
 require_once(__DIR__.'/moodle_temptables.php');
 
 class pgsql_native_moodle_temptables extends moodle_temptables {
-    // I love these classes :-P
+    /**
+     * Analyze the data in temporary tables to force statistics collection after bulk data loads.
+     * PostgreSQL does not natively support automatic temporary table stats collection, so we do it.
+     *
+     * @return void
+     */
+    public function update_stats() {
+        $temptables = $this->get_temptables();
+        foreach ($temptables as $temptablename) {
+            $this->mdb->execute("ANALYZE {".$temptablename."}");
+        }
+    }
 }
index 093cda6..6f12524 100644 (file)
@@ -979,6 +979,10 @@ class sqlsrv_native_moodle_database extends moodle_database {
         $dataobject = (array)$dataobject;
 
         $columns = $this->get_columns($table);
+        if (empty($columns)) {
+            throw new dml_exception('ddltablenotexist', $table);
+        }
+
         $cleaned = array ();
 
         foreach ($dataobject as $field => $value) {
index d44714e..ceed3db 100644 (file)
@@ -2253,6 +2253,14 @@ class core_dml_testcase extends database_driver_testcase {
         } catch (moodle_exception $e) {
             $this->assertInstanceOf('dml_exception', $e);
         }
+
+        // Try to insert a record into a non-existent table. dml_exception expected.
+        try {
+            $DB->insert_record('nonexistenttable', $record, true);
+            $this->fail("Expecting an exception, none occurred");
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof dml_exception);
+        }
     }
 
     public function test_import_record() {
index 1193086..2e62463 100644 (file)
@@ -109,7 +109,7 @@ class MoodleQuickForm_url extends HTML_QuickForm_text{
         if (count($options->repositories) > 0) {
             $straddlink = get_string('choosealink', 'repository');
             $str .= <<<EOD
-<button id="filepicker-button-{$client_id}" style="display:none">
+<button id="filepicker-button-{$client_id}" class="visibleifjs">
 $straddlink
 </button>
 EOD;
@@ -120,7 +120,6 @@ EOD;
 
         $module = array('name'=>'form_url', 'fullpath'=>'/lib/form/url.js', 'requires'=>array('core_filepicker'));
         $PAGE->requires->js_init_call('M.form_url.init', array($options), true, $module);
-        $PAGE->requires->js_function_call('show_item', array('filepicker-button-'.$client_id));
 
         return $str;
     }
index 190ab09..d5a9e6d 100644 (file)
@@ -967,10 +967,20 @@ class grade_category extends grade_object {
      *
      * @return bool True if extra credit used
      */
-    function is_extracredit_used() {
-        return ($this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2
-             or $this->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
-             or $this->aggregation == GRADE_AGGREGATE_SUM);
+    public function is_extracredit_used() {
+        return self::aggregation_uses_extracredit($this->aggregation);
+    }
+
+    /**
+     * Returns true if aggregation passed is using extracredit.
+     *
+     * @param int $aggregation Aggregation const.
+     * @return bool True if extra credit used
+     */
+    public static function aggregation_uses_extracredit($aggregation) {
+        return ($aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2
+             or $aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
+             or $aggregation == GRADE_AGGREGATE_SUM);
     }
 
     /**
@@ -979,10 +989,21 @@ class grade_category extends grade_object {
      * @return bool True if an aggregation coefficient is being used
      */
     public function is_aggregationcoef_used() {
-        return ($this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN
-             or $this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2
-             or $this->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
-             or $this->aggregation == GRADE_AGGREGATE_SUM);
+        return self::aggregation_uses_aggregationcoef($this->aggregation);
+
+    }
+
+    /**
+     * Returns true if aggregation uses aggregationcoef
+     *
+     * @param int $aggregation Aggregation const.
+     * @return bool True if an aggregation coefficient is being used
+     */
+    public static function aggregation_uses_aggregationcoef($aggregation) {
+        return ($aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN
+             or $aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2
+             or $aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
+             or $aggregation == GRADE_AGGREGATE_SUM);
 
     }
 
@@ -1573,14 +1594,14 @@ class grade_category extends grade_object {
 
             //weight and extra credit share a column :( Would like a default of 1 for weight and 0 for extra credit
             //Flip from the default of 0 to 1 (or vice versa) if ALL items in the category are still set to the old default.
-            if ($params->aggregation==GRADE_AGGREGATE_WEIGHTED_MEAN || $params->aggregation==GRADE_AGGREGATE_EXTRACREDIT_MEAN) {
+            if (self::aggregation_uses_aggregationcoef($params->aggregation)) {
                 $sql = $defaultaggregationcoef = null;
 
-                if ($params->aggregation==GRADE_AGGREGATE_WEIGHTED_MEAN) {
+                if (!self::aggregation_uses_extracredit($params->aggregation)) {
                     //if all items in this category have aggregation coefficient of 0 we can change it to 1 ie evenly weighted
                     $sql = "select count(id) from {grade_items} where categoryid=:categoryid and aggregationcoef!=0";
                     $defaultaggregationcoef = 1;
-                } else if ($params->aggregation==GRADE_AGGREGATE_EXTRACREDIT_MEAN) {
+                } else {
                     //if all items in this category have aggregation coefficient of 1 we can change it to 0 ie no extra credit
                     $sql = "select count(id) from {grade_items} where categoryid=:categoryid and aggregationcoef!=1";
                     $defaultaggregationcoef = 0;
index d4ce639..5451609 100644 (file)
@@ -41,6 +41,7 @@ class core_grade_category_testcase extends grade_base_testcase {
         $this->sub_test_grade_category_aggregate_grades();
         $this->sub_test_grade_category_apply_limit_rules();
         $this->sub_test_grade_category_is_aggregationcoef_used();
+        $this->sub_test_grade_category_aggregation_uses_aggregationcoef();
         $this->sub_test_grade_category_fetch_course_tree();
         $this->sub_test_grade_category_get_children();
         $this->sub_test_grade_category_load_grade_item();
@@ -67,6 +68,8 @@ class core_grade_category_testcase extends grade_base_testcase {
 
         // Do this last as adding a second course category messes up the data.
         $this->sub_test_grade_category_insert_course_category();
+        $this->sub_test_grade_category_is_extracredit_used();
+        $this->sub_test_grade_category_aggregation_uses_extracredit();
     }
 
     // Adds 3 new grade categories at various depths.
@@ -531,11 +534,43 @@ class core_grade_category_testcase extends grade_base_testcase {
 
     }
 
-    /**
-     * TODO implement
-     */
     protected function sub_test_grade_category_is_aggregationcoef_used() {
+        $category = new grade_category();
+        // Following use aggregationcoef.
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
+        $this->assertTrue($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
+        $this->assertTrue($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
+        $this->assertTrue($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_SUM;
+        $this->assertTrue($category->is_aggregationcoef_used());
 
+        // Following don't use aggregationcoef.
+        $category->aggregation = GRADE_AGGREGATE_MAX;
+        $this->assertFalse($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_MEAN;
+        $this->assertFalse($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_MEDIAN;
+        $this->assertFalse($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_MIN;
+        $this->assertFalse($category->is_aggregationcoef_used());
+        $category->aggregation = GRADE_AGGREGATE_MODE;
+        $this->assertFalse($category->is_aggregationcoef_used());
+    }
+
+    protected function sub_test_grade_category_aggregation_uses_aggregationcoef() {
+
+        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN));
+        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN2));
+        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_EXTRACREDIT_MEAN));
+        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_SUM));
+
+        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MAX));
+        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEAN));
+        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEDIAN));
+        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MIN));
+        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MODE));
     }
 
     protected function sub_test_grade_category_fetch_course_tree() {
@@ -721,4 +756,43 @@ class core_grade_category_testcase extends grade_base_testcase {
         $grade->insert();
         return $grade->rawgrade;
     }
+
+    protected function sub_test_grade_category_is_extracredit_used() {
+        $category = new grade_category();
+        // Following use aggregationcoef.
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
+        $this->assertTrue($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
+        $this->assertTrue($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_SUM;
+        $this->assertTrue($category->is_extracredit_used());
+
+        // Following don't use aggregationcoef.
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
+        $this->assertFalse($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_MAX;
+        $this->assertFalse($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_MEAN;
+        $this->assertFalse($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_MEDIAN;
+        $this->assertFalse($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_MIN;
+        $this->assertFalse($category->is_extracredit_used());
+        $category->aggregation = GRADE_AGGREGATE_MODE;
+        $this->assertFalse($category->is_extracredit_used());
+    }
+
+    protected function sub_test_grade_category_aggregation_uses_extracredit() {
+
+        $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN2));
+        $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_EXTRACREDIT_MEAN));
+        $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_SUM));
+
+        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN));
+        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MAX));
+        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEAN));
+        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEDIAN));
+        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MIN));
+        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MODE));
+    }
 }
index df36acd..8fa85e9 100644 (file)
@@ -379,105 +379,6 @@ M.util.init_maximised_embed = function(Y, id) {
     };
 };
 
-/**
- * Attach handler to single_select
- *
- * This code was deprecated in Moodle 2.4 and will be removed in Moodle 2.6
- *
- * Please see lib/yui/formautosubmit/formautosubmit.js for its replacement
- */
-M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
-    if (M.cfg.developerdebug) {
-        Y.log("You are using a deprecated function call (M.util.init_select_autosubmit). Please look at rewriting your call to use moodle-core-formautosubmit");
-    }
-    Y.use('event-key', function() {
-        var select = Y.one('#'+selectid);
-        if (select) {
-            // Try to get the form by id
-            var form = Y.one('#'+formid) || (function(){
-                // Hmmm the form's id may have been overriden by an internal input
-                // with the name id which will KILL IE.
-                // We need to manually iterate at this point because if the case
-                // above is true YUI's ancestor method will also kill IE!
-                var form = select;
-                while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
-                    form = form.ancestor();
-                }
-                return form;
-            })();
-            // Make sure we have the form
-            if (form) {
-                var buttonflag = 0;
-                // Create a function to handle our change event
-                var processchange = function(e, paramobject) {
-                    if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
-                        // chrome doesn't pick up on a click when selecting an element in a select menu, so we use
-                        // the on change event to fire this function. This just checks to see if a button was
-                        // first pressed before redirecting to the appropriate page.
-                        if (Y.UA.os == 'windows' && Y.UA.chrome){
-                            if (buttonflag == 1) {
-                                buttonflag = 0;
-                                this.submit();
-                            }
-                        } else {
-                            this.submit();
-                        }
-                    }
-                    if (e.button == 1) {
-                        buttonflag = 1;
-                    }
-                    paramobject.lastindex = select.get('selectedIndex');
-                };
-
-                var changedown = function(e, paramobject) {
-                    if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
-                        if(e.keyCode == 13) {
-                            form.submit();
-                        }
-                        paramobject.lastindex = select.get('selectedIndex');
-                    }
-                }
-
-                var paramobject = new Object();
-                paramobject.lastindex = select.get('selectedIndex');
-                paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
-                // Bad hack to circumvent problems with different browsers on different systems.
-                if (Y.UA.os == 'macintosh') {
-                    if(Y.UA.webkit) {
-                        paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
-                    }
-                    paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
-                } else {
-                    if(Y.UA.os == 'windows' && Y.UA.chrome) {
-                        paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
-                    }
-                    paramobject.eventkeypress = Y.on('keydown', changedown, select, '', form, paramobject);
-                }
-            }
-        }
-    });
-};
-
-/**
- * Attach handler to url_select
- * Deprecated from 2.4 onwards.
- * Please use @see init_select_autosubmit() for redirecting to a url (above).
- * This function has accessability issues and also does not use the formid passed through as a parameter.
- */
-M.util.init_url_select = function(Y, formid, selectid, nothing) {
-    if (M.cfg.developerdebug) {
-        Y.log("You are using a deprecated function call (M.util.init_url_select). Please look at rewriting your call to use moodle-core-formautosubmit");
-    }
-    YUI().use('node', function(Y) {
-        Y.on('change', function() {
-            if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
-                window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
-            }
-        },
-        '#'+selectid);
-    });
-};
-
 /**
  * Breaks out all links to the top frame - used in frametop page layout.
  */
@@ -1101,8 +1002,14 @@ function findParentNode(el, elName, elClass, elId) {
     specified tag name, class, and id. All conditions must be met,
     but any can be ommitted.
     Doesn't examine children of matches.
+
+    @deprecated since Moodle 2.7 - please do not use this function any more.
+    @todo MDL-43242 This will be deleted in Moodle 2.9.
+    @see Y.all
 */
 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
+    Y.log("findChildNodes() is deprecated. Please use Y.all instead.",
+            "warn", "javascript-static.js");
     var children = new Array();
     for (var i = 0; i < start.childNodes.length; i++) {
         var classfound = false;
@@ -1252,8 +1159,11 @@ function insertAtCursor(myField, myValue) {
         Call instead of setting window.onload directly or setting body onload=.
         Adds your function to a chain of functions rather than overwriting anything
         that exists.
+        @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
 */
 function addonload(fn) {
+    Y.log('addonload has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
+            'warn', 'javascript-static.js');
     var oldhandler=window.onload;
     window.onload=function() {
         if(oldhandler) oldhandler();
@@ -1274,8 +1184,11 @@ function addonload(fn) {
  *                    document, use `document`.
  * @param {String} strTagName filter by tag names
  * @param {String} name same as HTML5 spec
+ * @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
  */
 function getElementsByClassName(oElm, strTagName, name) {
+    Y.log('getElementsByClassName has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
+            'warn', 'javascript-static.js');
     // for backwards compatibility
     if(typeof name == "object") {
         var names = new Array();
@@ -1447,19 +1360,24 @@ function close_window(e) {
 
 /**
  * Used in a couple of modules to hide navigation areas when using AJAX
+ * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
  */
-
 function show_item(itemid) {
-    var item = document.getElementById(itemid);
+    Y.log('show_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
+            'warn', 'javascript-static.js');
+    var item = Y.one('#' + itemid);
     if (item) {
-        item.style.display = "";
+        item.show();
     }
 }
 
+// Deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
 function destroy_item(itemid) {
-    var item = document.getElementById(itemid);
+    Y.log('destroy_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
+            'warn', 'javascript-static.js');
+    var item = Y.one('#' + itemid);
     if (item) {
-        item.parentNode.removeChild(item);
+        item.remove(true);
     }
 }
 /**
@@ -1552,12 +1470,14 @@ function update_progress_bar (id, width, pt, msg, es){
 
 /**
  * Used in a couple of modules to hide navigation areas when using AJAX
+ * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
  */
 function hide_item(itemid) {
-    // use class='hiddenifjs' instead
-    var item = document.getElementById(itemid);
+    Y.log('hide_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
+            'warn', 'javascript-static.js');
+    var item = Y.one('#' + itemid);
     if (item) {
-        item.style.display = "none";
+        item.hide();
     }
 }
 
@@ -1597,30 +1517,6 @@ M.util.help_popups = {
     }
 }
 
-/**
- * This code bas been deprecated and will be removed from Moodle 2.7
- *
- * Please see lib/yui/popuphelp/popuphelp.js for its replacement
- */
-M.util.help_icon = {
-    initialised : false,
-    setup : function(Y, properties) {
-        this.add(Y, properties);
-    },
-    add : function(Y) {
-        if (M.cfg.developerdebug) {
-            Y.log("You are using a deprecated function call (M.util.help_icon.add). " +
-                    "Please look at rewriting your call to support lib/yui/popuphelp/popuphelp.js");
-        }
-        if (!this.initialised) {
-            YUI().use('moodle-core-popuphelp', function() {
-                M.core.init_popuphelp([]);
-            });
-        }
-        this.initialised = true;
-    }
-};
-
 /**
  * Custom menu namespace
  */
index e001185..9c136f3 100644 (file)
@@ -817,7 +817,7 @@ class core_media_player_qt extends core_media_player {
         <param name="pluginspage" value="http://www.apple.com/quicktime/download/" />
         <param name="src" value="$url" />
         <param name="controller" value="true" />
-        <param name="loop" value="true" />
+        <param name="loop" value="false" />
         <param name="autoplay" value="false" />
         <param name="autostart" value="false" />
         <param name="scale" value="aspect" />
@@ -826,7 +826,7 @@ class core_media_player_qt extends core_media_player {
             <param name="src" value="$url" />
             <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
             <param name="controller" value="true" />
-            <param name="loop" value="true" />
+            <param name="loop" value="false" />
             <param name="autoplay" value="false" />
             <param name="autostart" value="false" />
             <param name="scale" value="aspect" />
@@ -930,7 +930,7 @@ class core_media_player_swf extends core_media_player {
   <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$width" height="$height">
     <param name="movie" value="$url" />
     <param name="autoplay" value="true" />
-    <param name="loop" value="true" />
+    <param name="loop" value="false" />
     <param name="controller" value="true" />
     <param name="scale" value="aspect" />
     <param name="base" value="." />
@@ -939,7 +939,7 @@ class core_media_player_swf extends core_media_player {
     <object type="application/x-shockwave-flash" data="$url" width="$width" height="$height">
       <param name="controller" value="true" />
       <param name="autoplay" value="true" />
-      <param name="loop" value="true" />
+      <param name="loop" value="false" />
       <param name="scale" value="aspect" />
       <param name="base" value="." />
       <param name="allowscriptaccess" value="never" />
index 6f53dcf..e69b3a4 100644 (file)
@@ -5696,6 +5696,11 @@ function email_to_user($user, $from, $subject, $messagetext, $messagehtml = '',
         return false;
     }
 
+    if (defined('BEHAT_SITE_RUNNING')) {
+        // Fake email sending in behat.
+        return true;
+    }
+
     if (!empty($CFG->noemailever)) {
         // Hidden setting for development sites, set in config.php if needed.
         debugging('Not sending email due to $CFG->noemailever config setting', DEBUG_NORMAL);
@@ -7650,7 +7655,6 @@ function random_string ($length=15) {
     $pool .= 'abcdefghijklmnopqrstuvwxyz';
     $pool .= '0123456789';
     $poollen = strlen($pool);
-    mt_srand ((double) microtime() * 1000000);
     $string = '';
     for ($i = 0; $i < $length; $i++) {
         $string .= substr($pool, (mt_rand()%($poollen)), 1);
@@ -7671,7 +7675,6 @@ function complex_random_string($length=null) {
     $pool  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
     $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
     $poollen = strlen($pool);
-    mt_srand ((double) microtime() * 1000000);
     if ($length===null) {
         $length = floor(rand(24, 32));
     }
@@ -7971,7 +7974,6 @@ function unformat_float($localefloat, $strict = false) {
  */
 function swapshuffle($array) {
 
-    srand ((double) microtime() * 10000000);
     $last = count($array) - 1;
     for ($i = 0; $i <= $last; $i++) {
         $from = rand(0, $last);
@@ -8011,7 +8013,6 @@ function swapshuffle_assoc($array) {
  * @return array
  */
 function draw_rand_array($array, $draws) {
-    srand ((double) microtime() * 10000000);
 
     $return = array();
 
index 24a3f56..fa07895 100644 (file)
@@ -240,6 +240,7 @@ class page_requirements_manager {
             $this->YUI_config->debug = true;
         } else {
             $this->yui3loader->filter = null;
+            $this->YUI_config->groups['moodle']['filter'] = null;
             $this->YUI_config->debug = false;
         }
 
@@ -287,6 +288,9 @@ class page_requirements_manager {
         if ($CFG->debugdeveloper) {
             $this->M_cfg['developerdebug'] = true;
         }
+        if (defined('BEHAT_SITE_RUNNING')) {
+            $this->M_cfg['behatsiterunning'] = true;
+        }
 
         // Accessibility stuff.
         $this->skip_link_to('maincontent', get_string('tocontent', 'access'));
index 9df959f..f1d3054 100644 (file)
@@ -59,95 +59,60 @@ if (!isset($CFG)) {
 // it can not be anything else, there is no point in having this in config.php
 $CFG->dirroot = dirname(dirname(__FILE__));
 
-// Normalise dataroot - we do not want any symbolic links, trailing / or any other weirdness there
-if (!isset($CFG->dataroot)) {
-    if (isset($_SERVER['REMOTE_ADDR'])) {
-        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
-    }
-    echo('Fatal error: $CFG->dataroot is not specified in config.php! Exiting.'."\n");
-    exit(1);
+// File permissions on created directories in the $CFG->dataroot
+if (!isset($CFG->directorypermissions)) {
+    $CFG->directorypermissions = 02777;      // Must be octal (that's why it's here)
 }
-$CFG->dataroot = realpath($CFG->dataroot);
-if ($CFG->dataroot === false) {
-    if (isset($_SERVER['REMOTE_ADDR'])) {
-        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
-    }
-    echo('Fatal error: $CFG->dataroot is not configured properly, directory does not exist or is not accessible! Exiting.'."\n");
-    exit(1);
-} else if (!is_writable($CFG->dataroot)) {
-    if (isset($_SERVER['REMOTE_ADDR'])) {
-        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
-    }
-    echo('Fatal error: $CFG->dataroot is not writable, admin has to fix directory permissions! Exiting.'."\n");
-    exit(1);
+if (!isset($CFG->filepermissions)) {
+    $CFG->filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
 }
-
-// wwwroot is mandatory
-if (!isset($CFG->wwwroot) or $CFG->wwwroot === 'http://example.com/moodle') {
-    if (isset($_SERVER['REMOTE_ADDR'])) {
-        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
-    }
-    echo('Fatal error: $CFG->wwwroot is not configured! Exiting.'."\n");
-    exit(1);
+// Better also set default umask because developers often forget to include directory
+// permissions in mkdir() and chmod() after creating new files.
+if (!isset($CFG->umaskpermissions)) {
+    $CFG->umaskpermissions = (($CFG->directorypermissions & 0777) ^ 0777);
 }
+umask($CFG->umaskpermissions);
 
-// Test environment is requested if:
-// * If $CFG->behat_switchcompletely has been set (maintains CLI scripts behaviour, which ATM is only preventive).
-// * If we are accessing though the built-in web server (cli-server).
-// * Behat is running (constant set hooking the behat init process before requiring config.php).
-// * If $CFG->behat_wwwroot has been set and the hostname/port match what the page was requested with.
-// Test environment is enabled if:
-// * User has previously enabled through admin/tool/behat/cli/util.php --enable or admin/tool/behat/cli/init.php
-// Both are required to switch to test mode
-if (!defined('BEHAT_SITE_RUNNING') && !empty($CFG->behat_dataroot) &&
-        !empty($CFG->behat_prefix) && file_exists($CFG->behat_dataroot)) {
+if (defined('BEHAT_SITE_RUNNING')) {
+    // We already switched to behat test site previously.
 
-    // Only included if behat_* are set, it is not likely to be a production site.
+} else if (!empty($CFG->behat_wwwroot) or !empty($CFG->behat_dataroot) or !empty($CFG->behat_prefix)) {
+    // The behat is configured on this server, we need to find out if this is the behat test
+    // site based on the URL used for access.
     require_once(__DIR__ . '/../lib/behat/lib.php');
+    if (behat_is_test_site()) {
+        // Checking the integrity of the provided $CFG->behat_* vars and the
+        // selected wwwroot to prevent conflicts with production and phpunit environments.
+        behat_check_config_vars();
 
-    $defaultbehatwwwroot = behat_get_wwwroot();
-
-    if (!empty($CFG->behat_switchcompletely) && php_sapi_name() !== 'cli') {
-        // Switch completely uses the production wwwroot as the test site URL.
-        $behatwwwroot = $defaultbehatwwwroot;
+        // Check that the directory does not contains other things.
+        if (!file_exists("$CFG->behat_dataroot/behattestdir.txt")) {
+            if ($dh = opendir($CFG->behat_dataroot)) {
+                while (($file = readdir($dh)) !== false) {
+                    if ($file === 'behat' or $file === '.' or $file === '..' or $file === '.DS_Store') {
+                        continue;
+                    }
+                    behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot directory is not empty, ensure this is the directory where you want to install behat test dataroot');
+                }
+                closedir($dh);
+                unset($dh);
+                unset($file);
+            }
 
-    } elseif (php_sapi_name() === 'cli-server') {
-        // If we are using the built-in server we use the provided $CFG->behat_wwwroot
-        // value or the default one if $CFG->behat_wwwroot is not set, only if it matches
-        // the requested URL.
-        if (behat_is_requested_url($defaultbehatwwwroot)) {
-            $behatwwwroot = $defaultbehatwwwroot;
+            if (defined('BEHAT_UTIL')) {
+                // Now we create dataroot directory structure for behat tests.
+                testing_initdataroot($CFG->behat_dataroot, 'behat');
+            } else {
+                behat_error(BEHAT_EXITCODE_INSTALL);
+            }
         }
 
-    } elseif (defined('BEHAT_TEST')) {
-        // This is when moodle codebase runs through vendor/bin/behat, we "are not supposed"
-        // to need a wwwroot, but before using the production one we should set something else
-        // as an alternative.
-        $behatwwwroot = $defaultbehatwwwroot;
-
-    } elseif (!empty($CFG->behat_wwwroot) && !empty($_SERVER['HTTP_HOST'])) {
-        // If $CFG->behat_wwwroot was set and matches the requested URL we
-        // use $CFG->behat_wwwroot as our wwwroot.
-        if (behat_is_requested_url($CFG->behat_wwwroot)) {
-            $behatwwwroot = $CFG->behat_wwwroot;
+        if (!defined('BEHAT_UTIL') and !defined('BEHAT_TEST')) {
+            // Somebody tries to access test site directly, tell them if not enabled.
+            if (!file_exists($CFG->behat_dataroot . '/behat/test_environment_enabled.txt')) {
+                behat_error(BEHAT_EXITCODE_CONFIG, 'Behat is configured but not enabled on this test site.');
+            }
         }
-    }
-
-    // If we found a proper behatwwwroot then we consider the behat test as requested.
-    $testenvironmentrequested = !empty($behatwwwroot);
-
-    // Only switch to test environment if it has been enabled.
-    $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
-    $testenvironmentenabled = file_exists($CFG->behat_dataroot . '/behat/test_environment_enabled.txt');
-
-    if ($testenvironmentenabled && $testenvironmentrequested) {
-
-        // Now we know which one will be our behat wwwroot.
-        $CFG->behat_wwwroot = $behatwwwroot;
-
-        // Checking the integrity of the provided $CFG->behat_* vars and the
-        // selected wwwroot to prevent conflicts with production and phpunit environments.
-        behat_check_config_vars();
 
         // Constant used to inform that the behat test site is being used,
         // this includes all the processes executed by the behat CLI command like
@@ -169,6 +134,38 @@ if (!defined('BEHAT_SITE_RUNNING') && !empty($CFG->behat_dataroot) &&
     }
 }
 
+// Normalise dataroot - we do not want any symbolic links, trailing / or any other weirdness there
+if (!isset($CFG->dataroot)) {
+    if (isset($_SERVER['REMOTE_ADDR'])) {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
+    }
+    echo('Fatal error: $CFG->dataroot is not specified in config.php! Exiting.'."\n");
+    exit(1);
+}
+$CFG->dataroot = realpath($CFG->dataroot);
+if ($CFG->dataroot === false) {
+    if (isset($_SERVER['REMOTE_ADDR'])) {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
+    }
+    echo('Fatal error: $CFG->dataroot is not configured properly, directory does not exist or is not accessible! Exiting.'."\n");
+    exit(1);
+} else if (!is_writable($CFG->dataroot)) {
+    if (isset($_SERVER['REMOTE_ADDR'])) {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
+    }
+    echo('Fatal error: $CFG->dataroot is not writable, admin has to fix directory permissions! Exiting.'."\n");
+    exit(1);
+}
+
+// wwwroot is mandatory
+if (!isset($CFG->wwwroot) or $CFG->wwwroot === 'http://example.com/moodle') {
+    if (isset($_SERVER['REMOTE_ADDR'])) {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
+    }
+    echo('Fatal error: $CFG->wwwroot is not configured! Exiting.'."\n");
+    exit(1);
+}
+
 // Make sure there is some database table prefix.
 if (!isset($CFG->prefix)) {
     $CFG->prefix = '';
@@ -219,6 +216,7 @@ if (!isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['argv'][0])) {
 
 // sometimes default PHP settings are borked on shared hosting servers, I wonder why they have to do that??
 ini_set('precision', 14); // needed for upgrades and gradebook
+ini_set('serialize_precision', 17); // Make float serialization consistent on all systems.
 
 // Scripts may request no debug and error messages in output
 // please note it must be defined before including the config.php script
@@ -334,10 +332,10 @@ if (file_exists("$CFG->dataroot/climaintenance.html")) {
 
 if (CLI_SCRIPT) {
     // sometimes people use different PHP binary for web and CLI, make 100% sure they have the supported PHP version
-    if (version_compare(phpversion(), '5.3.3') < 0) {
+    if (version_compare(phpversion(), '5.4.4') < 0) {
         $phpversion = phpversion();
         // do NOT localise - lang strings would not work here and we CAN NOT move it to later place
-        echo "Moodle 2.5 or later requires at least PHP 5.3.3 (currently using version $phpversion).\n";
+        echo "Moodle 2.7 or later requires at least PHP 5.4.4 (currently using version $phpversion).\n";
         echo "Some servers may have multiple PHP versions installed, are you using the correct executable?\n";
         exit(1);
     }
@@ -348,20 +346,6 @@ if (!defined('AJAX_SCRIPT')) {
     define('AJAX_SCRIPT', false);
 }
 
-// File permissions on created directories in the $CFG->dataroot
-if (!isset($CFG->directorypermissions)) {
-    $CFG->directorypermissions = 02777;      // Must be octal (that's why it's here)
-}
-if (!isset($CFG->filepermissions)) {
-    $CFG->filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
-}
-// Better also set default umask because developers often forget to include directory
-// permissions in mkdir() and chmod() after creating new files.
-if (!isset($CFG->umaskpermissions)) {
-    $CFG->umaskpermissions = (($CFG->directorypermissions & 0777) ^ 0777);
-}
-umask($CFG->umaskpermissions);
-
 // Exact version of currently used yui2 and 3 library.
 $CFG->yui2version = '2.9.0';
 $CFG->yui3version = '3.13.0';
index 84e9a5a..1c918b4 100644 (file)
@@ -259,6 +259,7 @@ function stats_cron_daily($maxdays=1) {
             $failed = true;
             break;
         }
+        $DB->update_temp_table_stats();
 
         stats_progress('1');
 
@@ -385,6 +386,10 @@ function stats_cron_daily($maxdays=1) {
             $failed = true;
             break;
         }
+        // The steps up until this point, all add to {temp_stats_daily} and don't use new tables.
+        // There is no point updating statistics as they won't be used until the DELETE below.
+        $DB->update_temp_table_stats();
+
         stats_progress('7');
 
         // Default frontpage role enrolments are all site users (not deleted)
@@ -581,6 +586,7 @@ function stats_cron_daily($maxdays=1) {
             $failed = true;
             break;
         }
+        $DB->update_temp_table_stats();
         stats_progress('15');
 
         // How many view actions for guests or not-logged-in on frontpage
@@ -1736,6 +1742,9 @@ function stats_temp_table_fill($timestart, $timeend) {
 
     $DB->execute($sql);
 
+    // We have just loaded all the temp tables, collect statistics for that.
+    $DB->update_temp_table_stats();
+
     return true;
 }
 
index 41a4243..661481f 100644 (file)
@@ -644,18 +644,23 @@ abstract class testing_util {
             return null;
         }
 
-        $ref = file_get_contents("$CFG->dirroot/.git/HEAD");
-        if ($ref === false) {
+        $headcontent = file_get_contents("$CFG->dirroot/.git/HEAD");
+        if ($headcontent === false) {
             return null;
         }
 
-        $ref = trim($ref);
+        $headcontent = trim($headcontent);
 
-        if (strpos($ref, 'ref: ') !== 0) {
+        // If it is pointing to a hash we return it directly.
+        if (strlen($headcontent) === 40) {
+            return $headcontent;
+        }
+
+        if (strpos($headcontent, 'ref: ') !== 0) {
             return null;
         }
 
-        $ref = substr($ref, 5);
+        $ref = substr($headcontent, 5);
 
         if (!file_exists("$CFG->dirroot/.git/$ref")) {
             return null;
index 4cee9b1..c67a1a9 100644 (file)
@@ -38,7 +38,13 @@ function testing_cli_argument_path($moodlepath) {
         $moodlepath = preg_replace('|^/admin/|', "/$CFG->admin/", $moodlepath);
     }
 
-    $cwd = getcwd();
+    if (isset($_SERVER['REMOTE_ADDR'])) {
+        // Web access, this should not happen often.
+        $cwd = dirname(dirname(__DIR__));
+    } else {
+        // This is the real CLI script, work with relative paths.
+        $cwd = getcwd();
+    }
     if (substr($cwd, -1) !== DIRECTORY_SEPARATOR) {
         $cwd .= DIRECTORY_SEPARATOR;
     }
index b1cba20..80df165 100644 (file)
@@ -2274,8 +2274,6 @@ class core_accesslib_testcase extends advanced_testcase {
             $userids = array_slice($userids, 0, 5);
         }
 
-        // Random time!
-        // srand(666);
         foreach ($userids as $userid) { // No guest or deleted.
             // Each user gets 0-10 random roles.
             $rcount = rand(0, 10);
diff --git a/lib/tests/ajaxlib_test.php b/lib/tests/ajaxlib_test.php
new file mode 100644 (file)
index 0000000..9e80697
--- /dev/null
@@ -0,0 +1,113 @@
+<?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/>.
+
+/**
+ * Code quality unit tests that are fast enough to run each time.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2013 Andrew Nicols
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+class core_ajaxlib_testcase extends advanced_testcase {
+
+    protected function helper_test_clean_output() {
+        $this->resetAfterTest();
+
+        $result = ajax_capture_output();
+
+        // ob_start should normally return without issue.
+        $this->assertTrue($result);
+
+        $result = ajax_check_captured_output();
+        $this->assertEmpty($result);
+    }
+
+    protected function helper_test_dirty_output($expectexception = false) {
+        $this->resetAfterTest();
+
+        // Keep track of the content we will output.
+        $content = "Some example content";
+
+        $result = ajax_capture_output();
+
+        // ob_start should normally return without issue.
+        $this->assertTrue($result);
+
+        // Fill the output buffer.
+        echo $content;
+
+        if ($expectexception) {
+            $this->setExpectedException('coding_exception');
+            ajax_check_captured_output();
+        } else {
+            $result = ajax_check_captured_output();
+            $this->assertEquals($result, $content);
+        }
+    }
+
+    public function test_output_capture_normal_debug_none() {
+        // In normal conditions, and with DEBUG_NONE set, we should not receive any output or throw any exceptions.
+        set_debugging(DEBUG_NONE);
+        $this->helper_test_clean_output();
+    }
+
+    public function test_output_capture_normal_debug_normal() {
+        // In normal conditions, and with DEBUG_NORMAL set, we should not receive any output or throw any exceptions.
+        set_debugging(DEBUG_NORMAL);
+        $this->helper_test_clean_output();
+    }
+
+    public function test_output_capture_normal_debug_all() {
+        // In normal conditions, and with DEBUG_ALL set, we should not receive any output or throw any exceptions.
+        set_debugging(DEBUG_ALL);
+        $this->helper_test_clean_output();
+    }
+
+    public function test_output_capture_normal_debugdeveloper() {
+        // In normal conditions, and with DEBUG_DEVELOPER set, we should not receive any output or throw any exceptions.
+        set_debugging(DEBUG_DEVELOPER);
+        $this->helper_test_clean_output();
+    }
+
+    public function test_output_capture_error_debug_none() {
+        // With DEBUG_NONE set, we should not throw any exception, but the output will be returned.
+        set_debugging(DEBUG_NONE);
+        $this->helper_test_dirty_output();
+    }
+
+    public function test_output_capture_error_debug_normal() {
+        // With DEBUG_NORMAL set, we should not throw any exception, but the output will be returned.
+        set_debugging(DEBUG_NORMAL);
+        $this->helper_test_dirty_output();
+    }
+
+    public function test_output_capture_error_debug_all() {
+        // In error conditions, and with DEBUG_ALL set, we should not receive any output or throw any exceptions.
+        set_debugging(DEBUG_ALL);
+        $this->helper_test_dirty_output();
+    }
+
+    public function test_output_capture_error_debugdeveloper() {
+        // With DEBUG_DEVELOPER set, we should throw an exception.
+        set_debugging(DEBUG_DEVELOPER);
+        $this->helper_test_dirty_output(true);
+    }
+
+}
index 81285c0..b521638 100644 (file)
@@ -67,6 +67,16 @@ class behat_hooks extends behat_base {
      */
     protected static $initprocessesfinished = false;
 
+    /**
+     * Some exceptions can only be caught in a before or after step hook,
+     * they can not be thrown there as they will provoke a framework level
+     * failure, but we can store them here to fail the step in i_look_for_exceptions()
+     * which result will be parsed by the framework as the last step result.
+     *
+     * @var Null or the exception last step throw in the before or after hook.
+     */
+    protected static $currentstepexception = null;
+
     /**
      * Gives access to moodle codebase, ensures all is ready and sets up the test lock.
      *
@@ -107,7 +117,7 @@ class behat_hooks extends behat_base {
 
         if (!behat_util::is_server_running()) {
             throw new Exception($CFG->behat_wwwroot .
-                ' is not available, ensure you started your PHP built-in server or your web server is correctly started and set up.' .
+                ' is not available, ensure you specified correct url and that the server is set up and started.' .
                 ' More info in ' . behat_command::DOCS_URL . '#Running_tests');
         }
 
@@ -210,26 +220,27 @@ class behat_hooks extends behat_base {
             self::$initprocessesfinished = true;
         }
 
-        // Closing JS dialogs if present. Otherwise they would block this scenario execution.
-        if ($this->running_javascript()) {
-            try {
-                $session->getDriver()->getWebDriverSession()->accept_alert();
-            } catch (NoAlertOpenError $e) {
-                // All ok, there should not be JS dialogs in theory.
-            }
-        }
-
     }
 
     /**
      * Wait for JS to complete before beginning interacting with the DOM.
      *
-     * Executed only when running against a real browser.
+     * Executed only when running against a real browser. We wrap it
+     * all in a try & catch to forward the exception to i_look_for_exceptions
+     * so the exception will be at scenario level, which causes a failure, by
+     * default would be at framework level, which will stop the execution of
+     * the run.
      *
      * @BeforeStep @javascript
      */
     public function before_step_javascript($event) {
-        $this->wait_for_pending_js();
+
+        try {
+            $this->wait_for_pending_js();
+            self::$currentstepexception = null;
+        } catch (Exception $e) {
+            self::$currentstepexception = $e;
+        }
     }
 
     /**
@@ -238,12 +249,33 @@ class behat_hooks extends behat_base {
      * With this we ensure that there are not AJAX calls
      * still in progress.
      *
-     * Executed only when running against a real browser.
+     * Executed only when running against a real browser. We wrap it
+     * all in a try & catch to forward the exception to i_look_for_exceptions
+     * so the exception will be at scenario level, which causes a failure, by
+     * default would be at framework level, which will stop the execution of
+     * the run.
      *
      * @AfterStep @javascript
      */
     public function after_step_javascript($event) {
-        $this->wait_for_pending_js();
+
+        try {
+            $this->wait_for_pending_js();
+            self::$currentstepexception = null;
+        } catch (UnexpectedAlertOpen $e) {
+            self::$currentstepexception = $e;
+
+            // Accepting the alert so the framework can continue properly running
+            // the following scenarios. Some browsers already closes the alert, so
+            // wrapping in a try & catch.
+            try {
+                $this->getSession()->getDriver()->getWebDriverSession()->accept_alert();
+            } catch (Exception $e) {
+                // Catching the generic one as we never know how drivers reacts here.
+            }
+        } catch (Exception $e) {
+            self::$currentstepexception = $e;
+        }
     }
 
     /**
@@ -267,9 +299,10 @@ class behat_hooks extends behat_base {
                 // No javascript is running if there is no window right?
                 $pending = '';
             } catch (UnknownError $e) {
-                // Same exception as before, but some combinations of browser + OS reports it as an unknown error
-                // exception.
-                $pending = '';
+                // M is not defined when the window or the frame don't exist anymore.
+                if (strstr($e->getMessage(), 'M is not defined') != false) {
+                    $pending = '';
+                }
             }
 
             // If there are no pending JS we stop waiting.
@@ -293,10 +326,16 @@ class behat_hooks extends behat_base {
      * after each step so no features will splicitly use it.
      *
      * @Given /^I look for exceptions$/
+     * @throw Exception Unknown type, depending on what we caught in the hook or basic \Exception.
      * @see Moodle\BehatExtension\Tester\MoodleStepTester
      */
     public function i_look_for_exceptions() {
 
+        // If the step already failed in a hook throw the exception.
+        if (!is_null(self::$currentstepexception)) {
+            throw self::$currentstepexception;
+        }
+
         // Wrap in try in case we were interacting with a closed window.
         try {
 
@@ -368,13 +407,6 @@ class behat_hooks extends behat_base {
 
         } catch (NoSuchWindow $e) {
             // If we were interacting with a popup window it will not exists after closing it.
-        } catch (UnexpectedAlertOpen $e) {
-            // We fail the scenario if we find an opened JS alert/confirm, in most of the cases it
-            // will be there because we are leaving an edited form without submitting/cancelling
-            // it, but moodle is using JS confirms and we can not just cancel the JS dialog
-            // as in some cases (delete activity with JS enabled for example) the test writer should
-            // use extra steps to deal with moodle's behaviour.
-            throw new Exception('Modal window present. Ensure there are no edited forms pending to submit/cancel.');
         }
     }
 
index b720a86..a160016 100644 (file)
@@ -3,12 +3,24 @@ information provided here is intended especially for developers.
 
 === 2.7 ===
 
+DEPRECATIONS:
 * Abstract class \core\event\course_module_instances_list_viewed is deprecated now, use \core\event\instances_list_viewed instead.
 * mod_book\event\instances_list_viewed has been deprecated. Please use mod_book\event\course_module_instance_list_viewed instead.
 * mod_chat\event\instances_list_viewed has been deprecated. Please use mod_chat\event\course_module_instance_list_viewed instead.
 * mod_choice\event\instances_list_viewed has been deprecated. Please use mod_choice\event\course_module_instance_list_viewed instead.
 * mod_feedback\event\instances_list_viewed has been deprecated. Please use mod_feedback\event\course_module_instance_list_viewed instead.
 * mod_page\event\instances_list_viewed has been deprecated. Please use mod_page\event\course_module_instance_list_viewed instead.
+* The constants FRONTPAGECOURSELIST, FRONTPAGETOPICONLY & FRONTPAGECOURSELIMIT have been removed.
+
+YUI:
+  * The lightbox attribute for moodle-core-notification-dialogue has been
+    deprecated and replaced by the modal attribute. This was actually
+    changed in Moodle 2.2, but has only been marked as deprecated now. It
+    will be removed in Moodle 2.9.
+
+JavaSript:
+    * The findChildNodes global function has been deprecated. Y.all should
+      be used instead.
 
 === 2.6 ===
 
index 6f6c786..9b0f720 100644 (file)
Binary files a/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js and b/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-debug.js differ
index 2806b0f..38f3d5e 100644 (file)
Binary files a/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js and b/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js differ
index 02a6e8a..f08c612 100644 (file)
Binary files a/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js and b/lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu.js differ
index dc64557..0d1d0a2 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js differ
index 1aeb79c..c044e7d 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js differ
index dc64557..0d1d0a2 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js differ
index 5dffba2..e9763de 100644 (file)
Binary files a/lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-debug.js and b/lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-debug.js differ
index c928e91..30444d2 100644 (file)
Binary files a/lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-min.js and b/lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-min.js differ
index 5dffba2..e9763de 100644 (file)
Binary files a/lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker.js and b/lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker.js differ
index 929a1e8..b4b62ea 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js differ
index 76cfc21..b289ee5 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js differ
index 929a1e8..77dc52d 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js differ
index c3a5730..31d81fc 100644 (file)
Binary files a/lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-debug.js and b/lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-debug.js differ
index d026cfc..e99b50a 100644 (file)
Binary files a/lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-min.js and b/lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-min.js differ
index c3a5730..31d81fc 100644 (file)
Binary files a/lib/yui/build/moodle-core-tooltip/moodle-core-tooltip.js and b/lib/yui/build/moodle-core-tooltip/moodle-core-tooltip.js differ
index 7eb93de..9e84085 100644 (file)
@@ -284,7 +284,8 @@ ACTIONMENU.prototype = {
         var menu = e.target.ancestor(SELECTOR.MENU),
             menuvisible = (menu.hasClass('show'));
 
-        e.preventDefault();
+        // Prevent event propagation as it will trigger the hideIfOutside event handler in certain situations.
+        e.halt(true);
         this.hideMenu();
         if (menuvisible) {
             // The menu was visible and the user has clicked to toggle it again.
index e7eba74..4506f5f 100644 (file)
@@ -39,7 +39,7 @@ Y.extend(CHOOSERDIALOGUE, Y.Base, {
             draggable : true,
             visible : false, // Hide by default
             zindex : 100, // Display in front of other items
-            lightbox : true, // This dialogue should be modal
+            modal: true, // This dialogue should be modal.
             shim : true,
             closeButtonTitle : this.get('closeButtonTitle')
         };
index 0aec7ec..0d2bd63 100644 (file)
@@ -170,6 +170,11 @@ M.core_formchangechecker.report_form_dirty_state = function(e) {
     // This is the error message that we'll show to browsers which support it
     var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
 
+    if (M.cfg.behatsiterunning) {
+        // If the behat site is running we don't want browser alerts.
+        return;
+    }
+
     // Most browsers are happy with the returnValue being set on the event
     // But some browsers do not consistently pass the event
     if (e) {
index 30b81a4..ae4a4d4 100644 (file)
@@ -17,12 +17,13 @@ var DIALOGUE_NAME = 'Moodle dialogue',
 /**
  * A re-usable dialogue box with Moodle classes applied.
  *
- * @param {Object} config Object literal specifying the dialogue configuration properties.
+ * @param {Object} c Object literal specifying the dialogue configuration properties.
  * @constructor
  * @class M.core.dialogue
  * @extends Y.Panel
  */
-DIALOGUE = function(config) {
+DIALOGUE = function(c) {
+    var config = Y.clone(c);
     COUNT++;
     var id = 'moodle-dialogue-'+COUNT;
     config.notificationBase =
@@ -39,7 +40,6 @@ DIALOGUE = function(config) {
 
     config.srcNode =    '#'+id;
     config.width =      config.width || '400px';
-    config.visible =    config.visible || false;
     config.center =     config.centered && true;
     config.centered =   false;
     config.COUNT = COUNT;
@@ -48,12 +48,6 @@ DIALOGUE = function(config) {
         delete config.width;
     }
 
-    // lightbox param to keep the stable versions API.
-    if (config.lightbox !== false) {
-        config.modal = true;
-    }
-    delete config.lightbox;
-
     // closeButton param to keep the stable versions API.
     if (config.closeButton === false) {
         config.buttons = null;
@@ -356,10 +350,15 @@ Y.extend(DIALOGUE, Y.Panel, {
          * @attribute lightbox
          * @type Boolean
          * @default true
+         * @deprecated Since Moodle 2.7. Please use modal instead.
          */
-        lightbox : {
-            validator : Y.Lang.isBoolean,
-            value : true
+        lightbox: {
+            lazyAdd: false,
+            setter: function(value) {
+                Y.log("The lightbox attribute of M.core.dialogue has been deprecated since Moodle 2.7, please use the modal attribute instead",
+                    'warn', 'moodle-core-notification-dialogue');
+                this.set('modal', value);
+            }
         },
 
         /**
index 25a4dd7..2f19a8c 100644 (file)
@@ -26,10 +26,6 @@ function TOOLTIP(config) {
         config.constrain = true;
     }
 
-    if (typeof config.lightbox === 'undefined') {
-        config.lightbox = false;
-    }
-
     TOOLTIP.superclass.constructor.apply(this, [config]);
 }
 
@@ -440,5 +436,21 @@ Y.extend(TOOLTIP, M.core.dialogue, {
         }
     }
 });
+
+Y.Base.modifyAttrs(TOOLTIP, {
+    /**
+     * Whether the widget should be modal or not.
+     *
+     * Moodle override: We override this for tooltip to default it to false.
+     *
+     * @attribute Modal
+     * @type Boolean
+     * @default false
+     */
+    modal: {
+        value: false
+    }
+});
+
 M.core = M.core || {};
 M.core.tooltip = M.core.tooltip = TOOLTIP;
index 1c327ec..525eee6 100644 (file)
@@ -28,4 +28,8 @@ $string['default_help'] = 'If set, this feedback method will be enabled by defau
 $string['enabled'] = 'Feedback comments';
 $string['enabled_help'] = 'If enabled, the marker can leave feedback comments for each submission. ';
 $string['pluginname'] = 'Feedback comments';
+$string['commentinline'] = 'Comment inline';
+$string['commentinline_help'] = 'If enabled, the submission text will be copied into the feedback comment field during grading, making it easier to comment inline (using a different colour, perhaps) or to edit the original text.';
+$string['commentinlinedefault'] = 'Comment inline by default';
+$string['commentinlinedefault_help'] = 'If set, this comment inline functionality will be enabled by default for all new assignments.';
 
index 7791c2d..1f043cb 100644 (file)
@@ -186,6 +186,87 @@ class assign_feedback_comments extends assign_feedback_plugin {
         }
     }
 
+    /**
+     * Save the settings for feedback comments plugin
+     *
+     * @param stdClass $data
+     * @return bool
+     */
+    public function save_settings(stdClass $data) {
+        $this->set_config('commentinline', !empty($data->assignfeedback_comments_commentinline));
+        return true;
+    }
+
+    /**
+     * Get the default setting for feedback comments plugin
+     *
+     * @param MoodleQuickForm $mform The form to add elements to
+     * @return void
+     */
+    public function get_settings(MoodleQuickForm $mform) {
+        $default = $this->get_config('commentinline');
+        $mform->addElement('selectyesno',
+                           'assignfeedback_comments_commentinline',
+                           get_string('commentinline', 'assignfeedback_comments'));
+        $mform->addHelpButton('assignfeedback_comments_commentinline', 'commentinline', 'assignfeedback_comments');
+        $mform->setDefault('assignfeedback_comments_commentinline', $default);
+        // Disable comment online if comment feedback plugin is disabled.
+        $mform->disabledIf('assignfeedback_comments_commentinline', 'assignfeedback_comments_enabled', 'notchecked');
+   }
+
+    /**
+     * A student submission may contain image tags that refer to images stored
+     * in the file area for the submission. We cannot allow these links to be copied to
+     * the feedback text fields, so we must strip them from the content.
+     *
+     * @param string $source The submission text
+     * @return string The stripped text
+     */
+    protected function strip_moodle_content($source) {
+        $baseurl = '@@PLUGINFILE@@';
+        // Looking for something like < .* "@@pluginfile@@.*" .* >
+        $pattern = '$<[^<>]+["\']' . $baseurl . '[^"\']*["\'][^<>]*>$';
+        $stripped = preg_replace($pattern, '', $source);
+        // Use purify html to rebalence potentially mismatched tags and generally cleanup.
+        return purify_html($stripped);
+    }
+
+    /**
+     * Convert the text from any submission plugin that has an editor field to
+     * a format suitable for inserting in the feedback text field.
+     *
+     * @param stdClass $submission
+     * @param stdClass $data - Form data to be filled with the converted submission text and format.
+     * @return boolean - True if feedback text was set.
+     */
+    protected function convert_submission_text_to_feedback($submission, $data) {
+        $format = false;
+        $text = '';
+
+        foreach ($this->assignment->get_submission_plugins() as $plugin) {
+            $fields = $plugin->get_editor_fields();
+            if ($plugin->is_enabled() && $plugin->is_visible() && !empty($fields)) {
+                foreach ($fields as $key => $description) {
+                    $rawtext = $this->strip_moodle_content($plugin->get_editor_text($key, $submission->id));
+
+                    $newformat = $plugin->get_editor_format($key, $submission->id);
+
+                    if ($format !== false && $newformat != $format) {
+                        // There are 2 or more editor fields using different formats, set to plain as a fallback.
+                        $format = FORMAT_PLAIN;
+                    } else {
+                        $format = $newformat;
+                    }
+                    $text .= $rawtext;
+                }
+            }
+        }
+
+        $data->assignfeedbackcomments_editor['text'] = $text;
+        $data->assignfeedbackcomments_editor['format'] = $format;
+        return true;
+    }
+
     /**
      * Get form elements for the grading page
      *
@@ -194,12 +275,22 @@ class assign_feedback_comments extends assign_feedback_plugin {
      * @param stdClass $data
      * @return bool true if elements were added to the form
      */
-    public function get_form_elements($grade, MoodleQuickForm $mform, stdClass $data) {
+    public function get_form_elements_for_user($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
+        $commentinlinenabled = $this->get_config('commentinline');
+        $submission = $this->assignment->get_user_submission($userid, false);
+        $feedbackcomments = false;
+
         if ($grade) {
             $feedbackcomments = $this->get_feedback_comments($grade->id);
-            if ($feedbackcomments) {
-                $data->assignfeedbackcomments_editor['text'] = $feedbackcomments->commenttext;
-                $data->assignfeedbackcomments_editor['format'] = $feedbackcomments->commentformat;
+        }
+
+        if ($feedbackcomments && !empty($feedbackcomments->commenttext)) {
+            $data->assignfeedbackcomments_editor['text'] = $feedbackcomments->commenttext;
+            $data->assignfeedbackcomments_editor['format'] = $feedbackcomments->commentformat;
+        } else {
+            // No feedback given yet - maybe we need to copy the text from the submission?
+            if (!empty($commentinlinenabled) && $submission) {
+                $this->convert_submission_text_to_feedback($submission, $data);
             }
         }
 
@@ -296,6 +387,10 @@ class assign_feedback_comments extends assign_feedback_plugin {
      * @return bool was it a success? (false will trigger a rollback)
      */
     public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
+        if ($oldassignment->assignmenttype == 'online') {
+            $this->set_config('commentinline', $oldassignment->var1);
+            return true;
+        }
         return true;
     }
 
index f8d45ae..492a38d 100644 (file)
@@ -26,3 +26,11 @@ $settings->add(new admin_setting_configcheckbox('assignfeedback_comments/default
                    new lang_string('default', 'assignfeedback_comments'),
                    new lang_string('default_help', 'assignfeedback_comments'), 1));
 
+$setting = new admin_setting_configcheckbox('assignfeedback_comments/inline',
+                   new lang_string('commentinlinedefault', 'assignfeedback_comments'),
+                   new lang_string('commentinlinedefault_help', 'assignfeedback_comments'), 0);
+
+$setting->set_advanced_flag_options(admin_setting_flag::ENABLED, false);
+$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
+
+$settings->add($setting);
index c4e5f21..bae5096 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2013110500;
+$plugin->version   = 2013121000;
 $plugin->requires  = 2013110500;
 $plugin->component = 'assignfeedback_comments';
index 1eb58aa..d11f077 100644 (file)
@@ -77,6 +77,8 @@ class pdf extends \FPDI {
      */
     public function combine_pdfs($pdflist, $outfilename) {
 
+        raise_memory_limit(MEMORY_EXTRA);
+
         $this->setPageUnit('pt');
         $this->setPrintHeader(false);
         $this->setPrintFooter(false);
@@ -122,6 +124,8 @@ class pdf extends \FPDI {
      * @return int the number of pages in the PDF
      */
     public function load_pdf($filename) {
+        raise_memory_limit(MEMORY_EXTRA);
+
         $this->setPageUnit('pt');
         $this->scale = 72.0 / 100.0;
         $this->SetFont('helvetica', '', 16.0 * $this->scale);
@@ -133,6 +137,7 @@ class pdf extends \FPDI {
         $this->setPrintFooter(false);
         $this->pagecount = $this->setSourceFile($filename);
         $this->filename = $filename;
+
         return $this->pagecount;
     }
 
@@ -426,14 +431,14 @@ class pdf extends \FPDI {
 
         if ($generate) {
             // Use ghostscript to generate an image of the specified page.
-            $gsexec = \get_config('assignfeedback_editpdf', 'gspath');
-            $imageres = 100;
-            $filename = $this->filename;
-            $pagenoinc = $pageno + 1;
+            $gsexec = \escapeshellarg(\get_config('assignfeedback_editpdf', 'gspath'));
+            $imageres = \escapeshellarg(100);
+            $imagefilearg = \escapeshellarg($imagefile);
+            $filename = \escapeshellarg($this->filename);
+            $pagenoinc = \escapeshellarg($pageno + 1);
             $command = "$gsexec -q -sDEVICE=png16m -dSAFER -dBATCH -dNOPAUSE -r$imageres -dFirstPage=$pagenoinc -dLastPage=$pagenoinc ".
-                "-dGraphicsAlphaBits=4 -dTextAlphaBits=4 -sOutputFile=\"$imagefile\" \"$filename\"";
+                "-dGraphicsAlphaBits=4 -dTextAlphaBits=4 -sOutputFile=$imagefilearg $filename";
 
-            //$command = escapeshellcmd($command);
             $output = null;
             $result = exec($command, $output);
             if (!file_exists($imagefile)) {
@@ -484,9 +489,10 @@ class pdf extends \FPDI {
 
         $file->copy_content_to($tempsrc); // Copy the file.
 
-        $gsexec = \get_config('assignfeedback_editpdf', 'gspath');
-        $command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=\"$tempdst\" \"$tempsrc\"";
-        //$command = escapeshellcmd($command);
+        $gsexec = \escapeshellarg(\get_config('assignfeedback_editpdf', 'gspath'));
+        $tempdstarg = \escapeshellarg($tempdst);
+        $tempsrcarg = \escapeshellarg($tempsrc);
+        $command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg";
         exec($command);
         @unlink($tempsrc);
         if (!file_exists($tempdst)) {
index 45eb07a..b1929ee 100644 (file)
Binary files a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js and b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js differ
index 444d396..9bbcef0 100644 (file)
Binary files a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js and b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js differ
index 45eb07a..b1929ee 100644 (file)
Binary files a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js and b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js differ
index a305844..e72fa22 100644 (file)
@@ -14,7 +14,6 @@ COMMENTSEARCH = function(config) {
     config.draggable = false;
     config.centered = true;
     config.width = '400px';
-    config.lightbox = true;
     config.visible = false;
     config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf');
     config.footerContent = '';
@@ -149,5 +148,22 @@ Y.extend(COMMENTSEARCH, M.core.dialogue, {
     }
 });
 
+Y.Base.modifyAttrs(COMMENTSEARCH, {
+    /**
+     * Whether the widget should be modal or not.
+     *
+     * Moodle override: We override this for commentsearch to force it always true.
+     *
+     * @attribute Modal
+     * @type Boolean
+     * @default true
+     */
+    modal: {
+        getter: function() {
+            return true;
+        }
+    }
+});
+
 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH;
index 781b490..ecc6fe9 100644 (file)
@@ -14,7 +14,6 @@ DROPDOWN = function(config) {
     config.draggable = false;
     config.centered = false;
     config.width = 'auto';
-    config.lightbox = false;
     config.visible = false;
     config.footerContent = '';
     DROPDOWN.superclass.constructor.apply(this, [config]);
@@ -97,5 +96,22 @@ Y.extend(DROPDOWN, M.core.dialogue, {
     }
 });
 
+Y.Base.modifyAttrs(DROPDOWN, {
+    /**
+     * Whether the widget should be modal or not.
+     *
+     * Moodle override: We override this for commentsearch to force it always false.
+     *
+     * @attribute Modal
+     * @type Boolean
+     * @default false
+     */
+    modal: {
+        getter: function() {
+            return false;
+        }
+    }
+});
+
 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 M.assignfeedback_editpdf.dropdown = DROPDOWN;
index 673074f..c3667df 100644 (file)
@@ -451,8 +451,8 @@ class assign {
                 // Save changes button.
                 $action = 'grade';
                 if ($this->process_save_grade($mform)) {
-                    $message = get_string('gradingchangessaved', 'assign');
-                    $action = 'savegradingresult';
+                    $action = 'redirect';
+                    $nextpageparams['action'] = 'savegradingresult';
                 }
             } else {
                 // Cancel button.
@@ -488,6 +488,7 @@ class assign {
             redirect($nextpageurl);
             return;
         } else if ($action == 'savegradingresult') {
+            $message = get_string('gradingchangessaved', 'assign');
             $o .= $this->view_savegrading_result($message);
         } else if ($action == 'quickgradingresult') {
             $mform = null;
@@ -2842,7 +2843,9 @@ class assign {
                                                   $this->get_course_module()->id,
                                                   $this->get_return_action(),
                                                   $this->get_return_params(),
-                                                  true);
+                                                  true,
+                                                  $useridlistid,
+                                                  $rownum);
 
             $o .= $this->get_renderer()->render($history);
         }
@@ -3700,7 +3703,9 @@ class assign {
                                                       $this->get_course_module()->id,
                                                       $this->get_return_action(),
                                                       $this->get_return_params(),
-                                                      false);
+                                                      false,
+                                                      0,
+                                                      0);
 
                 $o .= $this->get_renderer()->render($history);
             }
@@ -4214,7 +4219,7 @@ class assign {
 
         $graders = array();
         if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) {
-            if ($groups = groups_get_all_groups($this->get_course()->id, $userid)) {
+            if ($groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_course_module()->groupingid)) {
                 foreach ($groups as $group) {
                     foreach ($potentialgraders as $grader) {
                         if ($grader->id == $userid) {
@@ -5119,7 +5124,7 @@ class assign {
     protected function process_copy_previous_attempt(&$notices) {
         require_sesskey();
 
-        return copy_previous_attempt($notices);
+        return $this->copy_previous_attempt($notices);
     }
 
     /**
index 6b03323..72d5b96 100644 (file)
@@ -476,22 +476,26 @@ class assign_submission_status implements renderable {
  */
 class assign_attempt_history implements renderable {
 
-    /** @var array submissions */
+    /** @var array submissions - The list of previous attempts */
     public $submissions = array();
-    /** @var array grades */
+    /** @var array grades - The grades for the previous attempts */
     public $grades = array();
-    /** @var array submissionplugins */
+    /** @var array submissionplugins - The list of submission plugins to render the previous attempts */
     public $submissionplugins = array();
-    /** @var array feedbackplugins */
+    /** @var array feedbackplugins - The list of feedback plugins to render the previous attempts */
     public $feedbackplugins = array();
-    /** @var int coursemoduleid */
+    /** @var int coursemoduleid - The cmid for the assignment */
     public $coursemoduleid = 0;
-    /** @var string returnaction */
+    /** @var string returnaction - The action for the next page. */
     public $returnaction = '';
-    /** @var string returnparams */
+    /** @var string returnparams - The params for the next page. */
     public $returnparams = array();
-    /** @var bool cangrade */
+    /** @var bool cangrade - Does this user have grade capability? */
     public $cangrade = false;
+    /** @var string useridlistid - Id of the useridlist stored in cache, this plus rownum determines the userid */
+    public $useridlistid = 0;
+    /** @var int rownum - The rownum of the user in the useridlistid - this plus useridlistid determines the userid */
+    public $rownum = 0;
 
     /**
      * Constructor
@@ -504,6 +508,8 @@ class assign_attempt_history implements renderable {
      * @param string $returnaction
      * @param array $returnparams
      * @param bool $cangrade
+     * @param int $useridlistid
+     * @param int $rownum
      */
     public function __construct($submissions,
                                 $grades,
@@ -512,7 +518,9 @@ class assign_attempt_history implements renderable {
                                 $coursemoduleid,
                                 $returnaction,
                                 $returnparams,
-                                $cangrade) {
+                                $cangrade,
+                                $useridlistid,
+                                $rownum) {
         $this->submissions = $submissions;
         $this->grades = $grades;
         $this->submissionplugins = $submissionplugins;
@@ -521,6 +529,8 @@ class assign_attempt_history implements renderable {
         $this->returnaction = $returnaction;
         $this->returnparams = $returnparams;
         $this->cangrade = $cangrade;
+        $this->useridlistid = $useridlistid;
+        $this->rownum = $rownum;
     }
 }
 
index f9058c8..8633c81 100644 (file)
@@ -55,7 +55,7 @@ class mod_assign_renderer extends plugin_renderer_base {
      * @return string
      */
     public function render_assign_files(assign_files $tree) {
-        $this->htmlid = 'assign_files_tree_'.uniqid();
+        $this->htmlid = html_writer::random_id('assign_files_tree');
         $this->page->requires->js_init_call('M.mod_assign.init_tree', array(true, $this->htmlid));
         $html = '<div id="'.$this->htmlid.'">';
         $html .= $this->htmllize_tree($tree, $tree->dir);
@@ -685,7 +685,9 @@ class mod_assign_renderer extends plugin_renderer_base {
                     $o .= $this->output->box_end();
                 } else if ($submission->status == ASSIGN_SUBMISSION_STATUS_REOPENED) {
                     $o .= $this->output->box_start('generalbox submissionaction');
-                    $urlparams = array('id' => $status->coursemoduleid, 'action' => 'editprevioussubmission');
+                    $urlparams = array('id' => $status->coursemoduleid,
+                                       'action' => 'editprevioussubmission',
+                                       'sesskey'=>sesskey());
                     $o .= $this->output->single_button(new moodle_url('/mod/assign/view.php', $urlparams),
                                                        get_string('addnewattemptfromprevious', 'assign'), 'get');
                     $o .= $this->output->box_start('boxaligncenter submithelp');
@@ -813,10 +815,10 @@ class mod_assign_renderer extends plugin_renderer_base {
                     // Edit previous feedback.
                     $returnparams = http_build_query($history->returnparams);
                     $urlparams = array('id' => $history->coursemoduleid,
-                                   'userid'=>$grade->userid,
+                                   'rownum'=>$history->rownum,
+                                   'useridlistid'=>$history->useridlistid,
                                    'attemptnumber'=>$grade->attemptnumber,
                                    'action'=>'grade',
-                                   'rownum'=>0,
                                    'returnaction'=>$history->returnaction,
                                    'returnparams'=>$returnparams);
                     $url = new moodle_url('/mod/assign/view.php', $urlparams);
index 45cf117..5ecf115 100644 (file)
@@ -139,10 +139,6 @@ class assign_submission_onlinetext extends assign_submission_plugin {
 
         $onlinetextsubmission = $this->get_onlinetext_submission($submission->id);
 
-        $text = format_text($data->onlinetext,
-                            $data->onlinetext_editor['format'],
-                            array('context'=>$this->assignment->get_context()));
-
         $fs = get_file_storage();
 
         $files = $fs->get_area_files($this->assignment->get_context()->id,
@@ -157,7 +153,8 @@ class assign_submission_onlinetext extends assign_submission_plugin {
             'objectid' => $submission->id,
             'other' => array(
                 'pathnamehashes' => array_keys($files),
-                'content' => trim($text)
+                'content' => trim($data->onlinetext),
+                'format' => $data->onlinetext_editor['format']
             )
         );
         $event = \assignsubmission_onlinetext\event\assessable_uploaded::create($params);
@@ -423,12 +420,9 @@ class assign_submission_onlinetext extends assign_submission_plugin {
         // Format the info for each submission plugin (will be logged).
         $onlinetextsubmission = $this->get_onlinetext_submission($submission->id);
         $onlinetextloginfo = '';
-        $text = format_text($onlinetextsubmission->onlinetext,
-                            $onlinetextsubmission->onlineformat,
-                            array('context'=>$this->assignment->get_context()));
         $onlinetextloginfo .= get_string('numwordsforlog',
                                          'assignsubmission_onlinetext',
-                                         count_words($text));
+                                         count_words($onlinetextsubmission->onlinetext));
 
         return $onlinetextloginfo;
     }
index 8d40bc3..ce2616d 100644 (file)
@@ -60,6 +60,7 @@ class assignsubmission_onlinetext_events_testcase extends advanced_testcase {
         $this->assertEquals($context->id, $event->contextid);
         $this->assertEquals($submission->id, $event->objectid);
         $this->assertEquals(array(), $event->other['pathnamehashes']);
+        $this->assertEquals(FORMAT_PLAIN, $event->other['format']);
         $this->assertEquals('Submission text', $event->other['content']);
         $expected = new stdClass();
         $expected->modulename = 'assign';
diff --git a/mod/assign/tests/behat/allow_another_attempt.feature b/mod/assign/tests/behat/allow_another_attempt.feature
new file mode 100644 (file)
index 0000000..eeaf5f0
--- /dev/null
@@ -0,0 +1,58 @@
+@mod @mod_assign
+Feature: In an assignment, students start a new attempt based on their previous one
+  In order to improve my submission
+  As a student
+  I need to submit my assignment editing an online form, receive feedback, and then improve my submission.
+
+  @javascript
+  Scenario: Submit a text online and edit the submission
+    Given the following "courses" exists:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assignment name |
+      | Description | Submit your online text |
+      | assignsubmission_onlinetext_enabled | 1 |
+      | assignsubmission_file_enabled | 0 |
+      | Attempts reopened | Manually |
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    When I press "Add submission"
+    And I fill the moodle form with:
+      | Online text | I'm the student first submission |
+    And I press "Save changes"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View/grade all submissions"
+    And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
+    And I fill the moodle form with:
+      | Allow another attempt | 1 |
+    And I press "Save changes"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I press "Add a new attempt based on previous submission"
+    And I press "Save changes"
+    Then I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View/grade all submissions"
+    And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
+    And I should see "I'm the student first submission"
diff --git a/mod/assign/tests/behat/comment_inline.feature b/mod/assign/tests/behat/comment_inline.feature
new file mode 100644 (file)
index 0000000..0de4a80
--- /dev/null
@@ -0,0 +1,50 @@
+@mod @mod_assign
+Feature: In an assignment, teachers can edit a students submission inline
+  In order to easily mark students assignments
+  As a teacher
+  I need to have a students submission text copied to the grading online form.
+
+  @javascript
+  Scenario: Submit a text online and edit the submission
+    Given the following "courses" exists:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assignment name |
+      | Description | Submit your online text |
+      | assignsubmission_onlinetext_enabled | 1 |
+      | assignsubmission_file_enabled | 0 |
+      | assignfeedback_comments_enabled | 1 |
+      | assignfeedback_comments_commentinline | 1 |
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I press "Add submission"
+    And I fill the moodle form with:
+      | Online text | I'm the student first submission |
+    And I press "Save changes"
+    And I log out
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View/grade all submissions"
+    And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
+    And I press "Save changes"
+    And I press "Continue"
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I should see "I'm the student first submission" in the "Feedback comments" "table_row"
diff --git a/mod/assign/tests/behat/edit_previous_feedback.feature b/mod/assign/tests/behat/edit_previous_feedback.feature
new file mode 100644 (file)
index 0000000..81f5bfd
--- /dev/null
@@ -0,0 +1,74 @@
+@mod @mod_assign
+Feature: In an assignment, teachers can edit feedback for a students previous submission attempt
+  In order to correct feedback for a previous submission attempt
+  As a teacher
+  I need to be able to edit the feedback for a students previous submission attempt.
+
+  @javascript
+  Scenario: Edit feedback for a students previous attempt.
+    Given the following "courses" exists:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+      | student2 | Student | 2 | student2@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assignment name |
+      | Description | Submit your online text |
+      | assignsubmission_onlinetext_enabled | 1 |
+      | assignfeedback_comments_enabled | 1 |
+      | Attempts reopened | Manually |
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I press "Add submission"
+    And I fill the moodle form with:
+      | Online text | I'm the student first submission |
+    And I press "Save changes"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View/grade all submissions"
+    And I click on "Grade Student 2" "link" in the "Student 2" "table_row"
+    And I fill the moodle form with:
+      | Grade | 49 |
+      | Feedback comments | I'm the teacher first feedback |
+      | Allow another attempt | Yes |
+    And I press "Save changes"
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I click on ".mod-assign-history-link" "css_element"
+    And I should see "I'm the teacher first feedback" in the "Feedback comments" "table_row"
+    And I log out
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I follow "View/grade all submissions"
+    And I click on "Grade Student 2" "link" in the "Student 2" "table_row"
+    And I click on ".mod-assign-history-link" "css_element"
+    And I follow "Edit the grade and feedback for attempt number 1"
+    And I fill the moodle form with:
+      | Grade | 50 |
+      | Feedback comments | I'm the teacher second feedback |
+    And I press "Save changes"
+    And I log out
+    Then I log in as "student2"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    And I click on ".mod-assign-history-link" "css_element"
+    And I should see "I'm the teacher second feedback" in the "Feedback comments" "table_row"
+    And I should see "50.00"
index 0ab0abf..766f851 100644 (file)
@@ -756,27 +756,28 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
     public function test_get_graders() {
         $this->create_extra_users();
         $this->setUser($this->editingteachers[0]);
-        $assign = $this->create_instance();
 
+        // Create an assignment with no groups.
+        $assign = $this->create_instance();
         $this->assertCount(self::DEFAULT_TEACHER_COUNT +
                            self::DEFAULT_EDITING_TEACHER_COUNT +
                            self::EXTRA_TEACHER_COUNT +
                            self::EXTRA_EDITING_TEACHER_COUNT,
                            $assign->testable_get_graders($this->students[0]->id));
 
-        $assign = $this->create_instance();
         // Force create an assignment with SEPARATEGROUPS.
-        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
-        $params = array('course'=>$this->course->id);
-        $instance = $generator->create_instance($params);
-        $cm = get_coursemodule_from_instance('assign', $instance->id);
-        set_coursemodule_groupmode($cm->id, SEPARATEGROUPS);
-        $cm->groupmode = SEPARATEGROUPS;
-        $context = context_module::instance($cm->id);
-        $assign = new testable_assign($context, $cm, $this->course);
+        $data = new stdClass();
+        $data->courseid = $this->course->id;
+        $data->name = 'Grouping';
+        $groupingid = groups_create_grouping($data);
+        groups_assign_grouping($groupingid, $this->groups[0]->id);
+        $assign = $this->create_instance(array('groupingid' => $groupingid, 'groupmode' => SEPARATEGROUPS));
 
         $this->setUser($this->students[1]);
         $this->assertCount(4, $assign->testable_get_graders($this->students[0]->id));
+        // Note the second student is in a group that is not in the grouping.
+        // This means that we get all graders that are not in a group in the grouping.
+        $this->assertCount(10, $assign->testable_get_graders($this->students[1]->id));
     }
 
     public function test_group_members_only() {
@@ -1493,6 +1494,77 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $CFG->usecomments = $commentconfig;
     }
 
+    /**
+     * Testing for comment inline settings
+     */
+    public function test_feedback_comment_commentinline() {
+        global $CFG;
+
+        $sourcetext = "Hello!
+
+I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
+
+URL outside a tag: https://moodle.org/logo/logo-240x60.gif
+Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
+
+External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
+External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
+Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
+Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
+Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
+Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
+";
+
+        // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
+        $filteredtext = "Hello!
+
+I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
+
+URL outside a tag: https://moodle.org/logo/logo-240x60.gif
+Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
+
+External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
+External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
+Internal link 1:
+Internal link 2:
+Anchor link 1:Link text
+Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
+";
+
+        $this->setUser($this->editingteachers[0]);
+        $params = array('assignsubmission_onlinetext_enabled' => 1,
+                        'assignfeedback_comments_enabled' => 1,
+                        'assignfeedback_comments_commentinline' => 1);
+        $assign = $this->create_instance($params);
+
+        $this->setUser($this->students[0]);
+        // Add a submission but don't submit now.
+        $submission = $assign->get_user_submission($this->students[0]->id, true);
+        $data = new stdClass();
+
+        // Test the internal link is stripped, but the external one is not.
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>$sourcetext,
+                                         'format'=>FORMAT_MOODLE);
+
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        $this->setUser($this->editingteachers[0]);
+
+        $data = new stdClass();
+        require_once($CFG->dirroot . '/mod/assign/gradeform.php');
+        $pagination = array('userid'=>$this->students[0]->id,
+                            'rownum'=>0,
+                            'last'=>true,
+                            'useridlistid'=>time(),
+                            'attemptnumber'=>0);
+        $formparams = array($assign, $data, $pagination);
+        $mform = new mod_assign_grade_form(null, $formparams);
+
+        $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
+    }
+
     /**
      * Testing for feedback comment plugin settings
      */
index 44c2f89..601d8b5 100644 (file)
@@ -1,5 +1,12 @@
 This files describes API changes in the assign code.
 
+=== 2.6.1 ===
+
+* format_text() is no longer used for formating assignment content to be used in events (assign_submission_onlinetext::save()) or
+  the word count (assign_submission_onlinetext::format_for_log()) in mod/assign/submission/onlinetext/locallib.php. format_text()
+  should only be used when displaying information to the screen. It was being used incorrectly before in these areas. Plugins using
+  the event assessable_uploaded() should use file_rewrite_pluginfile_urls() to translate the text back to the desired output.
+
 === 2.6 ===
 * To see submission/grades of inactive users, user should have moodle/course:viewsuspendedusers capability.
 * count_* functions will return only active participants.
index 266f34f..1e8f15b 100644 (file)
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
-$module->version  = 2013110500;    // The current module version (Date: YYYYMMDDXX).
+$module->version  = 2013110501;    // The current module version (Date: YYYYMMDDXX).
 $module->requires = 2013110500;    // Requires this Moodle version.
 $module->cron     = 60;
 
index 499a932..5bf9d03 100644 (file)
@@ -1933,7 +1933,7 @@ class assignment_base {
         $context = context_module::instance($this->cm->id);
 
         // Get ids of users enrolled in the given course.
-        list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
+        list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $groupid);
         $params['assignmentid'] = $this->cm->instance;
 
         // Get ids of users enrolled in the given course.
index c7f3f0f..37f8d1d 100644 (file)
@@ -339,7 +339,7 @@ class assignment_upload extends assignment_base {
         $context = context_module::instance($this->cm->id);
 
         // Get ids of users enrolled in the given course.
-        list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
+        list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $groupid);
         $params['assignmentid'] = $this->cm->instance;
 
         $query = '';
index 039071c..023109f 100644 (file)
@@ -136,7 +136,7 @@ class assignment_uploadsingle extends assignment_base {
         $context = context_module::instance($this->cm->id);
 
         // Get ids of users enrolled in the given course.
-        list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
+        list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $groupid);
         $params['assignmentid'] = $this->cm->instance;
 
         // Get ids of users enrolled in the given course.
@@ -378,7 +378,7 @@ class assignment_uploadsingle extends assignment_base {
                 foreach ($files as $file) {
                     $filename = $file->get_filename();
                     $mimetype = $file->get_mimetype();
-                    $link = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment', 'submission/'.$submission->id.'/'.$filename);
+                    $link = moodle_url::make_pluginfile_url($this->context->id, 'mod_assignment', 'submission', $submission->id, $file->get_filepath(), $filename);
                     $filenode->add($filename, $link, navigation_node::TYPE_SETTING, null, null, new pix_icon(file_file_icon($file), ''));
                 }
             }
index 255a7c7..6fabde1 100644 (file)
@@ -44,7 +44,7 @@ $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assi
 if (!file_exists($classfile)) {
     throw new moodle_exception('unsupportedsubplugin', 'assignment', new moodle_url('/course/view.php', array('id' => $course->id)), $assignment->assignmenttype);
 }
-require ($classfile);
+require_once($classfile);
 $assignmentclass = "assignment_$assignment->assignmenttype";
 $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
 
index 00805bf..caa8158 100644 (file)
@@ -121,12 +121,12 @@ class moodle1_mod_book_handler extends moodle1_mod_handler {
      * @param array $data
      */
     public function process_book_chapters($data) {
-        $this->write_xml('chapter', $data, array('/chapter/id'));
-
-        // convert chapter files
+        // Convert chapter files.
         $this->fileman->filearea = 'chapter';
         $this->fileman->itemid   = $data['id'];
         $data['content'] = moodle1_converter::migrate_referenced_files($data['content'], $this->fileman);
+
+        $this->write_xml('chapter', $data, array('/chapter/id'));
     }
 
     /**
index 4844023..fdcfc56 100644 (file)
@@ -63,7 +63,7 @@ class data_field_url extends data_field_base {
             $str .= '<label class="accesshide" for="' . $fieldid . '">'. $this->field->name .'</label>';
             $str .= '<input type="text" name="field_'.$this->field->id.'_0" id="'.$fieldid.'" value="'.s($url).'" size="60" />';
             if (count($options->repositories) > 0) {
-                $str .= '<button id="filepicker-button-'.$options->client_id.'" style="display:none">'.$straddlink.'</button>';
+                $str .= '<button id="filepicker-button-'.$options->client_id.'" class="visibleifjs">'.$straddlink.'</button>';
             }
         }
 
@@ -72,7 +72,6 @@ class data_field_url extends data_field_base {
 
         $module = array('name'=>'data_urlpicker', 'fullpath'=>'/mod/data/data.js', 'requires'=>array('core_filepicker'));
         $PAGE->requires->js_init_call('M.data_urlpicker.init', array($options), true, $module);
-        $PAGE->requires->js_function_call('show_item', array('filepicker-button-'.$options->client_id));
 
         $str .= '</div>';
         return $str;
index e9480c9..57cb932 100644 (file)
@@ -39,3 +39,4 @@ Feature: Add scorm activity
     And I switch to "contentFrame" iframe
     And I should see "Play of the game"
     And I switch to the main frame
+    And I follow "Course 1"
diff --git a/mod/url/classes/event/course_module_instance_list_viewed.php b/mod/url/classes/event/course_module_instance_list_viewed.php
new file mode 100644 (file)
index 0000000..d06dea4
--- /dev/null
@@ -0,0 +1,31 @@
+<?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/>.
+
+/**
+ * The mod_url instance list viewed event.
+ *
+ * @package    mod_url
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_url\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
+
+}
diff --git a/mod/url/classes/event/course_module_viewed.php b/mod/url/classes/event/course_module_viewed.php
new file mode 100644 (file)
index 0000000..189170f
--- /dev/null
@@ -0,0 +1,41 @@
+<?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/>.
+
+/**
+ * The mod_url course module viewed event.
+ *
+ * @package    mod_url
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_url\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class course_module_viewed extends \core\event\course_module_viewed {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'url';
+        $this->data['crud'] = 'r';
+        $this->data['level'] = self::LEVEL_PARTICIPATING;
+    }
+}
index abec7ce..668e418 100644 (file)
@@ -33,7 +33,11 @@ $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
 require_course_login($course, true);
 $PAGE->set_pagelayout('incourse');
 
-add_to_log($course->id, 'url', 'view all', "index.php?id=$course->id", '');
+$params = array(
+    'context' => context_course::instance($course->id)
+);
+$event = \mod_url\event\course_module_instance_list_viewed::create($params);
+$event->trigger();
 
 $strurl       = get_string('modulename', 'url');
 $strurls      = get_string('modulenameplural', 'url');
index ecf6b9d..caf975e 100644 (file)
@@ -47,7 +47,14 @@ require_course_login($course, true, $cm);
 $context = context_module::instance($cm->id);
 require_capability('mod/url:view', $context);
 
-add_to_log($course->id, 'url', 'view', 'view.php?id='.$cm->id, $url->id, $cm->id);
+$params = array(
+    'context' => $context,
+    'objectid' => $url->id,
+    'courseid' => $course->id
+);
+$event = \mod_url\event\course_module_viewed::create($params);
+$event->add_record_snapshot('url', $url);
+$event->trigger();
 
 // Update 'viewed' state if required by completion system
 $completion = new completion_info($course);
index 2534f40..5917a40 100644 (file)
@@ -300,12 +300,14 @@ class mod_wiki_events_testcase extends advanced_testcase {
      * Test page_deleted and page_version_deleted and page_locks_deleted event.
      */
     public function test_page_deleted() {
-        global $USER;
+        global $DB;
 
         $this->setUp();
 
         $page = $this->wikigenerator->create_first_page($this->wiki);
         $context = context_module::instance($this->wiki->cmid);
+        $oldversions = $DB->get_records('wiki_versions', array('pageid' => $page->id));
+        $oldversion = array_shift($oldversions);
 
         // Triggering and capturing the event.
         $sink = $this->redirectEvents();
@@ -317,7 +319,8 @@ class mod_wiki_events_testcase extends advanced_testcase {
         // Checking that the event contains the page_version_deleted event.
         $this->assertInstanceOf('\mod_wiki\event\page_version_deleted', $event);
         $this->assertEquals($context, $event->get_context());
-        $this->assertEquals($page->id, $event->objectid);
+        $this->assertEquals($page->id, $event->other['pageid']);
+        $this->assertEquals($oldversion->id, $event->objectid);
         $expected = array($this->course->id, 'wiki', 'admin', 'admin.php?pageid=' .  $page->id,  $page->id, $this->wiki->cmid);
         $this->assertEventLegacyLogData($expected, $event);
 
index 4325722..afcfa7e 100644 (file)
@@ -27,7 +27,7 @@ class portfolio_plugin_googledocs extends portfolio_plugin_push_base {
     private $googleoauth = null;
 
     public function supported_formats() {
-        return array(PORTFOLIO_FORMAT_FILE);
+        return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML);
     }
 
     public static function get_name() {
index a0274f4..726405a 100644 (file)
@@ -551,6 +551,8 @@ class repository_dropbox extends repository {
     }
 
     public function sync_reference(stored_file $file) {
+        global $CFG;
+
         if ($file->get_referencelastsync() + DAYSECS > time()) {
             // Synchronise not more often than once a day.
             return false;
index 898b8db..d3d949f 100644 (file)
@@ -582,27 +582,23 @@ M.core_filepicker.init = function(Y, options) {
                 method: 'POST',
                 on: {
                     complete: function(id,o,p) {
-                        if (!o) {
-                            // TODO
-                            alert('IO FATAL');
-                            return;
-                        }
                         var data = null;
                         try {
                             data = Y.JSON.parse(o.responseText);
                         } catch(e) {
-                            scope.print_msg(M.str.repository.invalidjson, 'error');
-                            scope.display_error(M.str.repository.invalidjson+'<pre>'+stripHTML(o.responseText)+'</pre>', 'invalidjson')
-                            return;
+                            if (o && o.status && o.status > 0) {
+                                Y.use('moodle-core-notification-exception', function() {
+                                    return new M.core.exception(e);
+                                });
+                                return;
+                            }
                         }
                         // error checking
                         if (data && data.error) {
-                            scope.print_msg(data.error, 'error');
-                            if (args.onerror) {
-                                args.onerror(id,data,p);
-                            } else {
-                                this.fpnode.one('.fp-content').setContent('');
-                            }
+                            Y.use('moodle-core-notification-ajaxexception', function () {
+                                return new M.core.ajaxException(data);
+                            });
+                            this.fpnode.one('.fp-content').setContent('');
                             return;
                         } else {
                             if (data.msg) {
index bd2c762..fe4316d 100644 (file)
@@ -74,6 +74,8 @@ $repooptions = array(
     'ajax' => true,
     'mimetypes' => $accepted_types
 );
+
+ajax_capture_output();
 $repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions);
 
 // Check permissions
@@ -97,6 +99,7 @@ switch ($action) {
         if ($repo->check_login()) {
             $listing = repository::prepare_listing($repo->get_listing($req_path, $page));
             $listing['repo_id'] = $repo_id;
+            ajax_check_captured_output();
             echo json_encode($listing);
             break;
         } else {
@@ -105,23 +108,27 @@ switch ($action) {
     case 'login':
         $listing = $repo->print_login();
         $listing['repo_id'] = $repo_id;
+        ajax_check_captured_output();
         echo json_encode($listing);
         break;
     case 'logout':
         $logout = $repo->logout();
         $logout['repo_id'] = $repo_id;
+        ajax_check_captured_output();
         echo json_encode($logout);
         break;
     case 'searchform':
         $search_form['repo_id'] = $repo_id;
         $search_form['form'] = $repo->print_search();
         $search_form['allowcaching'] = true;
+        ajax_check_captured_output();
         echo json_encode($search_form);
         break;
     case 'search':
         $search_result = repository::prepare_listing($repo->search($search_text, (int)$page));
         $search_result['repo_id'] = $repo_id;
         $search_result['issearchresult'] = true;
+        ajax_check_captured_output();
         echo json_encode($search_result);
         break;
     case 'download':
@@ -160,6 +167,7 @@ switch ($action) {
             $info['file'] = $saveas_filename;
             $info['type'] = 'link';
             $info['url'] = $link;
+            ajax_check_captured_output();
             echo json_encode($info);
             die;
         } else {
@@ -251,6 +259,7 @@ switch ($action) {
                 // You can cache reository file in this callback
                 // or complete other tasks.
                 $repo->cache_file_by_reference($reference, $storedfile);
+                ajax_check_captured_output();
                 echo json_encode($event);
                 die;
             } else if ($repo->has_moodle_files()) {
@@ -261,6 +270,7 @@ switch ($action) {
                 // {@link repository::copy_to_area()}.
                 $fileinfo = $repo->copy_to_area($reference, $record, $maxbytes, $areamaxbytes);
 
+                ajax_check_captured_output();
                 echo json_encode($fileinfo);
                 die;
             } else {
@@ -286,12 +296,14 @@ switch ($action) {
                     $info['e'] = get_string('error', 'moodle');
                 }
             }
+            ajax_check_captured_output();
             echo json_encode($info);
             die;
         }
         break;
     case 'upload':
         $result = $repo->upload($saveas_filename, $maxbytes);
+        ajax_check_captured_output();
         echo json_encode($result);
         break;
 
@@ -304,6 +316,7 @@ switch ($action) {
         $newfilename = required_param('newfilename', PARAM_FILE);
 
         $info = repository::overwrite_existing_draftfile($itemid, $filepath, $filename, $newfilepath, $newfilename);
+        ajax_check_captured_output();
         echo json_encode($info);
         break;
 
@@ -311,6 +324,7 @@ switch ($action) {
         // delete tmp file
         $newfilepath = required_param('newfilepath', PARAM_PATH);
         $newfilename = required_param('newfilename', PARAM_FILE);
+        ajax_check_captured_output();
         echo json_encode(repository::delete_tempfile_from_draft($itemid, $newfilepath, $newfilename));
 
         break;
index 81563a3..b222f04 100644 (file)
@@ -799,6 +799,7 @@ body.tag .managelink {padding: 5px;}
 .corelightbox img {position:fixed;top:50%; left: 50%;}
 
 .mod-indent {display:table-cell;}
+.label .mod-indent {float:left; padding-top:20px}
 .mod-indent-1    {width:30px;}
 .mod-indent-2    {width:60px;}
 .mod-indent-3    {width:90px;}
index 047a459..4039a70 100644 (file)
     padding-left: 24px;
 }
 
+.section .label .mod-indent-outer {
+    padding-left: 24px;
+    display: block;
+}
+
 .dir-rtl .section .activity .mod-indent-outer {
     padding-left: auto;
     padding-right: 24px;
     padding-right: 32px;
     height: 2em;
     display: table-cell;
+
+}
+
+.sitetopic .section .label .activityinstance,
+.course-content .section .label .activityinstance,
+.sitetopic .section .label .contentwithoutlink,
+.course-content .section .label .contentwithoutlink {
+    padding-right: 32px;
+    display: block;
+    height: inherit;
 }
 
 .dir-rtl .sitetopic .section .activity .activityinstance,
index 84b7107..636cf8f 100644 (file)
@@ -51,8 +51,8 @@ echo $OUTPUT->doctype() ?>
 
     <header id="page-header" class="clearfix">
         <div id="page-navbar" class="clearfix">
-            <div class="breadcrumb-nav"><?php echo $OUTPUT->navbar(); ?></div>
-            <nav class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></nav>
+            <nav class="breadcrumb-nav"><?php echo $OUTPUT->navbar(); ?></nav>
+            <div class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></div>
         </div>
         <?php echo $OUTPUT->page_heading(); ?>
         <div id="course-header">