Merge branch 'MDL-38170-master' of git://github.com/sammarshallou/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 4 Mar 2013 15:25:00 +0000 (16:25 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 4 Mar 2013 15:25:00 +0000 (16:25 +0100)
124 files changed:
blocks/community/renderer.php
blocks/navigation/yui/navigation/navigation.js
cache/classes/factory.php
cache/classes/helper.php
cache/classes/loaders.php
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
calendar/managesubscriptions.php
course/editcategory.php
course/externallib.php
course/lib.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/yui/modchooser/modchooser.js
enrol/flatfile/adminlib.php [new file with mode: 0644]
enrol/flatfile/db/install.php [new file with mode: 0644]
enrol/flatfile/settings.php
enrol/manual/lib.php
enrol/manual/manage.php
filter/activitynames/filter.php
grade/edit/outcome/import.php
grade/report/grader/module.js
install/lang/he/moodle.php
lang/en/block.php
lang/en/plugin.php
lang/en/repository.php
lib/blocklib.php
lib/conditionlib.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/dmllib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filestorage/stored_file.php
lib/filestorage/tests/file_storage_test.php
lib/form/dndupload.js
lib/form/filemanager.js
lib/form/yui/shortforms/shortforms.js
lib/form/yui/showadvanced/showadvanced.js
lib/moodlelib.php
lib/outputrequirementslib.php
lib/pear/Crypt/CHAP.php
lib/pluginlib.php
lib/setup.php
lib/setuplib.php
lib/testing/generator/data_generator.php
lib/tests/conditionlib_test.php
lib/tests/setuplib_test.php
lib/upgradelib.php
lib/xhprof/readme_moodle.txt
lib/xhprof/xhprof_html/callgraph.php
lib/xhprof/xhprof_html/css/xhprof.css
lib/xhprof/xhprof_html/index.php
lib/xhprof/xhprof_html/jquery/indicator.gif
lib/xhprof/xhprof_html/typeahead.php
lib/xhprof/xhprof_lib/utils/callgraph_utils.php
lib/xhprof/xhprof_lib/utils/xhprof_runs.php
mod/assign/gradingtable.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/submission/comments/locallib.php
mod/assign/tests/base_test.php [new file with mode: 0644]
mod/assign/tests/generator/lib.php
mod/assign/tests/lib_test.php
mod/assign/tests/locallib_test.php
mod/assign/tests/upgradelib_test.php
mod/assign/view.php
mod/assignment/type/upload/assignment.class.php
mod/data/field/latlong/field.class.php
mod/data/import.php
mod/folder/backup/moodle2/backup_folder_stepslib.php
mod/folder/db/install.xml
mod/folder/db/upgrade.php
mod/folder/edit.php
mod/folder/lang/en/folder.php
mod/folder/lib.php
mod/folder/mod_form.php
mod/folder/module.js
mod/folder/renderer.php
mod/folder/version.php
mod/folder/view.php
mod/forum/lib.php
mod/forum/subscribe.php
mod/lesson/report.php
mod/quiz/styles.css
mod/workshop/form/assessment_form.php
notes/delete.php
notes/edit.php
notes/externallib.php
notes/lib.php
notes/tests/externallib_test.php
repository/coursefiles/lib.php
repository/equella/lib.php
repository/filepicker.js
repository/filepicker.php
repository/filesystem/lib.php
repository/flickr_public/lib.php
repository/lib.php
repository/local/lib.php
repository/merlot/lib.php
repository/recent/lib.php
repository/s3/lib.php
repository/tests/repository_test.php
repository/upgrade.txt
repository/upload/lib.php
repository/url/lib.php
repository/user/lib.php
repository/webdav/lib.php
repository/wikimedia/lib.php
repository/youtube/lib.php
theme/base/style/admin.css
theme/base/version.php
theme/formal_white/lib.php
theme/formal_white/style/formal_white.css
user/addnote.php
user/groupaddnote.php
user/view.php
version.php

index 4fb6c50..dca9839 100644 (file)
@@ -272,7 +272,7 @@ class block_community_renderer extends plugin_renderer_base {
                         'downloadcourseid' => $course->id, 'huburl' => $huburl,
                         'coursefullname' => $course->fullname, 'backupsize' => $course->backupsize);
                     $downloadurl = new moodle_url("/blocks/community/communitycourse.php", $params);
-                    $downloadbuttonhtml = html_writer::tag('a', get_string('download', 'block_community'),
+                    $downloadbuttonhtml = html_writer::tag('a', get_string('install', 'block_community'),
                                     array('href' => $downloadurl, 'class' => 'centeredbutton, hubcoursedownload'));
                 }
 
index d2631dd..b513e56 100644 (file)
@@ -120,6 +120,10 @@ TREE.prototype = {
      * The tree's ID, normally its block instance id.
      */
     id : null,
+    /**
+     * An array of initialised branches.
+     */
+    branches : [],
     /**
      * Initialise the tree object when its first created.
      */
@@ -134,9 +138,8 @@ TREE.prototype = {
         }
 
         // Delegate event to toggle expansion
-        var self = this;
-        Y.delegate('click', function(e){self.toggleExpansion(e);}, node.one('.block_tree'), '.tree_item.branch');
-        Y.delegate('actionkey', function(e){self.toggleExpansion(e);}, node.one('.block_tree'), '.tree_item.branch');
+        Y.delegate('click', this.toggleExpansion, node.one('.block_tree'), '.tree_item.branch', this);
+        Y.delegate('actionkey', this.toggleExpansion, node.one('.block_tree'), '.tree_item.branch', this);
 
         // Gather the expandable branches ready for initialisation.
         var expansions = [];
@@ -147,7 +150,7 @@ TREE.prototype = {
         }
         // Establish each expandable branch as a tree branch.
         for (var i in expansions) {
-            new BRANCH({
+            var branch = new BRANCH({
                 tree:this,
                 branchobj:expansions[i],
                 overrides : {
@@ -157,6 +160,12 @@ TREE.prototype = {
                 }
             }).wire();
             M.block_navigation.expandablebranchcount++;
+            this.branches[branch.get('id')] = branch;
+        }
+        if (M.block_navigation.expandablebranchcount > 0) {
+            // Delegate some events to handle AJAX loading.
+            Y.delegate('click', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
+            Y.delegate('actionkey', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
         }
 
         // Call the generic blocks init method to add all the generic stuff
@@ -164,6 +173,14 @@ TREE.prototype = {
             this.initialise_block(Y, node);
         }
     },
+    /**
+     * Fire actions for a branch when an event occurs.
+     */
+    fire_branch_action : function(event) {
+        var id = event.currentTarget.getAttribute('id');
+        var branch = this.branches[id];
+        branch.ajaxLoad(event);
+    },
     /**
      * This is a callback function responsible for expanding and collapsing the
      * branches of the tree. It is delegated to rather than multiple event handles.
@@ -271,11 +288,6 @@ BRANCH.prototype = {
      * The node for this branch (p)
      */
     node : null,
-    /**
-     * A reference to the ajax load event handlers when created.
-     */
-    event_ajaxload : null,
-    event_ajaxload_actionkey : null,
     /**
      * Initialises the branch when it is first created.
      */
@@ -378,8 +390,8 @@ BRANCH.prototype = {
             return false;
         }
         if (this.get('expandable')) {
-            this.event_ajaxload = this.node.on('ajaxload|click', this.ajaxLoad, this);
-            this.event_ajaxload_actionkey = this.node.on('actionkey', this.ajaxLoad, this);
+            this.node.setAttribute('data-expandable', '1');
+            this.node.setAttribute('data-loaded', '0');
         }
         return this;
     },
@@ -407,15 +419,21 @@ BRANCH.prototype = {
             e.stopPropagation();
         }
         if (e.type = 'actionkey' && e.action == 'enter' && e.target.test('A')) {
-            this.event_ajaxload_actionkey.detach();
-            this.event_ajaxload.detach();
-            return true; // no ajaxLoad for enter
+            // No ajaxLoad for enter.
+            this.node.setAttribute('data-expandable', '0');
+            this.node.setAttribute('data-loaded', '1');
+            return true;
         }
 
         if (this.node.hasClass('loadingbranch')) {
+            // Already loading. Just skip.
             return true;
         }
 
+        if (this.node.getAttribute('data-loaded') === '1') {
+            // We've already loaded this stuff.
+            return true;
+        }
         this.node.addClass('loadingbranch');
 
         var params = {
@@ -442,8 +460,7 @@ BRANCH.prototype = {
      */
     ajaxProcessResponse : function(tid, outcome) {
         this.node.removeClass('loadingbranch');
-        this.event_ajaxload.detach();
-        this.event_ajaxload_actionkey.detach();
+        this.node.setAttribute('data-loaded', '1');
         try {
             var object = Y.JSON.parse(outcome.responseText);
             if (object.children && object.children.length > 0) {
@@ -460,7 +477,6 @@ BRANCH.prototype = {
                     && coursecount >= M.block_navigation.courselimit) {
                     this.addViewAllCoursesChild(this);
                 }
-                this.get('tree').toggleExpansion({target:this.node});
                 return true;
             }
         } catch (ex) {
@@ -478,6 +494,7 @@ BRANCH.prototype = {
         // Make the new branch into an object
         var branch = new BRANCH({tree:this.get('tree'), branchobj:branchobj});
         if (branch.draw(this.getChildrenUL())) {
+            this.get('tree').branches[branch.get('id')] = branch;
             branch.wire();
             var count = 0, i, children = branch.get('children');
             for (i in children) {
index 24c7c1e..43f075b 100644 (file)
@@ -75,6 +75,12 @@ class cache_factory {
      */
     protected $cachesfromparams = array();
 
+    /**
+     * An array of stores organised by definitions.
+     * @var array
+     */
+    protected $definitionstores = array();
+
     /**
      * An array of instantiated stores.
      * @var array
@@ -272,9 +278,27 @@ class cache_factory {
         // order to address the issues.
         $store = $this->stores[$name]->create_clone($details);
         $store->initialise($definition);
+        $definitionid = $definition->get_id();
+        if (!isset($this->definitionstores[$definitionid])) {
+            $this->definitionstores[$definitionid] = array();
+        }
+        $this->definitionstores[$definitionid][] = $store;
         return $store;
     }
 
+    /**
+     * Returns an array of cache stores that have been initialised for use in definitions.
+     * @param cache_definition $definition
+     * @return array
+     */
+    public function get_store_instances_in_use(cache_definition $definition) {
+        $id = $definition->get_id();
+        if (!isset($this->definitionstores[$id])) {
+            return array();
+        }
+        return $this->definitionstores[$id];
+    }
+
     /**
      * Creates a cache config instance with the ability to write if required.
      *
index a8f3a26..20e2f6d 100644 (file)
@@ -312,15 +312,18 @@ class cache_helper {
         foreach ($instance->get_definitions() as $name => $definitionarr) {
             $definition = cache_definition::load($name, $definitionarr);
             if ($definition->invalidates_on_event($event)) {
-                // Create the cache.
-                $cache = $factory->create_cache($definition);
-                // Initialise, in case of a store.
-                if ($cache instanceof cache_store) {
-                    $cache->initialise($definition);
+                // Check if this definition would result in a persistent loader being in use.
+                if ($definition->should_be_persistent()) {
+                    // There may be a persistent cache loader. Lets purge that first so that any persistent data is removed.
+                    $cache = $factory->create_cache_from_definition($definition->get_component(), $definition->get_area());
+                    $cache->purge();
+                }
+                // Get all of the store instances that are in use for this store.
+                $stores = $factory->get_store_instances_in_use($definition);
+                foreach ($stores as $store) {
+                    // Purge each store individually.
+                    $store->purge();
                 }
-                // Purge the cache.
-                $cache->purge();
-
                 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
                 if ($invalidationeventset === false) {
                     // Get the event invalidation cache.
index 4835360..6402e2c 100644 (file)
@@ -1094,10 +1094,11 @@ class cache_application extends cache implements cache_loader_with_locking {
             $cache = cache::make('core', 'eventinvalidation');
             $events = $cache->get_many($definition->get_invalidation_events());
             $todelete = array();
+            $purgeall = false;
             // Iterate the returned data for the events.
             foreach ($events as $event => $keys) {
                 if ($keys === false) {
-                    // There are no keys.
+                    // No data to be invalidated yet.
                     continue;
                 }
                 // Look at each key and check the timestamp.
@@ -1105,11 +1106,18 @@ class cache_application extends cache implements cache_loader_with_locking {
                     // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
                     // invalidation and now)then we need to invaliate the key.
                     if ($timestamp >= $lastinvalidation) {
-                        $todelete[] = $key;
+                        if ($key === 'purged') {
+                            $purgeall = true;
+                            break;
+                        } else {
+                            $todelete[] = $key;
+                        }
                     }
                 }
             }
-            if (!empty($todelete)) {
+            if ($purgeall) {
+                $this->purge();
+            } else if (!empty($todelete)) {
                 $todelete = array_unique($todelete);
                 $this->delete_many($todelete);
             }
@@ -1435,6 +1443,7 @@ class cache_session extends cache {
             $cache = cache::make('core', 'eventinvalidation');
             $events = $cache->get_many($definition->get_invalidation_events());
             $todelete = array();
+            $purgeall = false;
             // Iterate the returned data for the events.
             foreach ($events as $event => $keys) {
                 if ($keys === false) {
@@ -1446,11 +1455,18 @@ class cache_session extends cache {
                     // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
                     // invalidation and now)then we need to invaliate the key.
                     if ($timestamp >= $lastinvalidation) {
-                        $todelete[] = $key;
+                        if ($key === 'purged') {
+                            $purgeall = true;
+                            break;
+                        } else {
+                            $todelete[] = $key;
+                        }
                     }
                 }
             }
-            if (!empty($todelete)) {
+            if ($purgeall) {
+                $this->purge();
+            } else if (!empty($todelete)) {
                 $todelete = array_unique($todelete);
                 $this->delete_many($todelete);
             }
index 193f5c7..906b355 100644 (file)
@@ -116,8 +116,8 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
         foreach ($this->servers as $server) {
             $this->connection->addServer($server[0], $server[1], true, $server[2]);
             // Test the connection to this server.
-            $this->isready = @$this->connection->set("ping", 'ping', MEMCACHE_COMPRESSED, 1);
         }
+        $this->isready = @$this->connection->set($this->parse_key('ping'), 'ping', MEMCACHE_COMPRESSED, 1);
     }
 
     /**
@@ -191,6 +191,20 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
         return self::MODE_APPLICATION + self::MODE_SESSION;
     }
 
+    /**
+     * Parses the given key to make it work for this memcache backend.
+     *
+     * @param string $key The raw key.
+     * @return string The resulting key.
+     */
+    protected function parse_key($key) {
+        if (strlen($key) > 245) {
+            $key = '_sha1_'.sha1($key);
+        }
+        $key = 'mdl_'.$key;
+        return $key;
+    }
+
     /**
      * Retrieves an item from the cache store given its key.
      *
@@ -198,7 +212,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
      * @return mixed The data that was associated with the key, or false if the key did not exist.
      */
     public function get($key) {
-        return $this->connection->get($key);
+        return $this->connection->get($this->parse_key($key));
     }
 
     /**
@@ -211,16 +225,23 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
      *      be set to false.
      */
     public function get_many($keys) {
-        $result = $this->connection->get($keys);
+        $mkeys = array();
+        foreach ($keys as $key) {
+            $mkeys[$key] = $this->parse_key($key);
+        }
+        $result = $this->connection->get($mkeys);
         if (!is_array($result)) {
             $result = array();
         }
-        foreach ($keys as $key) {
-            if (!array_key_exists($key, $result)) {
-                $result[$key] = false;
+        $return = array();
+        foreach ($mkeys as $key => $mkey) {
+            if (!array_key_exists($mkey, $result)) {
+                $return[$key] = false;
+            } else {
+                $return[$key] = $result[$mkey];
             }
         }
-        return $result;
+        return $return;
     }
 
     /**
@@ -231,7 +252,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
      * @return bool True if the operation was a success false otherwise.
      */
     public function set($key, $data) {
-        return $this->connection->set($key, $data, MEMCACHE_COMPRESSED, $this->definition->get_ttl());
+        return $this->connection->set($this->parse_key($key), $data, MEMCACHE_COMPRESSED, $this->definition->get_ttl());
     }
 
     /**
@@ -245,7 +266,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
     public function set_many(array $keyvaluearray) {
         $count = 0;
         foreach ($keyvaluearray as $pair) {
-            if ($this->connection->set($pair['key'], $pair['value'], MEMCACHE_COMPRESSED, $this->definition->get_ttl())) {
+            if ($this->connection->set($this->parse_key($pair['key']), $pair['value'], MEMCACHE_COMPRESSED, $this->definition->get_ttl())) {
                 $count++;
             }
         }
@@ -259,7 +280,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
      * @return bool Returns true if the operation was a success, false otherwise.
      */
     public function delete($key) {
-        return $this->connection->delete($key);
+        return $this->connection->delete($this->parse_key($key));
     }
 
     /**
index 77d2275..98d3955 100644 (file)
@@ -140,8 +140,8 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
                 $this->connection->setOption($key, $value);
             }
             $this->connection->addServers($this->servers);
-            $this->isready = @$this->connection->set("ping", 'ping', 1);
         }
+        $this->isready = @$this->connection->set("ping", 'ping', 1);
     }
 
     /**
index 545986a..edef90d 100644 (file)
@@ -422,7 +422,7 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'ttltest',
-            'ttl' => -10
+            'ttl' => -86400 // Set to a day in the past to be extra sure.
         ));
         $cache = cache::make('phpunit', 'ttltest');
         $this->assertInstanceOf('cache_application', $cache);
@@ -627,6 +627,66 @@ class cache_phpunit_tests extends advanced_testcase {
                 'crazyevent'
             )
         ));
+        $instance->phpunit_add_definition('phpunit/eventpurgetestpersistent', array(
+            'mode' => cache_store::MODE_APPLICATION,
+            'component' => 'phpunit',
+            'area' => 'eventpurgetestpersistent',
+            'persistent' => true,
+            'invalidationevents' => array(
+                'crazyevent'
+            )
+        ));
+        $cache = cache::make('phpunit', 'eventpurgetest');
+
+        $this->assertTrue($cache->set('testkey1', 'test data 1'));
+        $this->assertEquals('test data 1', $cache->get('testkey1'));
+        $this->assertTrue($cache->set('testkey2', 'test data 2'));
+        $this->assertEquals('test data 2', $cache->get('testkey2'));
+
+        // Purge the event.
+        cache_helper::purge_by_event('crazyevent');
+
+        // Check things have been removed.
+        $this->assertFalse($cache->get('testkey1'));
+        $this->assertFalse($cache->get('testkey2'));
+
+        // Now test the persistent cache.
+        $cache = cache::make('phpunit', 'eventpurgetestpersistent');
+        $this->assertTrue($cache->set('testkey1', 'test data 1'));
+        $this->assertEquals('test data 1', $cache->get('testkey1'));
+        $this->assertTrue($cache->set('testkey2', 'test data 2'));
+        $this->assertEquals('test data 2', $cache->get('testkey2'));
+
+        // Purge the event.
+        cache_helper::purge_by_event('crazyevent');
+
+        // Check things have been removed.
+        $this->assertFalse($cache->get('testkey1'));
+        $this->assertFalse($cache->get('testkey2'));
+    }
+
+    /**
+     * Tests session cache event purge
+     */
+    public function test_session_event_purge() {
+        $instance = cache_config_phpunittest::instance();
+        $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
+            'mode' => cache_store::MODE_SESSION,
+            'component' => 'phpunit',
+            'area' => 'eventpurgetest',
+            'invalidationevents' => array(
+                'crazyevent'
+            )
+        ));
+        $instance->phpunit_add_definition('phpunit/eventpurgetestpersistent', array(
+            'mode' => cache_store::MODE_SESSION,
+            'component' => 'phpunit',
+            'area' => 'eventpurgetestpersistent',
+            'persistent' => true,
+            'invalidationevents' => array(
+                'crazyevent'
+            )
+        ));
         $cache = cache::make('phpunit', 'eventpurgetest');
 
         $this->assertTrue($cache->set('testkey1', 'test data 1'));
@@ -640,6 +700,20 @@ class cache_phpunit_tests extends advanced_testcase {
         // Check things have been removed.
         $this->assertFalse($cache->get('testkey1'));
         $this->assertFalse($cache->get('testkey2'));
+
+        // Now test the persistent cache.
+        $cache = cache::make('phpunit', 'eventpurgetestpersistent');
+        $this->assertTrue($cache->set('testkey1', 'test data 1'));
+        $this->assertEquals('test data 1', $cache->get('testkey1'));
+        $this->assertTrue($cache->set('testkey2', 'test data 2'));
+        $this->assertEquals('test data 2', $cache->get('testkey2'));
+
+        // Purge the event.
+        cache_helper::purge_by_event('crazyevent');
+
+        // Check things have been removed.
+        $this->assertFalse($cache->get('testkey1'));
+        $this->assertFalse($cache->get('testkey2'));
     }
 
     /**
@@ -775,4 +849,4 @@ class cache_phpunit_tests extends advanced_testcase {
         $this->assertTrue($cache->set('test', 'test'));
         $this->assertEquals('test', $cache->get('test'));
     }
-}
\ No newline at end of file
+}
index 471521b..42ce92b 100644 (file)
@@ -46,7 +46,7 @@ class cache_config_phpunittest extends cache_config_writer {
                 case cache_store::MODE_APPLICATION:
                     $properties['overrideclass'] = 'cache_phpunit_application';
                     break;
-                case cache_store::MDOE_SESSION:
+                case cache_store::MODE_SESSION:
                     $properties['overrideclass'] = 'cache_phpunit_session';
                     break;
                 case cache_store::MODE_REQUEST:
index 07265bc..21782aa 100644 (file)
@@ -115,6 +115,14 @@ $PAGE->set_button(calendar_preferences_button($course));
 $renderer = $PAGE->get_renderer('core_calendar');
 
 echo $OUTPUT->header();
+
+// Filter subscriptions which user can't edit.
+foreach($subscriptions as $subscription) {
+    if (!calendar_can_edit_subscription($subscription)) {
+        unset($subscriptions[$subscription->id]);
+    }
+}
+
 // Display a table of subscriptions.
 echo $renderer->subscription_details($courseid, $subscriptions, $importresults);
 // Display the add subscription form.
index b61ecaa..b7184b7 100644 (file)
@@ -131,25 +131,9 @@ if ($mform->is_cancelled()) {
     redirect('manage.php?id='.$newcategory->id);
 }
 
-// Unfortunately the navigation never generates correctly for this page because technically this page doesn't actually
-// exist on the navigation; you get here through the course management page.
-// First up we'll try to make the course management page active seeing as that is where the user thinks they are.
-// The big prolem here is that the course management page is a common page for both editing users and common users and
-// is only added to the admin tree if the user has permission to edit at the system level.
-$node = $PAGE->settingsnav->get('root');
-if ($node) {
-    $node = $node->get('courses');
-    if ($node) {
-        $node = $node->get('coursemgmt');
-    }
-}
-if ($node) {
-    // The course management page exists so make that active.
-    $node->make_active();
-} else {
-    // Failing that we'll override the URL, not as accurate and chances are things
-    // won't be 100% correct all the time but should work most times.
-    // A common reason to arrive here is having the management capability within only a particular category (not at system level).
+// Page "Add new category" (with "Top" as a parent) does not exist in navigation.
+// We pretend we are on course management page.
+if (empty($id) && empty($parent)) {
     navigation_node::override_active_url(new moodle_url('/course/manage.php'));
 }
 
index a7277f4..3063966 100644 (file)
@@ -1678,7 +1678,6 @@ class core_course_external extends external_api {
             $newcategory = new stdClass();
             $newcategory->name = $category['name'];
             $newcategory->parent = $category['parent'];
-            $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
             // Format the description.
             if (!empty($category['description'])) {
                 $newcategory->description = $category['description'];
@@ -1921,6 +1920,70 @@ class core_course_external extends external_api {
         return null;
     }
 
+    /**
+     * Describes the parameters for delete_modules.
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 2.5
+     */
+    public static function delete_modules_parameters() {
+        return new external_function_parameters (
+            array(
+                'cmids' => new external_multiple_structure(new external_value(PARAM_INT, 'course module ID',
+                        VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of course module IDs'),
+            )
+        );
+    }
+
+    /**
+     * Deletes a list of provided module instances.
+     *
+     * @param array $cmids the course module ids
+     * @since Moodle 2.5
+     */
+    public static function delete_modules($cmids) {
+        global $CFG, $DB;
+
+        // Require course file containing the course delete module function.
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        // Clean the parameters.
+        $params = self::validate_parameters(self::delete_modules_parameters(), array('cmids' => $cmids));
+
+        // Keep track of the course ids we have performed a capability check on to avoid repeating.
+        $arrcourseschecked = array();
+
+        foreach ($params['cmids'] as $cmid) {
+            // Get the course module.
+            $cm = $DB->get_record('course_modules', array('id' => $cmid), '*', MUST_EXIST);
+
+            // Check if we have not yet confirmed they have permission in this course.
+            if (!in_array($cm->course, $arrcourseschecked)) {
+                // Ensure the current user has required permission in this course.
+                $context = context_course::instance($cm->course);
+                self::validate_context($context);
+                // Add to the array.
+                $arrcourseschecked[] = $cm->course;
+            }
+
+            // Ensure they can delete this module.
+            $modcontext = context_module::instance($cm->id);
+            require_capability('moodle/course:manageactivities', $modcontext);
+
+            // Delete the module.
+            course_delete_module($cm->id);
+        }
+    }
+
+    /**
+     * Describes the delete_modules return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 2.5
+     */
+    public static function delete_modules_returns() {
+        return null;
+    }
 }
 
 /**
index 6bc2895..ba5d976 100644 (file)
@@ -3684,16 +3684,18 @@ class course_request {
  * @param stdClass $currentcontext Current context of block
  */
 function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
-    // if above course context ,display all course fomats
-    list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
-    if ($course->id == SITEID) {
-        return array('*'=>get_string('page-x', 'pagetype'));
-    } else {
-        return array('*'=>get_string('page-x', 'pagetype'),
-            'course-*'=>get_string('page-course-x', 'pagetype'),
-            'course-view-*'=>get_string('page-course-view-x', 'pagetype')
-        );
+    // $currentcontext could be null, get_context_info_array() will throw an error if this is the case.
+    if (isset($currentcontext)) {
+        // if above course context ,display all course fomats
+        list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
+        if ($course->id == SITEID) {
+            return array('*'=>get_string('page-x', 'pagetype'));
+        }
     }
+    return array('*'=>get_string('page-x', 'pagetype'),
+        'course-*'=>get_string('page-course-x', 'pagetype'),
+        'course-view-*'=>get_string('page-course-view-x', 'pagetype')
+    );
 }
 
 /**
index ef8ef1b..0e6275e 100644 (file)
@@ -484,4 +484,55 @@ class courselib_testcase extends advanced_testcase {
         }
     }
 
+    public function test_course_page_type_list() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        // Create a category.
+        $category = new stdClass();
+        $category->name = 'Test Category';
+
+        $testcategory = $this->getDataGenerator()->create_category($category);
+
+        // Create a course.
+        $course = new stdClass();
+        $course->fullname = 'Apu loves Unit Təsts';
+        $course->shortname = 'Spread the lŭve';
+        $course->idnumber = '123';
+        $course->summary = 'Awesome!';
+        $course->summaryformat = FORMAT_PLAIN;
+        $course->format = 'topics';
+        $course->newsitems = 0;
+        $course->numsections = 5;
+        $course->category = $testcategory->id;
+
+        $testcourse = $this->getDataGenerator()->create_course($course);
+
+        // Create contexts.
+        $coursecontext = context_course::instance($testcourse->id);
+        $parentcontext = $coursecontext->get_parent_context(); // Not actually used.
+        $pagetype = 'page-course-x'; // Not used either.
+        $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
+
+        // Page type lists for normal courses.
+        $testpagetypelist1 = array();
+        $testpagetypelist1['*'] = 'Any page';
+        $testpagetypelist1['course-*'] = 'Any course page';
+        $testpagetypelist1['course-view-*'] = 'Any type of course main page';
+
+        $this->assertEquals($testpagetypelist1, $pagetypelist);
+
+        // Get the context for the front page course.
+        $sitecoursecontext = context_course::instance(SITEID);
+        $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
+
+        // Page type list for the front page course.
+        $testpagetypelist2 = array('*' => 'Any page');
+        $this->assertEquals($testpagetypelist2, $pagetypelist);
+
+        // Make sure that providing no current context to the function doesn't result in an error.
+        // Calls made from generate_page_type_patterns() may provide null values.
+        $pagetypelist = course_page_type_list($pagetype, null, null);
+        $this->assertEquals($pagetypelist, $testpagetypelist1);
+    }
 }
index 1489295..66b1fc7 100644 (file)
@@ -111,8 +111,12 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         $category2 = $DB->get_record('course_categories', array('id' => $category2->id));
         $category3 = $DB->get_record('course_categories', array('id' => $category3->id));
 
-        $this->assertGreaterThanOrEqual($category1->sortorder, $category3->sortorder);
-        $this->assertGreaterThanOrEqual($category2->sortorder, $category3->sortorder);
+        // sortorder sequence (and sortorder) must be:
+        // category 1
+        //   category 3
+        // category 2
+        $this->assertGreaterThan($category1->sortorder, $category3->sortorder);
+        $this->assertGreaterThan($category3->sortorder, $category2->sortorder);
 
         // Call without required capability
         $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid);
@@ -843,4 +847,104 @@ class core_course_external_testcase extends externallib_advanced_testcase {
         $updatedcoursewarnings = core_course_external::update_courses($courses);
         $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
     }
+
+    /**
+     * Test delete course_module.
+     */
+    public function test_delete_modules() {
+        global $DB;
+
+        // Ensure we reset the data after this test.
+        $this->resetAfterTest(true);
+
+        // Create a user.
+        $user = self::getDataGenerator()->create_user();
+
+        // Set the tests to run as the user.
+        self::setUser($user);
+
+        // Create a course to add the modules.
+        $course = self::getDataGenerator()->create_course();
+
+        // Create two test modules.
+        $record = new stdClass();
+        $record->course = $course->id;
+        $module1 = self::getDataGenerator()->create_module('forum', $record);
+        $module2 = self::getDataGenerator()->create_module('assignment', $record);
+
+        // Check the forum was correctly created.
+        $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id)));
+
+        // Check the assignment was correctly created.
+        $this->assertEquals(1, $DB->count_records('assignment', array('id' => $module2->id)));
+
+        // Check data exists in the course modules table.
+        $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
+                array('module1' => $module1->cmid, 'module2' => $module2->cmid)));
+
+        // Enrol the user in the course.
+        $enrol = enrol_get_plugin('manual');
+        $enrolinstances = enrol_get_instances($course->id, true);
+        foreach ($enrolinstances as $courseenrolinstance) {
+            if ($courseenrolinstance->enrol == "manual") {
+                $instance = $courseenrolinstance;
+                break;
+            }
+        }
+        $enrol->enrol_user($instance, $user->id);
+
+        // Assign capabilities to delete module 1.
+        $modcontext = context_module::instance($module1->cmid);
+        $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id);
+
+        // Assign capabilities to delete module 2.
+        $modcontext = context_module::instance($module2->cmid);
+        $newrole = create_role('Role 2', 'role2', 'Role 2 description');
+        $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id, $newrole);
+
+        // Deleting these module instances.
+        core_course_external::delete_modules(array($module1->cmid, $module2->cmid));
+
+        // Check the forum was deleted.
+        $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id)));
+
+        // Check the assignment was deleted.
+        $this->assertEquals(0, $DB->count_records('assignment', array('id' => $module2->id)));
+
+        // Check we retrieve no data in the course modules table.
+        $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
+                array('module1' => $module1->cmid, 'module2' => $module2->cmid)));
+
+        // Call with non-existent course module id and ensure exception thrown.
+        try {
+            core_course_external::delete_modules(array('1337'));
+            $this->fail('Exception expected due to missing course module.');
+        } catch (dml_missing_record_exception $e) {
+            $this->assertEquals('invalidrecord', $e->errorcode);
+        }
+
+        // Create two modules.
+        $module1 = self::getDataGenerator()->create_module('forum', $record);
+        $module2 = self::getDataGenerator()->create_module('assignment', $record);
+
+        // Since these modules were recreated the user will not have capabilities
+        // to delete them, ensure exception is thrown if they try.
+        try {
+            core_course_external::delete_modules(array($module1->cmid, $module2->cmid));
+            $this->fail('Exception expected due to missing capability.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('nopermissions', $e->errorcode);
+        }
+
+        // Unenrol user from the course.
+        $enrol->unenrol_user($instance, $user->id);
+
+        // Try and delete modules from the course the user was unenrolled in, make sure exception thrown.
+        try {
+            core_course_external::delete_modules(array($module1->cmid, $module2->cmid));
+            $this->fail('Exception expected due to being unenrolled from the course.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('requireloginerror', $e->errorcode);
+        }
+    }
 }
index 377e5ae..ce39f93 100644 (file)
@@ -161,6 +161,6 @@ YUI.add('moodle-course-modchooser', function(Y) {
     }
 },
 '@VERSION@', {
-    requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'transition', 'moodle-course-coursebase']
+    requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'moodle-course-coursebase']
 }
 );
diff --git a/enrol/flatfile/adminlib.php b/enrol/flatfile/adminlib.php
new file mode 100644 (file)
index 0000000..971a847
--- /dev/null
@@ -0,0 +1,57 @@
+<?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/>.
+
+/**
+ * Special flatfile settings.
+ *
+ * @package    enrol_flatfile
+ * @copyright  2013 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once("$CFG->libdir/adminlib.php");
+
+
+/**
+ * Setting class that stores only non-empty values.
+ */
+class enrol_flatfile_role_setting extends admin_setting_configtext {
+
+    public function __construct($role) {
+        parent::__construct('enrol_flatfile/map_'.$role->id, $role->localname, '', $role->shortname);
+    }
+
+    public function config_read($name) {
+        $value = parent::config_read($name);
+        if (is_null($value)) {
+            // In other settings NULL means we have to ask user for new value,
+            // here we just ignore missing role mappings.
+            $value = '';
+        }
+        return $value;
+    }
+
+    public function config_write($name, $value) {
+        if ($value === '') {
+            // We do not want empty values in config table,
+            // delete it instead.
+            $value = null;
+        }
+        return parent::config_write($name, $value);
+    }
+}
diff --git a/enrol/flatfile/db/install.php b/enrol/flatfile/db/install.php
new file mode 100644 (file)
index 0000000..93c2cda
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * Flatfile enrolment plugin installation.
+ *
+ * @package    enrol_flatfile
+ * @copyright  2013 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+function xmldb_enrol_flatfile_install() {
+    global $CFG, $DB;
+
+    // Flatfile role mappings are empty by default now.
+    $roles = get_all_roles();
+    foreach ($roles as $role) {
+        set_config('map_'.$role->id, $role->shortname, 'enrol_flatfile');
+    }
+}
index d8bba47..379568e 100644 (file)
@@ -25,6 +25,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+require_once(__DIR__.'/adminlib.php');
+
 if ($ADMIN->fulltree) {
 
     //--- general settings -----------------------------------------------------------------------------------
@@ -61,8 +63,8 @@ if ($ADMIN->fulltree) {
 
         $roles = role_fix_names(get_all_roles());
 
-        foreach ($roles as $id => $role) {
-            $settings->add(new admin_setting_configtext('enrol_flatfile/map_'.$id, $role->localname, '', $role->shortname));
+        foreach ($roles as $role) {
+            $settings->add(new enrol_flatfile_role_setting($role));
         }
         unset($roles);
     }
index 5b6ae7e..ab6f719 100644 (file)
@@ -68,7 +68,9 @@ class enrol_manual_plugin extends enrol_plugin {
 
         $context = context_course::instance($instance->courseid, MUST_EXIST);
 
-        if (!has_capability('enrol/manual:manage', $context) or !has_capability('enrol/manual:enrol', $context) or !has_capability('enrol/manual:unenrol', $context)) {
+        if (!has_capability('enrol/manual:enrol', $context)) {
+            // Note: manage capability not used here because it is used for editing
+            // of existing enrolments which is not possible here.
             return NULL;
         }
 
@@ -111,7 +113,7 @@ class enrol_manual_plugin extends enrol_plugin {
 
         $icons = array();
 
-        if (has_capability('enrol/manual:manage', $context)) {
+        if (has_capability('enrol/manual:enrol', $context) or has_capability('enrol/manual:unenrol', $context)) {
             $managelink = new moodle_url("/enrol/manual/manage.php", array('enrolid'=>$instance->id));
             $icons[] = $OUTPUT->action_icon($managelink, new pix_icon('t/enrolusers', get_string('enrolusers', 'enrol_manual'), 'core', array('class'=>'iconsmall')));
         }
@@ -436,10 +438,14 @@ class enrol_manual_plugin extends enrol_plugin {
     public function get_bulk_operations(course_enrolment_manager $manager) {
         global $CFG;
         require_once($CFG->dirroot.'/enrol/manual/locallib.php');
-        $bulkoperations = array(
-            'editselectedusers' => new enrol_manual_editselectedusers_operation($manager, $this),
-            'deleteselectedusers' => new enrol_manual_deleteselectedusers_operation($manager, $this)
-        );
+        $context = $manager->get_context();
+        $bulkoperations = array();
+        if (has_capability("enrol/manual:manage", $context)) {
+            $bulkoperations['editselectedusers'] = new enrol_manual_editselectedusers_operation($manager, $this);
+        }
+        if (has_capability("enrol/manual:unenrol", $context)) {
+            $bulkoperations['deleteselectedusers'] = new enrol_manual_deleteselectedusers_operation($manager, $this);
+        }
         return $bulkoperations;
     }
 
index f7eab04..7d91e4b 100644 (file)
@@ -35,9 +35,17 @@ $course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_
 $context = context_course::instance($course->id, MUST_EXIST);
 
 require_login($course);
-require_capability('enrol/manual:enrol', $context);
-require_capability('enrol/manual:manage', $context);
-require_capability('enrol/manual:unenrol', $context);
+$canenrol = has_capability('enrol/manual:enrol', $context);
+$canunenrol = has_capability('enrol/manual:unenrol', $context);
+
+// Note: manage capability not used here because it is used for editing
+// of existing enrolments which is not possible here.
+
+if (!$canenrol and !$canunenrol) {
+    // No need to invent new error strings here...
+    require_capability('enrol/manual:enrol', $context);
+    require_capability('enrol/manual:unenrol', $context);
+}
 
 if ($roleid < 0) {
     $roleid = $instance->roleid;
@@ -95,7 +103,7 @@ if ($course->startdate > 0) {
 $basemenu[3] = get_string('today') . ' (' . userdate($today, $timeformat) . ')' ;
 
 // Process add and removes.
-if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
+if ($canenrol && optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
     $userstoassign = $potentialuserselector->get_selected_users();
     if (!empty($userstoassign)) {
         foreach($userstoassign as $adduser) {
@@ -126,7 +134,7 @@ if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
 }
 
 // Process incoming role unassignments.
-if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
+if ($canunenrol && optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
     $userstounassign = $currentuserselector->get_selected_users();
     if (!empty($userstounassign)) {
         foreach($userstounassign as $removeuser) {
@@ -145,6 +153,9 @@ if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
 echo $OUTPUT->header();
 echo $OUTPUT->heading($instancename);
 
+$addenabled = $canenrol ? '' : 'disabled="disabled"';
+$removeenabled = $canunenrol ? '' : 'disabled="disabled"';
+
 ?>
 <form id="assignform" method="post" action="<?php echo $PAGE->url ?>"><div>
   <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
@@ -157,7 +168,7 @@ echo $OUTPUT->heading($instancename);
       </td>
       <td id="buttonscell">
           <div id="addcontrols">
-              <input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
+              <input name="add" <?php echo $addenabled; ?> id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
 
               <div class="enroloptions">
 
@@ -174,7 +185,7 @@ echo $OUTPUT->heading($instancename);
           </div>
 
           <div id="removecontrols">
-              <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
+              <input name="remove" id="remove" <?php echo $removeenabled; ?> type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
           </div>
       </td>
       <td id="potentialcell">
index dc2b636..e739e4a 100644 (file)
@@ -70,9 +70,9 @@ class filter_activitynames extends moodle_text_filter {
                             $href_tag_begin = html_writer::start_tag('a',
                                     array('class' => 'autolink', 'title' => $title,
                                         'href' => $cm->get_url()));
-                            self::$activitylist[] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
+                            self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
                             if ($currentname != $entitisedname) { /// If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545
-                                self::$activitylist[] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
+                                self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
                             }
                         }
                     }
@@ -80,8 +80,19 @@ class filter_activitynames extends moodle_text_filter {
             }
         }
 
+        $filterslist = array();
         if (self::$activitylist) {
-            return $text = filter_phrases ($text, self::$activitylist);
+            $cmid = $this->context->instanceid;
+            if ($this->context->contextlevel == CONTEXT_MODULE && isset(self::$activitylist[$cmid])) {
+                // remove filterobjects for the current module
+                $filterslist = array_diff_key(self::$activitylist, array($cmid => 1, $cmid.'-e' => 1));
+            } else {
+                $filterslist = self::$activitylist;
+            }
+        }
+
+        if ($filterslist) {
+            return $text = filter_phrases($text, $filterslist);
         } else {
             return $text;
         }
index c10a858..6e21e46 100644 (file)
@@ -30,7 +30,7 @@ require_once('import_outcomes_form.php');
 
 $courseid = optional_param('courseid', 0, PARAM_INT);
 $action   = optional_param('action', '', PARAM_ALPHA);
-$scope    = optional_param('scope', 'global', PARAM_ALPHA);
+$scope    = optional_param('scope', 'custom', PARAM_ALPHA);
 
 $PAGE->set_url('/grade/edit/outcome/import.php', array('courseid' => $courseid));
 
index 54af548..8d5f090 100644 (file)
@@ -741,9 +741,11 @@ M.gradereport_grader.classes.existingfield = function(ajax, userid, itemid) {
     this.editfeedback = ajax.showquickfeedback;
     this.grade = this.report.Y.one('#grade_'+userid+'_'+itemid);
 
-    for(var i = 0; i < this.report.grades.length; i++) {
-        if (this.report.grades[i]['user']==this.userid && this.report.grades[i]['item']==this.itemid) {
-            this.oldgrade = this.report.grades[i]['grade'];
+    if (this.report.grades) {
+        for (var i = 0; i < this.report.grades.length; i++) {
+            if (this.report.grades[i]['user']==this.userid && this.report.grades[i]['item']==this.itemid) {
+                this.oldgrade = this.report.grades[i]['grade'];
+            }
         }
     }
 
index cf90fc3..63d637a 100644 (file)
@@ -30,7 +30,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['language'] = '×\97×\91×\99×\9cת ×©×¤×\94';
+$string['language'] = 'שפת ×\9e×\9eשק';
 $string['next'] = 'הלאה';
 $string['previous'] = 'קודם';
 $string['reload'] = 'טען מחדש';
index 8e7d0c0..eefb901 100644 (file)
@@ -42,6 +42,7 @@ $string['defaultweight_help'] = 'The default weight allows you to choose roughly
 $string['deletecheck'] = 'Delete {$a} block?';
 $string['deleteblock'] = 'Delete {$a} block';
 $string['deleteblockcheck'] = 'Are you sure that you want to delete this block titled {$a}?';
+$string['deleteblockwarning'] = '<p>You are about to delete a block that appears elsewhere.</p><p>Original block location: {$a->location}<br />Display on page types: {$a->pagetype}</p><p>Are you sure you want to continue?</p>';
 $string['hideblock'] = 'Hide {$a} block';
 $string['hidedockpanel'] = 'Hide the dock panel';
 $string['hidepanel'] = 'Hide panel';
index 4d4dd8c..ae31942 100644 (file)
@@ -29,6 +29,7 @@ $string['actions'] = 'Actions';
 $string['availability'] = 'Availability';
 $string['checkforupdates'] = 'Check for available updates';
 $string['checkforupdateslast'] = 'Last check done on {$a}';
+$string['detectedmisplacedplugin'] = 'Plugin "{$a->component}" is installed in incorrect location "{$a->current}", expected location is "{$a->expected}"';
 $string['displayname'] = 'Plugin name';
 $string['err_response_curl'] = 'Unable to fetch available updates data - unexpected cURL error.';
 $string['err_response_format_version'] = 'Unexpected version of the response format. Please try to re-check for available updates.';
index 25713be..5ee707f 100644 (file)
@@ -158,7 +158,7 @@ $string['nofilesattached'] = 'No files attached';
 $string['nofilesavailable'] = 'No files available';
 $string['nomorefiles'] = 'No more attachments allowed';
 $string['nopathselected'] = 'No destination path select yet (double click tree node to select)';
-$string['nopermissiontoaccess'] = 'No permission to access this repository';
+$string['nopermissiontoaccess'] = 'No permission to access this repository.';
 $string['noresult'] = 'No search result';
 $string['norepositoriesavailable'] = 'Sorry, none of your current repositories can return files in the required format.';
 $string['norepositoriesexternalavailable'] = 'Sorry, none of your current repositories can return external files.';
index 5066ae5..f374414 100644 (file)
@@ -1176,6 +1176,27 @@ class block_manager {
             $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
             $message = get_string('deleteblockcheck', 'block', $blocktitle);
 
+            // If the block is being shown in sub contexts display a warning.
+            if ($block->instance->showinsubcontexts == 1) {
+                $parentcontext = context::instance_by_id($block->instance->parentcontextid);
+                $systemcontext = context_system::instance();
+                $messagestring = new stdClass();
+                $messagestring->location = $parentcontext->get_context_name();
+
+                // Checking for blocks that may have visibility on the front page and pages added on that.
+                if ($parentcontext->id != $systemcontext->id && is_inside_frontpage($parentcontext)) {
+                    $messagestring->pagetype = get_string('showonfrontpageandsubs', 'block');
+                } else {
+                    $pagetypes = generate_page_type_patterns($this->page->pagetype, $parentcontext);
+                    $messagestring->pagetype = $block->instance->pagetypepattern;
+                    if (isset($pagetypes[$block->instance->pagetypepattern])) {
+                        $messagestring->pagetype = $pagetypes[$block->instance->pagetypepattern];
+                    }
+                }
+
+                $message = get_string('deleteblockwarning', 'block', $messagestring);
+            }
+
             $PAGE->navbar->add($strdeletecheck);
             $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
             $PAGE->set_heading($site->fullname);
index 8633ecb..affc574 100644 (file)
@@ -613,7 +613,6 @@ abstract class condition_info_base {
             'email' => get_user_field_name('email'),
             'city' => get_user_field_name('city'),
             'country' => get_user_field_name('country'),
-            'interests' => get_user_field_name('interests'),
             'url' => get_user_field_name('url'),
             'icq' => get_user_field_name('icq'),
             'skype' => get_user_field_name('skype'),
@@ -1297,7 +1296,7 @@ abstract class condition_info_base {
      * @param int $fieldid the user profile field id
      * @return string the user value, or false if user does not have a user field value yet
      */
-    private function get_cached_user_profile_field($userid, $fieldid) {
+    protected function get_cached_user_profile_field($userid, $fieldid) {
         global $USER, $DB, $CFG;
 
         if ($userid === 0) {
index 1602999..2ba5735 100644 (file)
       </KEYS>
       <INDEXES>
         <INDEX NAME="action" UNIQUE="false" FIELDS="action" COMMENT="insert/update/delete"/>
+        <INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="grade_import_newitem" COMMENT="temporary table for storing new grade_item names from grade import">
index ddf6a4f..725b52a 100644 (file)
@@ -514,6 +514,15 @@ $functions = array(
         'capabilities'=> 'moodle/course:delete',
     ),
 
+    'core_course_delete_modules' => array(
+        'classname' => 'core_course_external',
+        'methodname' => 'delete_modules',
+        'classpath' => 'course/externallib.php',
+        'description' => 'Deletes all specified module instances',
+        'type' => 'write',
+        'capabilities' => 'moodle/course:manageactivities'
+    ),
+
     'core_course_duplicate_course' => array(
         'classname'   => 'core_course_external',
         'methodname'  => 'duplicate_course',
@@ -673,6 +682,33 @@ $functions = array(
         'capabilities'=> 'moodle/notes:manage',
     ),
 
+    'core_notes_delete_notes' => array(
+        'classname'   => 'core_notes_external',
+        'methodname'  => 'delete_notes',
+        'classpath'   => 'notes/externallib.php',
+        'description' => 'Delete notes',
+        'type'        => 'write',
+        'capabilities'=> 'moodle/notes:manage',
+    ),
+
+    'core_notes_get_notes' => array(
+        'classname'   => 'core_notes_external',
+        'methodname'  => 'get_notes',
+        'classpath'   => 'notes/externallib.php',
+        'description' => 'Get notes',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/notes:view',
+    ),
+
+    'core_notes_update_notes' => array(
+        'classname'   => 'core_notes_external',
+        'methodname'  => 'update_notes',
+        'classpath'   => 'notes/externallib.php',
+        'description' => 'Update notes',
+        'type'        => 'write',
+        'capabilities'=> 'moodle/notes:manage',
+    ),
+
     // === webservice related functions ===
 
     'moodle_webservice_get_siteinfo' => array(
index 201a7a1..4478ecc 100644 (file)
@@ -1660,5 +1660,31 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013021902.00);
     }
 
+    if ($oldversion < 2013022600.00) {
+        // Delete entries regarding invalid 'interests' option which breaks course.
+        $DB->delete_records('course_sections_avail_fields', array('userfield' => 'interests'));
+        $DB->delete_records('course_modules_avail_fields', array('userfield' => 'interests'));
+        // Clear course cache (will be rebuilt on first visit) in case of changes to these.
+        rebuild_course_cache(0, true);
+
+        upgrade_main_savepoint(true, 2013022600.00);
+    }
+
+    // Add index to field "timemodified" for grade_grades_history table.
+    if ($oldversion < 2013030400.00) {
+        $table = new xmldb_table('grade_grades_history');
+        $field = new xmldb_field('timemodified');
+
+        if ($dbman->field_exists($table, $field)) {
+            $index = new xmldb_index('timemodified', XMLDB_INDEX_NOTUNIQUE, array('timemodified'));
+            if (!$dbman->index_exists($table, $index)) {
+                $dbman->add_index($table, $index);
+            }
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013030400.00);
+    }
+
     return true;
 }
index 005f30d..a2229b9 100644 (file)
  * used to interact with the DB. All the dunctions in this library must be
  * generic and work against the major number of RDBMS possible. This is the
  * list of currently supported and tested DBs: mysql, postresql, mssql, oracle
-
+ *
  * This library is automatically included by Moodle core so you never need to
  * include it yourself.
-
+ *
  * For more info about the functions available in this library, please visit:
  *     http://docs.moodle.org/en/DML_functions
  * (feel free to modify, improve and document such page, thanks!)
index dbf30eb..fc69751 100644 (file)
@@ -3078,6 +3078,12 @@ class curl {
         }
         curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);
 
+        // Bypass proxy (for this request only) if required.
+        if (!empty($this->options['CURLOPT_URL']) &&
+                is_proxybypass($this->options['CURLOPT_URL'])) {
+            unset($this->options['CURLOPT_PROXY']);
+        }
+
         if ($this->debug){
             echo '<h1>Options</h1>';
             var_dump($this->options);
index 9c44430..8a40431 100644 (file)
@@ -180,6 +180,89 @@ class file_storage {
         return $preview;
     }
 
+    /**
+     * Return an available file name.
+     *
+     * This will return the next available file name in the area, adding/incrementing a suffix
+     * of the file, ie: file.txt > file (1).txt > file (2).txt > etc...
+     *
+     * If the file name passed is available without modification, it is returned as is.
+     *
+     * @param int $contextid context ID.
+     * @param string $component component.
+     * @param string $filearea file area.
+     * @param int $itemid area item ID.
+     * @param string $filepath the file path.
+     * @param string $filename the file name.
+     * @return string available file name.
+     * @throws coding_exception if the file name is invalid.
+     * @since 2.5
+     */
+    public function get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, $filename) {
+        global $DB;
+
+        // Do not accept '.' or an empty file name (zero is acceptable).
+        if ($filename == '.' || (empty($filename) && !is_numeric($filename))) {
+            throw new coding_exception('Invalid file name passed', $filename);
+        }
+
+        // The file does not exist, we return the same file name.
+        if (!$this->file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
+            return $filename;
+        }
+
+        // Trying to locate a file name using the used pattern. We remove the used pattern from the file name first.
+        $pathinfo = pathinfo($filename);
+        $basename = $pathinfo['filename'];
+        $matches = array();
+        if (preg_match('~^(.+) \(([0-9]+)\)$~', $basename, $matches)) {
+            $basename = $matches[1];
+        }
+
+        $filenamelike = $DB->sql_like_escape($basename) . ' (%)';
+        if (isset($pathinfo['extension'])) {
+            $filenamelike .= '.' . $DB->sql_like_escape($pathinfo['extension']);
+        }
+
+        $filenamelikesql = $DB->sql_like('f.filename', ':filenamelike');
+        $filenamelen = $DB->sql_length('f.filename');
+        $sql = "SELECT filename
+                FROM {files} f
+                WHERE
+                    f.contextid = :contextid AND
+                    f.component = :component AND
+                    f.filearea = :filearea AND
+                    f.itemid = :itemid AND
+                    f.filepath = :filepath AND
+                    $filenamelikesql
+                ORDER BY
+                    $filenamelen DESC,
+                    f.filename DESC";
+        $params = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid,
+                'filepath' => $filepath, 'filenamelike' => $filenamelike);
+        $results = $DB->get_fieldset_sql($sql, $params, IGNORE_MULTIPLE);
+
+        // Loop over the results to make sure we are working on a valid file name. Because 'file (1).txt' and 'file (copy).txt'
+        // would both be returned, but only the one only containing digits should be used.
+        $number = 1;
+        foreach ($results as $result) {
+            $resultbasename = pathinfo($result, PATHINFO_FILENAME);
+            $matches = array();
+            if (preg_match('~^(.+) \(([0-9]+)\)$~', $resultbasename, $matches)) {
+                $number = $matches[2] + 1;
+                break;
+            }
+        }
+
+        // Constructing the new filename.
+        $newfilename = $basename . ' (' . $number . ')';
+        if (isset($pathinfo['extension'])) {
+            $newfilename .= '.' . $pathinfo['extension'];
+        }
+
+        return $newfilename;
+    }
+
     /**
      * Generates a preview image for the stored file
      *
@@ -1635,6 +1718,11 @@ class file_storage {
     public function deleted_file_cleanup($contenthash) {
         global $DB;
 
+        if ($contenthash === sha1('')) {
+            // No need to delete empty content file with sha1('') content hash.
+            return;
+        }
+
         //Note: this section is critical - in theory file could be reused at the same
         //      time, if this happens we can still recover the file from trash
         if ($DB->record_exists('files', array('contenthash'=>$contenthash))) {
@@ -1852,8 +1940,8 @@ class file_storage {
         $referencehash = sha1($reference);
 
         $sql = "SELECT repositoryid, id FROM {files_reference}
-                 WHERE referencehash = ? and reference = ?";
-        $rs = $DB->get_recordset_sql($sql, array($referencehash, $reference));
+                 WHERE referencehash = ?";
+        $rs = $DB->get_recordset_sql($sql, array($referencehash));
 
         $now = time();
         foreach ($rs as $record) {
index fee7bfa..84d9510 100644 (file)
@@ -275,26 +275,32 @@ class stored_file {
     public function delete() {
         global $DB;
 
-        $transaction = $DB->start_delegated_transaction();
+        if ($this->is_directory()) {
+            // Directories can not be referenced, just delete the record.
+            $DB->delete_records('files', array('id'=>$this->file_record->id));
 
-        // If there are other files referring to this file, convert them to copies.
-        if ($files = $this->fs->get_references_by_storedfile($this)) {
-            foreach ($files as $file) {
-                $this->fs->import_external_file($file);
+        } else {
+            $transaction = $DB->start_delegated_transaction();
+
+            // If there are other files referring to this file, convert them to copies.
+            if ($files = $this->fs->get_references_by_storedfile($this)) {
+                foreach ($files as $file) {
+                    $this->fs->import_external_file($file);
+                }
             }
-        }
 
-        // If this file is a reference (alias) to another file, unlink it first.
-        if ($this->is_external_file()) {
-            $this->delete_reference();
-        }
+            // If this file is a reference (alias) to another file, unlink it first.
+            if ($this->is_external_file()) {
+                $this->delete_reference();
+            }
 
-        // Now delete the file record.
-        $DB->delete_records('files', array('id'=>$this->file_record->id));
+            // Now delete the file record.
+            $DB->delete_records('files', array('id'=>$this->file_record->id));
 
-        $transaction->allow_commit();
+            $transaction->allow_commit();
+        }
 
-        // moves pool file to trash if content not needed any more
+        // Move pool file to trash if content not needed any more.
         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
         return true; // BC only
     }
index 049acfe..c634bfb 100644 (file)
@@ -1352,4 +1352,67 @@ class filestoragelib_testcase extends advanced_testcase {
             $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
         $this->assertTrue($symlink2->is_external_file());
     }
+
+    public function test_get_unused_filename() {
+        global $USER;
+        $this->resetAfterTest(true);
+
+        $fs = get_file_storage();
+        $this->setAdminUser();
+        $contextid = context_user::instance($USER->id)->id;
+        $component = 'user';
+        $filearea = 'private';
+        $itemid = 0;
+        $filepath = '/';
+
+        // Create some private files.
+        $file = new stdClass;
+        $file->contextid = $contextid;
+        $file->component = 'user';
+        $file->filearea  = 'private';
+        $file->itemid    = 0;
+        $file->filepath  = '/';
+        $file->source    = 'test';
+        $filenames = array('foo.txt', 'foo (1).txt', 'foo (20).txt', 'foo (999)', 'bar.jpg', 'What (a cool file).jpg',
+                'Hurray! (1).php', 'Hurray! (2).php', 'Hurray! (9a).php', 'Hurray! (abc).php');
+        foreach ($filenames as $key => $filename) {
+            $file->filename = $filename;
+            $userfile = $fs->create_file_from_string($file, "file $key $filename content");
+            $this->assertInstanceOf('stored_file', $userfile);
+        }
+
+        // Asserting new generated names.
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'unused.txt');
+        $this->assertEquals('unused.txt', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo.txt');
+        $this->assertEquals('foo (21).txt', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (1).txt');
+        $this->assertEquals('foo (21).txt', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (2).txt');
+        $this->assertEquals('foo (2).txt', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (20).txt');
+        $this->assertEquals('foo (21).txt', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo');
+        $this->assertEquals('foo', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (123)');
+        $this->assertEquals('foo (123)', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (999)');
+        $this->assertEquals('foo (1000)', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar.png');
+        $this->assertEquals('bar.png', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar (12).png');
+        $this->assertEquals('bar (12).png', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar.jpg');
+        $this->assertEquals('bar (1).jpg', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar (1).jpg');
+        $this->assertEquals('bar (1).jpg', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'What (a cool file).jpg');
+        $this->assertEquals('What (a cool file) (1).jpg', $newfilename);
+        $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'Hurray! (1).php');
+        $this->assertEquals('Hurray! (3).php', $newfilename);
+
+        $this->setExpectedException('coding_exception');
+        $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, '');
+    }
+
 }
index 084f922..e6a6ad3 100644 (file)
@@ -796,12 +796,12 @@ M.form_dndupload.init = function(Y, options) {
                 extension = filename.substr(dotpos, filename.length);
             }
 
-            // Look to see if the name already has _NN at the end of it.
+            // Look to see if the name already has (NN) at the end of it.
             var number = 0;
-            var hasnumber = basename.match(/^(.*)_(\d+)$/);
-            if (hasnumber != null) {
+            var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
+            if (hasnumber !== null) {
                 // Note the current number & remove it from the basename.
-                number = parseInt(hasnumber[2]);
+                number = parseInt(hasnumber[2], 10);
                 basename = hasnumber[1];
             }
 
@@ -809,7 +809,7 @@ M.form_dndupload.init = function(Y, options) {
             var newname;
             do {
                 number++;
-                newname = basename + '_' + number + extension;
+                newname = basename + ' (' + number + ')' + extension;
             } while (this.has_name_clash(newname));
 
             return newname;
index 19080d9..917ad8c 100644 (file)
@@ -254,7 +254,7 @@ M.form_filemanager.init = function(Y, options) {
 
             this.msg_dlg.set('headerContent', header);
             this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
-            this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
+            this.msg_dlg_node.one('.fp-msg-text').setContent(Y.Escape.html(msg));
             this.msg_dlg.show();
         },
         is_disabled: function() {
@@ -325,7 +325,7 @@ M.form_filemanager.init = function(Y, options) {
                     }
                     this.mkdir_dialog.show();
                     Y.one('#fm-newname-'+scope.client_id).focus();
-                    Y.all('#fm-curpath-'+scope.client_id).setContent(this.currentpath)
+                    Y.all('#fm-curpath-'+scope.client_id).setContent(Y.Escape.html(this.currentpath))
                 }, this);
             } else {
                 this.filemanager.addClass('fm-nomkdir');
@@ -412,7 +412,7 @@ M.form_filemanager.init = function(Y, options) {
                     } else {
                         el.addClass('odd');
                     }
-                    el.one('.fp-path-folder-name').setContent(p[i].name).
+                    el.one('.fp-path-folder-name').setContent(Y.Escape.html(p[i].name)).
                         on('click', function(e, path) {
                             e.preventDefault();
                             if (!this.is_disabled()) {
@@ -602,7 +602,7 @@ M.form_filemanager.init = function(Y, options) {
             for (var i in licenses) {
                 var option = Y.Node.create('<option/>').
                     set('value', licenses[i].shortname).
-                    setContent(licenses[i].fullname);
+                    setContent(Y.Escape.html(licenses[i].fullname));
                 node.appendChild(option)
             }
         },
@@ -621,7 +621,7 @@ M.form_filemanager.init = function(Y, options) {
             node.setContent('');
             for (var i in list) {
                 node.appendChild(Y.Node.create('<option/>').
-                    set('value', list[i]).setContent(list[i]))
+                    set('value', list[i]).setContent(Y.Escape.html(list[i])));
             }
         },
         update_file: function(confirmed) {
@@ -923,7 +923,7 @@ M.form_filemanager.init = function(Y, options) {
                 if (selectnode.one('.fp-'+attrs[i])) {
                     var value = (node[attrs[i]+'_f']) ? node[attrs[i]+'_f'] : (node[attrs[i]] ? node[attrs[i]] : '');
                     selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
-                        .one('.fp-value').setContent(value);
+                        .one('.fp-value').setContent(Y.Escape.html(value));
                 }
             }
             // display thumbnail
@@ -948,7 +948,7 @@ M.form_filemanager.init = function(Y, options) {
                             selectnode.one('.fp-original').removeClass('fp-loading');
                             if (obj.original) {
                                 node.original = obj.original;
-                                selectnode.one('.fp-original .fp-value').setContent(node.original);
+                                selectnode.one('.fp-original .fp-value').setContent(Y.Escape.html(node.original));
                             } else {
                                 selectnode.one('.fp-original .fp-value').setContent(M.str.repository.unknownsource);
                             }
@@ -976,7 +976,7 @@ M.form_filemanager.init = function(Y, options) {
                                 for (var i in obj.references) {
                                     node.reflist += '<li>'+obj.references[i]+'</li>';
                                 }
-                                selectnode.one('.fp-reflist .fp-value').setContent(node.reflist);
+                                selectnode.one('.fp-reflist .fp-value').setContent(Y.Escape.html(node.reflist));
                             } else {
                                 selectnode.one('.fp-reflist .fp-value').setContent('');
                             }
index c874791..a14746d 100644 (file)
@@ -84,7 +84,12 @@ YUI.add('moodle-form-shortforms', function(Y) {
             fieldset.toggleClass(CSS.COLLAPSED);
             // Get corresponding hidden variable
             // - and invert it.
-            var statuselement = new Y.one('input[name=mform_isexpanded_'+fieldset.get('id')+']');
+            var statuselement = Y.one('input[name=mform_isexpanded_'+fieldset.get('id')+']');
+            if (!statuselement) {
+                Y.log("M.form.shortforms::switch_state was called on an fieldset without a status field: '" +
+                    fieldset.get('id') + "'", 'debug');
+                return;
+            }
             statuselement.set('value', Math.abs(Number(statuselement.get('value'))-1));
         }
     });
index 562e20e..f43e48f 100644 (file)
@@ -67,7 +67,12 @@ YUI.add('moodle-form-showadvanced', function(Y) {
             Y.one('#'+this.get('formid')).delegate('click', this.switch_state, SELECTORS.FIELDSETCONTAINSADVANCED+' .'+CSS.MORELESSTOGGLER);
         },
         process_fieldset : function(fieldset) {
-            var statuselement = new Y.one('input[name=mform_showmore_'+fieldset.get('id')+']');
+            var statuselement = Y.one('input[name=mform_showmore_'+fieldset.get('id')+']');
+            if (!statuselement) {
+                Y.log("M.form.showadvanced::process_fieldset was called on an fieldset without a status field: '" +
+                    fieldset.get('id') + "'", 'debug');
+                return;
+            }
             var morelesslink = Y.Node.create('<a href="#"></a>');
             morelesslink.addClass(CSS.MORELESSTOGGLER);
             if (statuselement.get('value') === '0') {
index 6890a83..5e89439 100644 (file)
@@ -1413,7 +1413,7 @@ function get_config($plugin, $name = NULL) {
 
     $cache = cache::make('core', 'config');
     $result = $cache->get($plugin);
-    if (!$result) {
+    if ($result === false) {
         // the user is after a recordset
         $result = new stdClass;
         if (!$iscore) {
@@ -9134,10 +9134,14 @@ function moodle_needs_upgrading() {
             continue;
         }
         $module = new stdClass();
+        $plugin = new stdClass();
         if (!is_readable($fullmod.'/version.php')) {
             continue;
         }
         include($fullmod.'/version.php');  // defines $module with version etc
+        if (!isset($module->version) and isset($plugin->version)) {
+            $module = $plugin;
+        }
         if (empty($installed[$mod])) {
             return true;
         } else if ($module->version > $installed[$mod]->version) {
index 89d509f..14ea861 100644 (file)
@@ -155,12 +155,15 @@ class page_requirements_manager {
         $sep = empty($CFG->yuislasharguments) ? '?' : '/';
 
         $this->yui3loader = new stdClass();
+        $this->YUI_config = new stdClass();
 
         // Set up some loader options.
         if (debugging('', DEBUG_DEVELOPER)) {
             $this->yui3loader->filter = 'RAW'; // For more detailed logging info use 'DEBUG' here.
+            $this->YUI_config->debug = true;
         } else {
             $this->yui3loader->filter = null;
+            $this->YUI_config->debug = false;
         }
         if (!empty($CFG->useexternalyui) and strpos($CFG->httpswwwroot, 'https:') !== 0) {
             $this->yui3loader->base = 'http://yui.yahooapis.com/' . $CFG->yui3version . '/build/';
@@ -182,7 +185,6 @@ class page_requirements_manager {
         }
 
         // Set up JS YUI loader helper object.
-        $this->YUI_config = new stdClass();
         $this->YUI_config->base         = $this->yui3loader->base;
         $this->YUI_config->comboBase    = $this->yui3loader->comboBase;
         $this->YUI_config->combine      = $this->yui3loader->combine;
@@ -393,7 +395,7 @@ class page_requirements_manager {
                 case 'core_filepicker':
                     $module = array('name'     => 'core_filepicker',
                                     'fullpath' => '/repository/filepicker.js',
-                                    'requires' => array('base', 'node', 'node-event-simulate', 'json', 'async-queue', 'io-base', 'io-upload-iframe', 'io-form', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort', 'resize-plugin', 'dd-plugin', 'moodle-core_filepicker'),
+                                    'requires' => array('base', 'node', 'node-event-simulate', 'json', 'async-queue', 'io-base', 'io-upload-iframe', 'io-form', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort', 'resize-plugin', 'dd-plugin', 'escape', 'moodle-core_filepicker'),
                                     'strings'  => array(array('lastmodified', 'moodle'), array('name', 'moodle'), array('type', 'repository'), array('size', 'repository'),
                                                         array('invalidjson', 'repository'), array('error', 'moodle'), array('info', 'moodle'),
                                                         array('nofilesattached', 'repository'), array('filepicker', 'repository'), array('logout', 'repository'),
index db33e98..561ec9f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-Copyright (c) 2002-2003, Michael Bretterklieber <michael@bretterklieber.com>
+Copyright (c) 2002-2010, Michael Bretterklieber <michael@bretterklieber.com>
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -101,7 +101,6 @@ class Crypt_CHAP extends PEAR
     function generateChallenge($varname = 'challenge', $size = 8)
     {
         $this->$varname = '';
-        mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
         for ($i = 0; $i < $size; $i++) {
             $this->$varname .= pack('C', 1 + mt_rand() % 255);
         }
@@ -149,7 +148,7 @@ class Crypt_CHAP_MD5 extends Crypt_CHAP
  * Generate MS-CHAPv1 Packets. MS-CHAP doesen't use the plaintext password, it uses the
  * NT-HASH wich is stored in the SAM-Database or in the smbpasswd, if you are using samba.
  * The NT-HASH is MD4(str2unicode(plaintextpass)).
- * You need the mhash extension for this class.
+ * You need the hash extension for this class.
  *
  * @package Crypt_CHAP
  */
@@ -165,13 +164,13 @@ class Crypt_CHAP_MSv1 extends Crypt_CHAP
     /**
      * Constructor
      *
-     * Loads the mhash extension
+     * Loads the hash extension
      * @return void
      */
     function Crypt_CHAP_MSv1()
     {
         $this->Crypt_CHAP();
-        $this->loadExtension('mhash');
+        $this->loadExtension('hash');
     }
 
     /**
@@ -183,9 +182,9 @@ class Crypt_CHAP_MSv1 extends Crypt_CHAP
     function ntPasswordHash($password = null)
     {
         if (isset($password)) {
-            return mhash(MHASH_MD4, $this->str2unicode($password));
+            return pack('H*',hash('md4', $this->str2unicode($password)));
         } else {
-            return mhash(MHASH_MD4, $this->str2unicode($this->password));
+            return pack('H*',hash('md4', $this->str2unicode($this->password)));
         }
     }
 
@@ -432,7 +431,7 @@ class Crypt_CHAP_MSv2 extends Crypt_CHAP_MSv1
      */
     function ntPasswordHashHash($nthash)
     {
-        return mhash(MHASH_MD4, $nthash);
+        return pack('H*',hash('md4', $nthash));
     }
 
     /**
@@ -444,7 +443,7 @@ class Crypt_CHAP_MSv2 extends Crypt_CHAP_MSv1
      */
     function challengeHash()
     {
-        return substr(mhash(MHASH_SHA1, $this->peerChallenge . $this->authChallenge . $this->username), 0, 8);
+        return substr(pack('H*',hash('sha1', $this->peerChallenge . $this->authChallenge . $this->username)), 0, 8);
     }
 
     /**
index 57e15fb..a626ab4 100644 (file)
@@ -2857,9 +2857,13 @@ class plugininfo_mod extends plugininfo_base {
         $versionfile = $this->full_path('version.php');
 
         $module = new stdClass();
+        $plugin = new stdClass();
         if (is_readable($versionfile)) {
             include($versionfile);
         }
+        if (!isset($module->version) and isset($plugin->version)) {
+            $module = $plugin;
+        }
         return $module;
     }
 
index 87c2850..3a5fe10 100644 (file)
@@ -91,10 +91,10 @@ if (!isset($CFG->wwwroot) or $CFG->wwwroot === 'http://example.com/moodle') {
 }
 
 // Ignore $CFG->behat_wwwroot and use the same wwwroot.
-if (isset($CFG->behat_switchcompletely)) {
+if (!empty($CFG->behat_switchcompletely)) {
     $CFG->behat_wwwroot = $CFG->wwwroot;
 
-} else if (!isset($CFG->behat_wwwroot)) {
+} else if (empty($CFG->behat_wwwroot)) {
     // Default URL for acceptance testing, only accessible from localhost.
     $CFG->behat_wwwroot = 'http://localhost:8000';
 }
@@ -107,7 +107,7 @@ if (isset($CFG->behat_switchcompletely)) {
 // Test environment is enabled if:
 // * User has previously enabled through admin/tool/behat/cli/util.php --enable.
 // Both are required to switch to test mode
-if (isset($CFG->behat_dataroot) && isset($CFG->behat_prefix) && file_exists($CFG->behat_dataroot)) {
+if (!empty($CFG->behat_dataroot) && !empty($CFG->behat_prefix) && file_exists($CFG->behat_dataroot)) {
 
     $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
 
index 34ead8c..129147a 100644 (file)
@@ -526,6 +526,22 @@ function get_exception_info($ex) {
         $debuginfo .= PHP_EOL.'$a contents: '.print_r($a, true);
     }
 
+    // Remove some absolute paths from message and debugging info.
+    $searches = array();
+    $replaces = array();
+    $cfgnames = array('tempdir', 'cachedir', 'themedir',
+        'langmenucachefile', 'langcacheroot', 'dataroot', 'dirroot');
+    foreach ($cfgnames as $cfgname) {
+        if (property_exists($CFG, $cfgname)) {
+            $searches[] = $CFG->$cfgname;
+            $replaces[] = "[$cfgname]";
+        }
+    }
+    if (!empty($searches)) {
+        $message   = str_replace($searches, $replaces, $message);
+        $debuginfo = str_replace($searches, $replaces, $debuginfo);
+    }
+
     // Be careful, no guarantee weblib.php is loaded.
     if (function_exists('clean_text')) {
         $message = clean_text($message);
@@ -1149,7 +1165,7 @@ function disable_output_buffering() {
  */
 function redirect_if_major_upgrade_required() {
     global $CFG;
-    $lastmajordbchanges = 2013021100.01;
+    $lastmajordbchanges = 2013022500.01;
     if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
             during_initial_install() or !empty($CFG->adminsetuppending)) {
         try {
index 0afeb63..425dbf1 100644 (file)
@@ -176,8 +176,12 @@ EOD;
             }
         }
 
-        if (!isset($record['password'])) {
-            $record['password'] = 'lala';
+        if (isset($record['password'])) {
+            $record['password'] = hash_internal_user_password($record['password']);
+        } else {
+            // The auth plugin may not fully support this,
+            // but it is still better/faster than hashing random stuff.
+            $record['password'] = AUTH_PASSWORD_NOT_CACHED;
         }
 
         if (!isset($record['email'])) {
@@ -204,9 +208,6 @@ EOD;
         $record['timemodified'] = $record['timecreated'];
         $record['lastip'] = '0.0.0.0';
 
-        // Use fast hash during testing.
-        $record['password'] = hash_internal_user_password($record['password'], true);
-
         if ($record['deleted']) {
             $delname = $record['email'].'.'.time();
             while ($DB->record_exists('user', array('username'=>$delname))) {
index b3ae7f5..5ef7365 100644 (file)
@@ -181,13 +181,14 @@ class conditionlib_testcase extends advanced_testcase {
     }
 
     private function make_course() {
-        global $DB;
-        $categoryid = $DB->insert_record('course_categories', (object)array('name'=>'conditionlibtest'));
-        $courseid = $DB->insert_record('course', (object)array(
-            'fullname'=>'Condition test','shortname'=>'CT1',
-            'category'=>$categoryid,'enablecompletion'=>1));
-        context_course::instance($courseid);
-        return $courseid;
+        $category = $this->getDataGenerator()->create_category(array('name' => 'conditionlibtest'));
+        $course = $this->getDataGenerator()->create_course(
+                array('fullname' => 'Condition test',
+                    'shortname' => 'CT1',
+                    'category' => $category->id,
+                    'enablecompletion' => 1));
+        context_course::instance($course->id);
+        return $course->id;
     }
 
     private function make_course_module($courseid,$params=array()) {
@@ -253,7 +254,7 @@ class conditionlib_testcase extends advanced_testcase {
             'completion'=>COMPLETION_TRACKING_MANUAL));
         $cmid2=$this->make_course_module($courseid,array(
             'showavailability'=>0,'availablefrom'=>0,'availableuntil'=>0));
-        $this->make_section($courseid,array($cmid1,$cmid2));
+        $this->make_section($courseid, array($cmid1, $cmid2), 1);
 
         // Add a fake grade item
         $gradeitemid=$DB->insert_record('grade_items',(object)array(
@@ -351,7 +352,7 @@ class conditionlib_testcase extends advanced_testcase {
         $courseid=$this->make_course();
         $cmid=$this->make_course_module($courseid,array(
             'showavailability'=>0,'availablefrom'=>0,'availableuntil'=>0));
-        $this->make_section($courseid,array($cmid));
+        $this->make_section($courseid, array($cmid), 1);
 
         // Check it has no conditions
         $test1=new condition_info((object)array('id'=>$cmid),
@@ -394,7 +395,7 @@ class conditionlib_testcase extends advanced_testcase {
         // Make course and module
         $courseid = $this->make_course();
         $cmid = $this->make_course_module($courseid);
-        $sectionid = $this->make_section($courseid, array($cmid));
+        $sectionid = $this->make_section($courseid, array($cmid), 1);
 
         // Check it has no conditions
         $test1 = new condition_info_section((object)array('id'=>$sectionid),
@@ -468,7 +469,7 @@ class conditionlib_testcase extends advanced_testcase {
         // Completion
         $oldid=$cmid;
         $cmid=$this->make_course_module($courseid);
-        $this->make_section($courseid,array($oldid,$cmid));
+        $this->make_section($courseid, array($oldid, $cmid), 1);
         $oldcm=$DB->get_record('course_modules',array('id'=>$oldid));
         $oldcm->completion=COMPLETION_TRACKING_MANUAL;
         $DB->update_record('course_modules',$oldcm);
@@ -735,5 +736,56 @@ class conditionlib_testcase extends advanced_testcase {
         $this->assertFalse($ci->is_available($text, false, $USER->id + 1));
         $this->assertFalse($ci->is_available($text, true, $USER->id + 1));
     }
+
+    /**
+     * Tests user fields to ensure that the list of provided fields includes only
+     * fields which the equivalent function can be used to obtain the value of.
+     */
+    public function test_condition_user_fields() {
+        global $CFG, $DB, $USER;
+
+        // Set up basic data.
+        $courseid = $this->make_course();
+        $cmid = $this->make_course_module($courseid);
+        $ci = new condition_info_testwrapper(
+                (object)array('id' => $cmid), CONDITION_MISSING_EVERYTHING);
+
+        // Add a custom user profile field. Unfortunately there is no back-end
+        // API for adding profile fields without having an actual form and doing
+        // redirects and stuff! These are the default text field parameters.
+        require_once($CFG->dirroot . '/user/profile/lib.php');
+        $field = (object)array(
+                'shortname' => 'myfield', 'name' => 'My field', 'required' => 0,
+                'locked' => 0, 'forceunique' => 0, 'signup' => 0,
+                'visible' => PROFILE_VISIBLE_ALL,
+                'datatype' => 'text', 'description' => 'A field of mine',
+                'descriptionformat' => FORMAT_HTML, 'defaultdata' => '',
+                'defaultdataformat' => FORMAT_HTML, 'param1' => 30, 'param2' => 2048,
+                'param3' => 0, 'param4' => '', 'param5' => '');
+        $customfieldid = $DB->insert_record('user_info_field', $field);
+
+        // Get list of condition user fields.
+        $fields = condition_info::get_condition_user_fields();
+
+        // Check custom field is included.
+        $this->assertEquals('My field', $fields[$customfieldid]);
+
+        // For all other fields, check it actually works to get data from them.
+        foreach ($fields as $fieldid => $name) {
+            // Not checking the result, just that it's possible to get it
+            // without error.
+            $ci->get_cached_user_profile_field($USER->id, $fieldid);
+        }
+    }
 }
 
+
+/**
+ * Test wrapper used only to make protected functions public so they can be
+ * tested.
+ */
+class condition_info_testwrapper extends condition_info {
+    public function get_cached_user_profile_field($userid, $fieldid) {
+        return parent::get_cached_user_profile_field($userid, $fieldid);
+    }
+}
index 30a257b..63431ee 100644 (file)
@@ -118,4 +118,28 @@ class core_setuplib_testcase extends basic_testcase {
             $this->assertTrue(is_web_crawler(), "$agent should be considered a search engine");
         }
     }
+
+    /**
+     * Test if get_exception_info() removes file system paths
+     */
+    public function test_exception_info_removes_serverpaths() {
+        global $CFG;
+
+        // This doesn't test them all possible ones, but these are set for unit tests.
+        $cfgnames = array('dataroot', 'dirroot', 'tempdir', 'cachedir');
+
+        $fixture  = '';
+        $expected = '';
+        foreach ($cfgnames as $cfgname) {
+            if (!empty($CFG->$cfgname)) {
+                $fixture  .= $CFG->$cfgname.' ';
+                $expected .= "[$cfgname] ";
+            }
+        }
+        $exception     = new moodle_exception('generalexceptionmessage', 'error', '', $fixture, $fixture);
+        $exceptioninfo = get_exception_info($exception);
+
+        $this->assertContains($expected, $exceptioninfo->message, 'Exception message does not contain system paths');
+        $this->assertContains($expected, $exceptioninfo->debuginfo, 'Exception debug info does not contain system paths');
+    }
 }
index 9382583..33a185c 100644 (file)
@@ -97,6 +97,23 @@ class plugin_defective_exception extends moodle_exception {
     }
 }
 
+/**
+ * @package    core
+ * @subpackage upgrade
+ * @copyright  2009 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class plugin_misplaced_exception extends moodle_exception {
+    function __construct($component, $expected, $current) {
+        global $CFG;
+        $a = new stdClass();
+        $a->component = $component;
+        $a->expected  = $expected;
+        $a->current   = $current;
+        parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a);
+    }
+}
+
 /**
  * Sets maximum expected time needed for upgrade task.
  * Please always make sure that upgrade will not run longer!
@@ -381,12 +398,19 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
         }
 
         $plugin = new stdClass();
+        $module = new stdClass(); // Prevent some notices when plugin placed in wrong directory.
         require($fullplug.'/version.php');  // defines $plugin with version etc
 
+        if (!isset($plugin->version) and isset($module->version)) {
+            $plugin = $module;
+        }
+
         // if plugin tells us it's full name we may check the location
         if (isset($plugin->component)) {
             if ($plugin->component !== $component) {
-                throw new plugin_defective_exception($component, 'Plugin installed in wrong folder.');
+                $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullplug);
+                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', get_component_directory($plugin->component));
+                throw new plugin_misplaced_exception($component, $expected, $current);
             }
         }
 
@@ -532,12 +556,19 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
         }
 
         $module = new stdClass();
+        $plugin = new stdClass(); // Prevent some notices when plugin placed in wrong directory.
         require($fullmod .'/version.php');  // defines $module with version etc
 
+        if (!isset($module->version) and isset($plugin->version)) {
+            $module = $plugin;
+        }
+
         // if plugin tells us it's full name we may check the location
         if (isset($module->component)) {
             if ($module->component !== $component) {
-                throw new plugin_defective_exception($component, 'Plugin installed in wrong folder.');
+                $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullmod);
+                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', get_component_directory($module->component));
+                throw new plugin_misplaced_exception($component, $expected, $current);
             }
         }
 
@@ -703,15 +734,21 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
             throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
         }
         $plugin = new stdClass();
+        $module = new stdClass(); // Prevent some notices when module placed in wrong directory.
         $plugin->version = NULL;
         $plugin->cron    = 0;
         include($fullblock.'/version.php');
+        if (!isset($plugin->version) and isset($module->version)) {
+            $plugin = $module;
+        }
         $block = $plugin;
 
         // if plugin tells us it's full name we may check the location
         if (isset($block->component)) {
             if ($block->component !== $component) {
-                throw new plugin_defective_exception($component, 'Plugin installed in wrong folder.');
+                $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullblock);
+                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', get_component_directory($block->component));
+                throw new plugin_misplaced_exception($component, $expected, $current);
             }
         }
 
index 6168abd..f732a36 100644 (file)
@@ -1,17 +1,19 @@
 Description of XHProf 0.9.2 library/viewer import into Moodle
 
-Removed:
+Removed (commit #1):
+ * .arcconfig - Definitions for arcanist/phabricator removed completely
+ * composer.json - Composer's definition removed completely
  * examples - examples dir removed completely
  * extension - extension dir removed completely
  * package.xml - PECL package definition removed completely
  * xhprof_html/docs - documentation dir removed completely
 
-Added:
+Added (commit #2 - always taken from current moodle.git master):
  * index.html - prevent directory browsing on misconfigured servers
  * xhprof_moodle.php - containing all the stuff needed to run the xhprof profiler within Moodle
  * readme_moodle.txt - this file ;-)
 
-Our changes:  Look for "moodle" in code
+Our changes:  Look for "moodle" in code (commit #3 - always mimic from current moodle.git master):
  * xhprof_html/index.php  ----|
  * xhprof_html/callgraph.php -|=> Changed to use own DB iXHProfRuns implementation (moodle_xhprofrun)
  * xhprof_html/typeahead.php -|
index 9f7acab..a315dbc 100644 (file)
  * @author Changhao Jiang (cjiang@facebook.com)
  */
 
-// start moodle modification: moodleize this script
+// Start moodle modification: moodleize this script.
 require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
 require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
 require_login();
 require_capability('moodle/site:config', context_system::instance());
-// end moodle modification
+// End moodle modification.
 
 // by default assume that xhprof_html & xhprof_lib directories
 // are at the same level.
@@ -85,11 +85,10 @@ if (!array_key_exists($type, $xhprof_legal_image_types)) {
   $type = $params['type'][1]; // default image type.
 }
 
-// start moodle modification: use own XHProfRuns implementation
-//$xhprof_runs_impl = new XHProfRuns_Default();
-require_once($GLOBALS['XHPROF_LIB_ROOT'].'/../xhprof_moodle.php');
+// Start moodle modification: use own XHProfRuns implementation.
+// $xhprof_runs_impl = new XHProfRuns_Default();
 $xhprof_runs_impl = new moodle_xhprofrun();
-// end moodle modification
+// End moodle modification.
 
 if (!empty($run)) {
   // single run call graph image generation
index 712fc77..fc727d9 100644 (file)
  *  limitations under the License.
  */
 
-/* start moodle modification: add basic, smaller, font specs */
+/* Start moodle modification: add basic, smaller, font specs */
 body, p, table, li {
-  font: normal normal normal 13px/1.231 arial, helvetica, clean, sans-serif;
+    font: normal normal normal 13px/1.231 arial, helvetica, clean, sans-serif;
 }
-
-/* end moodle modification */
-
+/* End moodle modification */
 
 td.sorted {
   color:#0000FF;
index e032916..4748424 100644 (file)
 //             Changhao Jiang
 //
 
-// start moodle modification: moodleize this script
+// Start moodle modification: moodleize this script.
 require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
 require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
 require_login();
 require_capability('moodle/site:config', context_system::instance());
-// end moodle modification
+// End moodle modification.
 
 // by default assume that xhprof_html & xhprof_lib directories
 // are at the same level.
@@ -87,10 +87,10 @@ $vbbar = ' class="vbbar"';
 $vrbar = ' class="vrbar"';
 $vgbar = ' class="vgbar"';
 
-// start moodle modification: use own XHProfRuns implementation
-//$xhprof_runs_impl = new XHProfRuns_Default()
+// Start moodle modification: use own XHProfRuns implementation.
+// $xhprof_runs_impl = new XHProfRuns_Default();
 $xhprof_runs_impl = new moodle_xhprofrun();
-// end moodle modification
+// End moodle modification.
 
 displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts,
                     $symbol, $sort, $run1, $run2);
index 085ccae..9c26a71 100644 (file)
Binary files a/lib/xhprof/xhprof_html/jquery/indicator.gif and b/lib/xhprof/xhprof_html/jquery/indicator.gif differ
index 78a7537..0cebba7 100644 (file)
  *             Changhao Jiang
  */
 
-// start moodle modification: moodleize this script
+// Start moodle modification: moodleize this script.
 require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
 require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
 require_login();
 require_capability('moodle/site:config', context_system::instance());
-// end moodle modification
+// End moodle modification.
 
 // by default assume that xhprof_html & xhprof_lib directories
 // are at the same level.
@@ -34,10 +34,9 @@ $GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib';
 
 require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php';
 
-// start moodle modification: use own XHProfRuns implementation
-//$xhprof_runs_impl = new XHProfRuns_Default();
-require_once($GLOBALS['XHPROF_LIB_ROOT'].'/../xhprof_moodle.php');
+// Start moodle modification: use own XHProfRuns implementation.
+// $xhprof_runs_impl = new XHProfRuns_Default();
 $xhprof_runs_impl = new moodle_xhprofrun();
-// end moodle modification
+// End moodle modification.
 
 require_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/typeahead_common.php';
index 203ecee..93ccaa6 100644 (file)
@@ -25,6 +25,7 @@ $xhprof_legal_image_types = array(
     "jpg" => 1,
     "gif" => 1,
     "png" => 1,
+    "svg" => 1, // support scalable vector graphic
     "ps"  => 1,
     );
 
@@ -68,6 +69,9 @@ function xhprof_generate_mime_header($type, $length) {
     case 'png':
       $mime = 'image/png';
       break;
+    case 'svg':
+      $mime = 'image/svg+xml'; // content type for scalable vector graphic
+      break;
     case 'ps':
       $mime = 'application/postscript';
     default:
@@ -103,11 +107,11 @@ function xhprof_generate_image_by_dot($dot_script, $type) {
        2 => array("pipe", "w")
        );
 
-  // start moodle modification: use $CFG->pathtodot for executing this
-  //$cmd = " dot -T".$type;
+  // Start moodle modification: use $CFG->pathtodot for executing this.
+  // $cmd = " dot -T".$type;
   global $CFG;
   $cmd = (!empty($CFG->pathtodot) ? $CFG->pathtodot : 'dot') . ' -T' . $type;
-  // end moodle modification
+  // End moodle modification.
 
   $process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array());
   if (is_resource($process)) {
index c12f77c..cde5ff5 100644 (file)
@@ -149,7 +149,9 @@ class XHProfRuns_Default implements iXHProfRuns {
   function list_runs() {
     if (is_dir($this->dir)) {
         echo "<hr/>Existing runs:\n<ul>\n";
-        foreach (glob("{$this->dir}/*.{$this->suffix}") as $file) {
+        $files = glob("{$this->dir}/*.{$this->suffix}");
+        usort($files, create_function('$a,$b', 'return filemtime($b) - filemtime($a);'));
+        foreach ($files as $file) {
             list($run,$source) = explode('.', basename($file));
             echo '<li><a href="' . htmlentities($_SERVER['SCRIPT_NAME'])
                 . '?run=' . htmlentities($run) . '&source='
index 526ae85..8e91f12 100644 (file)
@@ -117,7 +117,9 @@ class assign_grading_table extends table_sql implements renderable {
         $params['assignmentid1'] = (int)$this->assignment->get_instance()->id;
         $params['assignmentid2'] = (int)$this->assignment->get_instance()->id;
 
-        $fields = user_picture::fields('u') . ', ';
+        $extrauserfields = get_extra_user_fields($this->assignment->get_context());
+
+        $fields = user_picture::fields('u', $extrauserfields) . ', ';
         $fields .= 'u.id as userid, ';
         $fields .= 's.status as status, ';
         $fields .= 's.id as submissionid, ';
@@ -187,6 +189,11 @@ class assign_grading_table extends table_sql implements renderable {
             // Fullname.
             $columns[] = 'fullname';
             $headers[] = get_string('fullname');
+
+            foreach ($extrauserfields as $extrafield) {
+                $columns[] = $extrafield;
+                $headers[] = get_user_field_name($extrafield);
+            }
         } else {
             // Record ID.
             $columns[] = 'recordid';
@@ -300,6 +307,9 @@ class assign_grading_table extends table_sql implements renderable {
         // Set the columns.
         $this->define_columns($columns);
         $this->define_headers($headers);
+        foreach ($extrauserfields as $extrafield) {
+             $this->column_class($extrafield, $extrafield);
+        }
         // We require at least one unique column for the sort.
         $this->sortable(true, 'userid');
         $this->no_sorting('recordid');
@@ -896,6 +906,12 @@ class assign_grading_table extends table_sql implements renderable {
      * @return mixed string or NULL
      */
     public function other_cols($colname, $row) {
+        // For extra user fields the result is already in $row.
+        if (empty($this->plugincache[$colname])) {
+            return $row->$colname;
+        }
+
+        // This must be a plugin field.
         $plugincache = $this->plugincache[$colname];
 
         $plugin = $plugincache[0];
index 04dbde7..aad2a58 100644 (file)
@@ -337,49 +337,8 @@ function assign_print_overview($courses, &$htmlarray) {
     // We do all possible database work here *outside* of the loop to ensure this scales.
     list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
 
-    // Build up and array of unmarked submissions indexed by assignment id/ userid
-    // for use where the user has grading rights on assignment.
-    $dbparams = array_merge(array(ASSIGN_SUBMISSION_STATUS_SUBMITTED), $assignmentidparams);
-    $rs = $DB->get_recordset_sql('SELECT
-                                      s.assignment as assignment,
-                                      s.userid as userid,
-                                      s.id as id,
-                                      s.status as status,
-                                      g.timemodified as timegraded
-                                  FROM {assign_submission} s
-                                  LEFT JOIN {assign_grades} g ON
-                                      s.userid = g.userid AND
-                                      s.assignment = g.assignment
-                                  WHERE
-                                      ( g.timemodified is NULL OR
-                                      s.timemodified > g.timemodified ) AND
-                                      s.timemodified IS NOT NULL AND
-                                      s.status = ? AND
-                                      s.assignment ' . $sqlassignmentids, $dbparams);
-
-    $unmarkedsubmissions = array();
-    foreach ($rs as $rd) {
-        $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
-    }
-    $rs->close();
-
-    // Get all user submissions, indexed by assignment id.
-    $dbparams = array_merge(array($USER->id, $USER->id), $assignmentidparams);
-    $mysubmissions = $DB->get_records_sql('SELECT
-                                               a.id AS assignment,
-                                               a.nosubmissions AS nosubmissions,
-                                               g.timemodified AS timemarked,
-                                               g.grader AS grader,
-                                               g.grade AS grade,
-                                               s.status AS status
-                                           FROM {assign} a
-                                           LEFT JOIN {assign_grades} g ON
-                                               g.assignment = a.id AND
-                                               g.userid = ?
-                                           LEFT JOIN {assign_submission} s ON
-                                               s.assignment = a.id AND
-                                               s.userid = ?
-                                           WHERE a.id ' . $sqlassignmentids, $dbparams);
+    $mysubmissions = null;
+    $unmarkedsubmissions = null;
 
     foreach ($assignments as $assignment) {
         // Do not show assignments that are not open.
@@ -415,6 +374,34 @@ function assign_print_overview($courses, &$htmlarray) {
         }
         $context = context_module::instance($assignment->coursemodule);
         if (has_capability('mod/assign:grade', $context)) {
+            if (!isset($unmarkedsubmissions)) {
+                // Build up and array of unmarked submissions indexed by assignment id/ userid
+                // for use where the user has grading rights on assignment.
+                $dbparams = array_merge(array(ASSIGN_SUBMISSION_STATUS_SUBMITTED), $assignmentidparams);
+                $rs = $DB->get_recordset_sql('SELECT
+                                                  s.assignment as assignment,
+                                                  s.userid as userid,
+                                                  s.id as id,
+                                                  s.status as status,
+                                                  g.timemodified as timegraded
+                                              FROM {assign_submission} s
+                                              LEFT JOIN {assign_grades} g ON
+                                                  s.userid = g.userid AND
+                                                  s.assignment = g.assignment
+                                              WHERE
+                                                  ( g.timemodified is NULL OR
+                                                  s.timemodified > g.timemodified ) AND
+                                                  s.timemodified IS NOT NULL AND
+                                                  s.status = ? AND
+                                                  s.assignment ' . $sqlassignmentids, $dbparams);
+
+                $unmarkedsubmissions = array();
+                foreach ($rs as $rd) {
+                    $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
+                }
+                $rs->close();
+            }
+
             // Count how many people can submit.
             $submissions = 0;
             if ($students = get_enrolled_users($context, 'mod/assign:view', 0, 'u.id')) {
@@ -435,6 +422,26 @@ function assign_print_overview($courses, &$htmlarray) {
             }
         }
         if (has_capability('mod/assign:submit', $context)) {
+            if (!isset($mysubmissions)) {
+                // Get all user submissions, indexed by assignment id.
+                $dbparams = array_merge(array($USER->id, $USER->id), $assignmentidparams);
+                $mysubmissions = $DB->get_records_sql('SELECT
+                                                           a.id AS assignment,
+                                                           a.nosubmissions AS nosubmissions,
+                                                           g.timemodified AS timemarked,
+                                                           g.grader AS grader,
+                                                           g.grade AS grade,
+                                                           s.status AS status
+                                                       FROM {assign} a
+                                                       LEFT JOIN {assign_grades} g ON
+                                                           g.assignment = a.id AND
+                                                           g.userid = ?
+                                                       LEFT JOIN {assign_submission} s ON
+                                                           s.assignment = a.id AND
+                                                           s.userid = ?
+                                                       WHERE a.id ' . $sqlassignmentids, $dbparams);
+            }
+
             $str .= '<div class="details">';
             $str .= get_string('mysubmission', 'assign');
             $submission = $mysubmissions[$assignment->id];
index e428889..9ba0f6d 100644 (file)
@@ -136,8 +136,12 @@ class assign {
      * @return void
      */
     public function register_return_link($action, $params) {
-        $this->returnaction = $action;
-        $this->returnparams = $params;
+        global $PAGE;
+        $params['action'] = $action;
+        $currenturl = $PAGE->url;
+
+        $currenturl->params($params);
+        $PAGE->set_url($currenturl);
     }
 
     /**
@@ -146,7 +150,14 @@ class assign {
      * @return string action
      */
     public function get_return_action() {
-        return $this->returnaction;
+        global $PAGE;
+
+        $params = $PAGE->url->params();
+
+        if (!empty($params['action'])) {
+            return $params['action'];
+        }
+        return '';
     }
 
     /**
@@ -168,7 +179,12 @@ class assign {
      * @return array params
      */
     public function get_return_params() {
-        return $this->returnparams;
+        global $PAGE;
+
+        $params = $PAGE->url->params();
+        unset($params['id']);
+        unset($params['action']);
+        return $params;
     }
 
     /**
@@ -330,79 +346,102 @@ class assign {
         $mform = null;
         $notices = array();
 
+        $nextpageparams = array('id'=>$this->get_course_module()->id);
+
         // Handle form submissions first.
         if ($action == 'savesubmission') {
             $action = 'editsubmission';
             if ($this->process_save_submission($mform, $notices)) {
-                $action = 'view';
+                $action = 'redirect';
+                $nextpageparams['action'] = 'view';
             }
         } else if ($action == 'lock') {
             $this->process_lock();
-            $action = 'grading';
+            $action = 'redirect';
+            $nextpageparams['action'] = 'grading';
         } else if ($action == 'reverttodraft') {
             $this->process_revert_to_draft();
-            $action = 'grading';
+            $action = 'redirect';
+            $nextpageparams['action'] = 'grading';
         } else if ($action == 'unlock') {
             $this->process_unlock();
-            $action = 'grading';
+            $action = 'redirect';
+            $nextpageparams['action'] = 'grading';
         } else if ($action == 'confirmsubmit') {
             $action = 'submit';
             if ($this->process_submit_for_grading($mform)) {
-                $action = 'view';
+                $action = 'redirect';
+                $nextpageparams['action'] = 'view';
             }
         } else if ($action == 'gradingbatchoperation') {
             $action = $this->process_grading_batch_operation($mform);
+            if ($action == 'grading') {
+                $action = 'redirect';
+                $nextpageparams['action'] = 'grading';
+            }
         } else if ($action == 'submitgrade') {
             if (optional_param('saveandshownext', null, PARAM_RAW)) {
                 // Save and show next.
                 $action = 'grade';
                 if ($this->process_save_grade($mform)) {
-                    $action = 'nextgrade';
+                    $action = 'redirect';
+                    $nextpageparams['action'] = 'grade';
+                    $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
+                    $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
                 }
             } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
-                $action = 'previousgrade';
+                $action = 'redirect';
+                $nextpageparams['action'] = 'grade';
+                $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
+                $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
             } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
-                // Show next button.
-                $action = 'nextgrade';
+                $action = 'redirect';
+                $nextpageparams['action'] = 'grade';
+                $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
+                $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
             } else if (optional_param('savegrade', null, PARAM_RAW)) {
                 // Save changes button.
                 $action = 'grade';
                 if ($this->process_save_grade($mform)) {
-                    $action = 'grading';
+                    $action = 'redirect';
+                    $nextpageparams['action'] = 'grading';
                 }
             } else {
                 // Cancel button.
-                $action = 'grading';
+                $action = 'redirect';
+                $nextpageparams['action'] = 'grading';
             }
         } else if ($action == 'quickgrade') {
             $message = $this->process_save_quick_grades();
             $action = 'quickgradingresult';
         } else if ($action == 'saveoptions') {
             $this->process_save_grading_options();
-            $action = 'grading';
+            $action = 'redirect';
+            $nextpageparams['action'] = 'grading';
         } else if ($action == 'saveextension') {
             $action = 'grantextension';
             if ($this->process_save_extension($mform)) {
-                $action = 'grading';
+                $action = 'redirect';
+                $nextpageparams['action'] = 'grading';
             }
         } else if ($action == 'revealidentitiesconfirm') {
             $this->process_reveal_identities();
-            $action = 'grading';
+            $action = 'redirect';
+            $nextpageparams['action'] = 'grading';
         }
 
-        $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
+        $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
+                              'useridlistid'=>optional_param('useridlistid', 0, PARAM_INT));
         $this->register_return_link($action, $returnparams);
 
         // Now show the right view page.
-        if ($action == 'previousgrade') {
-            $mform = null;
-            $o .= $this->view_single_grade_page($mform, -1);
+        if ($action == 'redirect') {
+            $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
+            redirect($nextpageurl);
+            return;
         } else if ($action == 'quickgradingresult') {
             $mform = null;
             $o .= $this->view_quickgrading_result($message);
-        } else if ($action == 'nextgrade') {
-            $mform = null;
-            $o .= $this->view_single_grade_page($mform, 1);
         } else if ($action == 'grade') {
             $o .= $this->view_single_grade_page($mform);
         } else if ($action == 'viewpluginassignfeedback') {
@@ -2332,10 +2371,9 @@ class assign {
      * Print the grading page for a single user submission.
      *
      * @param moodleform $mform
-     * @param int $offset
      * @return string
      */
-    protected function view_single_grade_page($mform, $offset=0) {
+    protected function view_single_grade_page($mform) {
         global $DB, $CFG;
 
         $o = '';
@@ -2353,21 +2391,23 @@ class assign {
                                     get_string('grading', 'assign'));
         $o .= $this->get_renderer()->render($header);
 
-        $rownum = required_param('rownum', PARAM_INT) + $offset;
-        $useridlist = optional_param('useridlist', '', PARAM_TEXT);
-        if ($useridlist) {
-            $useridlist = explode(',', $useridlist);
-        } else {
+        $rownum = required_param('rownum', PARAM_INT);
+        $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
+        $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
+        if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
             $useridlist = $this->get_grading_userid_list();
+            $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
+        }
+
+        if ($rownum < 0 || $rownum > count($useridlist)) {
+            throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
         }
+
         $last = false;
         $userid = $useridlist[$rownum];
         if ($rownum == count($useridlist) - 1) {
             $last = true;
         }
-        if (!$userid) {
-            throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
-        }
         $user = $DB->get_record('user', array('id' => $userid));
         if ($user) {
             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
@@ -2375,7 +2415,8 @@ class assign {
                                                    $this->get_course()->id,
                                                    $viewfullnames,
                                                    $this->is_blind_marking(),
-                                                   $this->get_uniqueid_for_user($user->id));
+                                                   $this->get_uniqueid_for_user($user->id),
+                                                   get_extra_user_fields($this->get_context()));
             $o .= $this->get_renderer()->render($usersummary);
         }
         $submission = $this->get_user_submission($userid, false);
@@ -2457,7 +2498,7 @@ class assign {
 
         // Now show the grading form.
         if (!$mform) {
-            $pagination = array( 'rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last);
+            $pagination = array( 'rownum'=>$rownum, 'useridlistid'=>$useridlistid, 'last'=>$last);
             $formparams = array($this, $data, $pagination);
             $mform = new mod_assign_grade_form(null,
                                                $formparams,
@@ -2520,6 +2561,7 @@ class assign {
         $returnparams = optional_param('returnparams', '', PARAM_TEXT);
 
         $params = array();
+        $returnparams = str_replace('&amp;', '&', $returnparams);
         parse_str($returnparams, $params);
         $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
         $params = array_merge($newparams, $params);
@@ -2580,10 +2622,6 @@ class assign {
         $filter = get_user_preferences('assign_filter', '');
         $controller = $gradingmanager->get_active_controller();
         $showquickgrading = empty($controller);
-        if (optional_param('action', '', PARAM_ALPHA) == 'saveoptions') {
-            $quickgrading = optional_param('quickgrading', false, PARAM_BOOL);
-            set_user_preference('assign_quickgrading', $quickgrading);
-        }
         $quickgrading = get_user_preferences('assign_quickgrading', false);
 
         // Print options for changing the filter and changing the number of results per page.
@@ -3606,6 +3644,9 @@ class assign {
         $info = new stdClass();
         if ($blindmarking) {
             $info->username = get_string('participant', 'assign') . ' ' . $uniqueidforuser;
+            $userfrom->firstname = get_string('participant', 'assign');
+            $userfrom->lastname = $uniqueidforuser;
+            $userfrom->email = $CFG->noreplyaddress;
         } else {
             $info->username = fullname($userfrom, true);
         }
@@ -4116,11 +4157,17 @@ class assign {
         // Need submit permission to submit an assignment.
         require_capability('mod/assign:grade', $this->context);
 
+        // Is advanced grading enabled?
+        $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
+        $controller = $gradingmanager->get_active_controller();
+        $showquickgrading = empty($controller);
+
         $gradingoptionsparams = array('cm'=>$this->get_course_module()->id,
                                       'contextid'=>$this->context->id,
                                       'userid'=>$USER->id,
                                       'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
-                                      'showquickgrading'=>false);
+                                      'showquickgrading'=>$showquickgrading,
+                                      'quickgrading'=>false);
 
         $mform = new mod_assign_grading_options_form(null, $gradingoptionsparams);
         if ($formdata = $mform->get_data()) {
@@ -4128,6 +4175,9 @@ class assign {
             if (isset($formdata->filter)) {
                 set_user_preference('assign_filter', $formdata->filter);
             }
+            if ($showquickgrading) {
+                set_user_preference('assign_quickgrading', isset($formdata->quickgrading));
+            }
         }
     }
 
@@ -4371,7 +4421,13 @@ class assign {
 
         $rownum = $params['rownum'];
         $last = $params['last'];
-        $useridlist = $params['useridlist'];
+        $useridlistid = $params['useridlistid'];
+        $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
+        if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
+            $useridlist = $this->get_grading_userid_list();
+            $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
+        }
+
         $userid = $useridlist[$rownum];
         $grade = $this->get_user_grade($userid, false);
 
@@ -4478,8 +4534,8 @@ class assign {
         $mform->addElement('hidden', 'rownum', $rownum);
         $mform->setType('rownum', PARAM_INT);
         $mform->setConstant('rownum', $rownum);
-        $mform->addElement('hidden', 'useridlist', implode(',', $useridlist));
-        $mform->setType('useridlist', PARAM_TEXT);
+        $mform->addElement('hidden', 'useridlistid', $useridlistid);
+        $mform->setType('useridlistid', PARAM_INT);
         $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
         $mform->setType('ajax', PARAM_INT);
 
@@ -4847,11 +4903,11 @@ class assign {
         require_sesskey();
 
         $rownum = required_param('rownum', PARAM_INT);
-        $useridlist = optional_param('useridlist', '', PARAM_TEXT);
-        if ($useridlist) {
-            $useridlist = explode(',', $useridlist);
-        } else {
+        $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
+        $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
+        if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
             $useridlist = $this->get_grading_userid_list();
+            $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
         }
         $last = false;
         $userid = $useridlist[$rownum];
@@ -4861,7 +4917,7 @@ class assign {
 
         $data = new stdClass();
 
-        $gradeformparams = array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>false);
+        $gradeformparams = array('rownum'=>$rownum, 'useridlistid'=>$useridlistid, 'last'=>false);
         $mform = new mod_assign_grade_form(null,
                                            array($this, $data, $gradeformparams),
                                            'post',
index 02b7c35..c62a4cb 100644 (file)
@@ -119,23 +119,30 @@ class assign_user_summary implements renderable {
     public $blindmarking = false;
     /** @var int $uniqueidforuser */
     public $uniqueidforuser;
+    /** @var array $extrauserfields */
+    public $extrauserfields;
 
     /**
      * Constructor
      * @param stdClass $user
      * @param int $courseid
      * @param bool $viewfullnames
+     * @param bool $blindmarking
+     * @param int $uniqueidforuser
+     * @param array $extrauserfields
      */
     public function __construct(stdClass $user,
                                 $courseid,
                                 $viewfullnames,
                                 $blindmarking,
-                                $uniqueidforuser) {
+                                $uniqueidforuser,
+                                $extrauserfields) {
         $this->user = $user;
         $this->courseid = $courseid;
         $this->viewfullnames = $viewfullnames;
         $this->blindmarking = $blindmarking;
         $this->uniqueidforuser = $uniqueidforuser;
+        $this->extrauserfields = $extrauserfields;
     }
 }
 
index 181e353..7fd5c42 100644 (file)
@@ -137,7 +137,15 @@ class mod_assign_renderer extends plugin_renderer_base {
             $o .= $this->output->spacer(array('width'=>30));
             $urlparams = array('id' => $summary->user->id, 'course'=>$summary->courseid);
             $url = new moodle_url('/user/view.php', $urlparams);
-            $o .= $this->output->action_link($url, fullname($summary->user, $summary->viewfullnames));
+            $fullname = fullname($summary->user, $summary->viewfullnames);
+            $extrainfo = array();
+            foreach ($summary->extrauserfields as $extrafield) {
+                $extrainfo[] = $summary->user->$extrafield;
+            }
+            if (count($extrainfo)) {
+                $fullname .= ' (' . implode(', ', $extrainfo) . ')';
+            }
+            $o .= $this->output->action_link($url, $fullname);
         }
         $o .= $this->output->box_end();
         $o .= $this->output->container_end();
index aa3c880..16bca8b 100644 (file)
@@ -150,3 +150,13 @@ td.submissioneditable {
 .hidefull {
     display: none;
 }
+
+.quickgradingform form .commentscontainer input,
+.quickgradingform form .commentscontainer textarea {
+    display: none;
+}
+
+.jsenabled .quickgradingform form .commentscontainer input,
+.jsenabled .quickgradingform form .commentscontainer textarea {
+    display: inline;
+}
index 33f97be..6922ab3 100644 (file)
@@ -71,7 +71,8 @@ class assign_submission_comments extends assign_submission_plugin {
         $comment = new comment($options);
         $comment->set_view_permission(true);
 
-        return $comment->output(true);
+        $o = $this->assignment->get_renderer()->container($comment->output(true), 'commentscontainer');
+        return $o;
     }
 
     /**
diff --git a/mod/assign/tests/base_test.php b/mod/assign/tests/base_test.php
new file mode 100644 (file)
index 0000000..e349a2e
--- /dev/null
@@ -0,0 +1,248 @@
+<?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/>.
+
+/**
+ * Base class for unit tests for mod_assign.
+ *
+ * @package    mod_assign
+ * @category   phpunit
+ * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
+
+/**
+ * Unit tests for (some of) mod/assign/locallib.php.
+ *
+ * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_assign_base_testcase extends advanced_testcase {
+
+    /** @const Default number of students to create */
+    const DEFAULT_STUDENT_COUNT = 3;
+    /** @const Default number of teachers to create */
+    const DEFAULT_TEACHER_COUNT = 2;
+    /** @const Default number of editing teachers to create */
+    const DEFAULT_EDITING_TEACHER_COUNT = 2;
+    /** @const Optional extra number of students to create */
+    const EXTRA_STUDENT_COUNT = 40;
+    /** @const Optional extra number of teachers to create */
+    const EXTRA_TEACHER_COUNT = 5;
+    /** @const Optional extra number of editing teachers to create */
+    const EXTRA_EDITING_TEACHER_COUNT = 5;
+    /** @const Number of groups to create */
+    const GROUP_COUNT = 6;
+
+    /** @var stdClass $course New course created to hold the assignments */
+    protected $course = null;
+
+    /** @var array $teachers List of DEFAULT_TEACHER_COUNT teachers in the course*/
+    protected $teachers = null;
+
+    /** @var array $editingteachers List of DEFAULT_EDITING_TEACHER_COUNT editing teachers in the course */
+    protected $editingteachers = null;
+
+    /** @var array $students List of DEFAULT_STUDENT_COUNT students in the course*/
+    protected $students = null;
+
+    /** @var array $extrateachers List of EXTRA_TEACHER_COUNT teachers in the course*/
+    protected $extrateachers = null;
+
+    /** @var array $extraeditingteachers List of EXTRA_EDITING_TEACHER_COUNT editing teachers in the course*/
+    protected $extraeditingteachers = null;
+
+    /** @var array $extrastudents List of EXTRA_STUDENT_COUNT students in the course*/
+    protected $extrastudents = null;
+
+    /** @var array $groups List of 10 groups in the course */
+    protected $groups = null;
+
+    /**
+     * Setup function - we will create a course and add an assign instance to it.
+     */
+    protected function setUp() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $this->course = $this->getDataGenerator()->create_course();
+        $this->teachers = array();
+        for ($i = 0; $i < self::DEFAULT_TEACHER_COUNT; $i++) {
+            array_push($this->teachers, $this->getDataGenerator()->create_user());
+        }
+
+        $this->editingteachers = array();
+        for ($i = 0; $i < self::DEFAULT_EDITING_TEACHER_COUNT; $i++) {
+            array_push($this->editingteachers, $this->getDataGenerator()->create_user());
+        }
+
+        $this->students = array();
+        for ($i = 0; $i < self::DEFAULT_STUDENT_COUNT; $i++) {
+            array_push($this->students, $this->getDataGenerator()->create_user());
+        }
+
+        $this->groups = array();
+        for ($i = 0; $i < self::GROUP_COUNT; $i++) {
+            array_push($this->groups, $this->getDataGenerator()->create_group(array('courseid'=>$this->course->id)));
+        }
+
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        foreach ($this->teachers as $i => $teacher) {
+            $this->getDataGenerator()->enrol_user($teacher->id,
+                                                  $this->course->id,
+                                                  $teacherrole->id);
+            groups_add_member($this->groups[$i % self::GROUP_COUNT], $teacher);
+        }
+
+        $editingteacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
+        foreach ($this->editingteachers as $i => $editingteacher) {
+            $this->getDataGenerator()->enrol_user($editingteacher->id,
+                                                  $this->course->id,
+                                                  $editingteacherrole->id);
+            groups_add_member($this->groups[$i % self::GROUP_COUNT], $editingteacher);
+        }
+
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        foreach ($this->students as $i => $student) {
+            $this->getDataGenerator()->enrol_user($student->id,
+                                                  $this->course->id,
+                                                  $studentrole->id);
+            groups_add_member($this->groups[$i % self::GROUP_COUNT], $student);
+        }
+    }
+
+    /*
+     * For tests that make sense to use alot of data, create extra students/teachers.
+     */
+    protected function createExtraUsers() {
+        global $DB;
+        $this->extrateachers = array();
+        for ($i = 0; $i < self::EXTRA_TEACHER_COUNT; $i++) {
+            array_push($this->extrateachers, $this->getDataGenerator()->create_user());
+        }
+
+        $this->extraeditingteachers = array();
+        for ($i = 0; $i < self::EXTRA_EDITING_TEACHER_COUNT; $i++) {
+            array_push($this->extraeditingteachers, $this->getDataGenerator()->create_user());
+        }
+
+        $this->extrastudents = array();
+        for ($i = 0; $i < self::EXTRA_STUDENT_COUNT; $i++) {
+            array_push($this->extrastudents, $this->getDataGenerator()->create_user());
+        }
+
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        foreach ($this->extrateachers as $i => $teacher) {
+            $this->getDataGenerator()->enrol_user($teacher->id,
+                                                  $this->course->id,
+                                                  $teacherrole->id);
+            groups_add_member($this->groups[$i % self::GROUP_COUNT], $teacher);
+        }
+
+        $editingteacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
+        foreach ($this->extraeditingteachers as $i => $editingteacher) {
+            $this->getDataGenerator()->enrol_user($editingteacher->id,
+                                                  $this->course->id,
+                                                  $editingteacherrole->id);
+            groups_add_member($this->groups[$i % self::GROUP_COUNT], $editingteacher);
+        }
+
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        foreach ($this->extrastudents as $i => $student) {
+            $this->getDataGenerator()->enrol_user($student->id,
+                                                  $this->course->id,
+                                                  $studentrole->id);
+            if ($i < (self::EXTRA_STUDENT_COUNT / 2)) {
+                groups_add_member($this->groups[$i % self::GROUP_COUNT], $student);
+            }
+        }
+
+    }
+
+    /**
+     * Convenience function to create a testable instance of an assignment.
+     *
+     * @param array $params Array of parameters to pass to the generator
+     * @return testable_assign Testable wrapper around the assign class.
+     */
+    protected function create_instance($params=array()) {
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $this->course->id;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+        return new testable_assign($context, $cm, $this->course);
+    }
+
+    public function test_create_instance() {
+        $this->assertNotEmpty($this->create_instance());
+    }
+
+}
+
+/**
+ * Test subclass that makes all the protected methods we want to test public.
+ */
+class testable_assign extends assign {
+
+    public function testable_process_reveal_identities() {
+        return parent::process_reveal_identities();
+    }
+
+    public function testable_show_intro() {
+        return parent::show_intro();
+    }
+
+    public function testable_delete_grades() {
+        return parent::delete_grades();
+    }
+
+    public function testable_apply_grade_to_user($formdata, $userid) {
+        return parent::apply_grade_to_user($formdata, $userid);
+    }
+
+    public function testable_get_grading_userid_list() {
+        return parent::get_grading_userid_list();
+    }
+
+    public function testable_is_graded($userid) {
+        return parent::is_graded($userid);
+    }
+
+    public function testable_update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
+        return parent::update_submission($submission, $userid, $updatetime, $teamsubmission);
+    }
+
+    public function testable_submissions_open($userid = 0) {
+        return parent::submissions_open($userid);
+    }
+
+    public function testable_save_user_extension($userid, $extensionduedate) {
+        return parent::save_user_extension($userid, $extensionduedate);
+    }
+
+    public function testable_get_graders($userid) {
+        // Changed method from protected to public.
+        return parent::get_graders($userid);
+    }
+}
index 9e8e8ed..065e062 100644 (file)
@@ -46,12 +46,6 @@ class mod_assign_generator extends testing_module_generator {
             throw new coding_exception('module generator requires $record->course');
         }
 
-        if (isset($options['idnumber'])) {
-            $record->cmidnumber = $options['idnumber'];
-        } else {
-            $record->cmidnumber = '';
-        }
-
         $defaultsettings = array(
             'name'                              => get_string('pluginname', 'assign').' '.$i,
             'intro'                             => 'Test assign ' . $i,
@@ -63,18 +57,13 @@ class mod_assign_generator extends testing_module_generator {
             'sendlatenotifications'             => 0,
             'duedate'                           => 0,
             'allowsubmissionsfromdate'          => 0,
-            'assignsubmission_onlinetext_enabled' => 0,
-            'assignsubmission_file_enabled'     => 0,
-            'assignsubmission_comments_enabled' => 0,
-            'assignfeedback_comments_enabled'   => 0,
-            'assignfeedback_file_enabled'       => 0,
-            'assignfeedback_offline_enabled'    => 0,
             'grade'                             => 100,
             'cutoffdate'                        => 0,
             'teamsubmission'                    => 0,
             'requireallteammemberssubmit'       => 0,
             'teamsubmissiongroupingid'          => 0,
             'blindmarking'                      => 0,
+            'cmidnumber'                        => ''
         );
 
         foreach ($defaultsettings as $name => $value) {
index 19d049c..93bcd3d 100644 (file)
@@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die();
 global $CFG;
 require_once($CFG->dirroot . '/mod/assign/lib.php');
 require_once($CFG->dirroot . '/mod/assign/locallib.php');
+require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
 
 /**
  * Unit tests for (some of) mod/assign/lib.php.
@@ -36,96 +37,10 @@ require_once($CFG->dirroot . '/mod/assign/locallib.php');
  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_assign_lib_testcase extends advanced_testcase {
-
-    /** @var stdClass $course New course created to hold the assignments */
-    protected $course = null;
-
-    /** @var array $teachers List of 5 default teachers in the course*/
-    protected $teachers = null;
-
-    /** @var array $editingteachers List of 5 default editing teachers in the course*/
-    protected $editingteachers = null;
-
-    /** @var array $students List of 100 default students in the course*/
-    protected $students = null;
-
-    /** @var array $groups List of 10 groups in the course */
-    protected $groups = null;
-
-    /**
-     * Setup function - we will create a course and users.
-     */
-    protected function setUp() {
-        global $DB, $CFG;
-
-        $this->resetAfterTest(true);
-
-        $this->course = $this->getDataGenerator()->create_course();
-        $this->teachers = array();
-        for ($i = 0; $i < 5; $i++) {
-            array_push($this->teachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->editingteachers = array();
-        for ($i = 0; $i < 5; $i++) {
-            array_push($this->editingteachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->students = array();
-        for ($i = 0; $i < 100; $i++) {
-            array_push($this->students, $this->getDataGenerator()->create_user());
-        }
-
-        $this->groups = array();
-        for ($i = 0; $i < 10; $i++) {
-            array_push($this->groups, $this->getDataGenerator()->create_group(array('courseid'=>$this->course->id)));
-        }
-
-        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
-        foreach ($this->teachers as $i => $teacher) {
-            $this->getDataGenerator()->enrol_user($teacher->id,
-                                                  $this->course->id,
-                                                  $teacherrole->id);
-            groups_add_member($this->groups[$i % 10], $teacher);
-        }
-
-        $editingteacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
-        foreach ($this->editingteachers as $i => $editingteacher) {
-            $this->getDataGenerator()->enrol_user($editingteacher->id,
-                                                  $this->course->id,
-                                                  $editingteacherrole->id);
-            groups_add_member($this->groups[$i % 10], $editingteacher);
-        }
-
-        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
-        foreach ($this->students as $i => $student) {
-            $this->getDataGenerator()->enrol_user($student->id,
-                                                  $this->course->id,
-                                                  $studentrole->id);
-            if ($i < 80) {
-                groups_add_member($this->groups[$i % 10], $student);
-            }
-        }
-    }
-
-    /**
-     * Create an assignment in the current course.
-     *
-     * @param array $params - A list of params used to configure the assignment
-     * @return assign class
-     */
-    private function create_instance($params=array()) {
-        $this->setUser($this->editingteachers[0]);
-        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
-        $params['course'] = $this->course->id;
-        $instance = $generator->create_instance($params);
-        $cm = get_coursemodule_from_instance('assign', $instance->id);
-        $context = context_module::instance($cm->id);
-        return new assign($context, $cm, $this->course);
-    }
+class mod_assign_lib_testcase extends mod_assign_base_testcase {
 
     public function test_assign_print_overview() {
+        $this->setUser($this->editingteachers[0]);
         $this->create_instance();
         $this->create_instance(array('duedate'=>time()));
 
@@ -146,6 +61,7 @@ class mod_assign_lib_testcase extends advanced_testcase {
     }
 
     public function test_print_recent_activity() {
+        $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
 
         $submission = $assign->get_user_submission($this->students[0]->id, true);
@@ -155,6 +71,7 @@ class mod_assign_lib_testcase extends advanced_testcase {
     }
 
     public function test_assign_get_recent_mod_activity() {
+        $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
 
         $submission = $assign->get_user_submission($this->students[0]->id, true);
@@ -177,16 +94,20 @@ class mod_assign_lib_testcase extends advanced_testcase {
     }
 
     public function test_assign_user_complete() {
+        global $PAGE;
+
+        $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance(array('submissiondrafts' => 1));
+        $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id'=>$assign->get_course_module()->id)));
 
         $submission = $assign->get_user_submission($this->students[0]->id, true);
 
         $this->expectOutputRegex('/Draft/');
         assign_user_complete($this->course, $this->students[0], $assign->get_course_module(), $assign->get_instance());
-
     }
 
     public function test_assign_user_outline() {
+        $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
 
         $this->setUser($this->teachers[0]);
index 2aba609..400a432 100644 (file)
@@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die();
 global $CFG;
 require_once($CFG->dirroot . '/mod/assign/locallib.php');
 require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
+require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
 
 /**
  * Unit tests for (some of) mod/assign/locallib.php.
@@ -36,163 +37,15 @@ require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_assign_locallib_testcase extends advanced_testcase {
-
-    /** @const Default number of students to create */
-    const DEFAULT_STUDENT_COUNT = 3;
-    /** @const Default number of teachers to create */
-    const DEFAULT_TEACHER_COUNT = 2;
-    /** @const Default number of editing teachers to create */
-    const DEFAULT_EDITING_TEACHER_COUNT = 2;
-    /** @const Optional extra number of students to create */
-    const EXTRA_STUDENT_COUNT = 40;
-    /** @const Optional extra number of teachers to create */
-    const EXTRA_TEACHER_COUNT = 5;
-    /** @const Optional extra number of editing teachers to create */
-    const EXTRA_EDITING_TEACHER_COUNT = 5;
-    /** @const Number of groups to create */
-    const GROUP_COUNT = 6;
-
-    /** @var stdClass $course New course created to hold the assignments */
-    protected $course = null;
-
-    /** @var array $teachers List of DEFAULT_TEACHER_COUNT teachers in the course*/
-    protected $teachers = null;
-
-    /** @var array $editingteachers List of DEFAULT_EDITING_TEACHER_COUNT editing teachers in the course */
-    protected $editingteachers = null;
-
-    /** @var array $students List of DEFAULT_STUDENT_COUNT students in the course*/
-    protected $students = null;
-
-    /** @var array $extrateachers List of EXTRA_TEACHER_COUNT teachers in the course*/
-    protected $extrateachers = null;
-
-    /** @var array $extraeditingteachers List of EXTRA_EDITING_TEACHER_COUNT editing teachers in the course*/
-    protected $extraeditingteachers = null;
-
-    /** @var array $extrastudents List of EXTRA_STUDENT_COUNT students in the course*/
-    protected $extrastudents = null;
-
-    /** @var array $groups List of 10 groups in the course */
-    protected $groups = null;
-
-    /**
-     * Setup function - we will create a course and add an assign instance to it.
-     */
-    protected function setUp() {
-        global $DB;
-
-        $this->resetAfterTest(true);
-
-        $this->course = $this->getDataGenerator()->create_course();
-        $this->teachers = array();
-        for ($i = 0; $i < self::DEFAULT_TEACHER_COUNT; $i++) {
-            array_push($this->teachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->editingteachers = array();
-        for ($i = 0; $i < self::DEFAULT_EDITING_TEACHER_COUNT; $i++) {
-            array_push($this->editingteachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->students = array();
-        for ($i = 0; $i < self::DEFAULT_STUDENT_COUNT; $i++) {
-            array_push($this->students, $this->getDataGenerator()->create_user());
-        }
-
-        $this->groups = array();
-        for ($i = 0; $i < self::GROUP_COUNT; $i++) {
-            array_push($this->groups, $this->getDataGenerator()->create_group(array('courseid'=>$this->course->id)));
-        }
-
-        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
-        foreach ($this->teachers as $i => $teacher) {
-            $this->getDataGenerator()->enrol_user($teacher->id,
-                                                  $this->course->id,
-                                                  $teacherrole->id);
-            groups_add_member($this->groups[$i % self::GROUP_COUNT], $teacher);
-        }
-
-        $editingteacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
-        foreach ($this->editingteachers as $i => $editingteacher) {
-            $this->getDataGenerator()->enrol_user($editingteacher->id,
-                                                  $this->course->id,
-                                                  $editingteacherrole->id);
-            groups_add_member($this->groups[$i % self::GROUP_COUNT], $editingteacher);
-        }
-
-        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
-        foreach ($this->students as $i => $student) {
-            $this->getDataGenerator()->enrol_user($student->id,
-                                                  $this->course->id,
-                                                  $studentrole->id);
-            groups_add_member($this->groups[$i % self::GROUP_COUNT], $student);
-        }
-    }
-
-    /*
-     * For tests that make sense to use alot of data, create extra students/teachers.
-     */
-    private function createExtraUsers() {
-        global $DB;
-        $this->extrateachers = array();
-        for ($i = 0; $i < self::EXTRA_TEACHER_COUNT; $i++) {
-            array_push($this->extrateachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->extraeditingteachers = array();
-        for ($i = 0; $i < self::EXTRA_EDITING_TEACHER_COUNT; $i++) {
-            array_push($this->extraeditingteachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->extrastudents = array();
-        for ($i = 0; $i < self::EXTRA_STUDENT_COUNT; $i++) {
-            array_push($this->extrastudents, $this->getDataGenerator()->create_user());
-        }
-
-        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
-        foreach ($this->extrateachers as $i => $teacher) {
-            $this->getDataGenerator()->enrol_user($teacher->id,
-                                                  $this->course->id,
-                                                  $teacherrole->id);
-            groups_add_member($this->groups[$i % self::GROUP_COUNT], $teacher);
-        }
-
-        $editingteacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
-        foreach ($this->extraeditingteachers as $i => $editingteacher) {
-            $this->getDataGenerator()->enrol_user($editingteacher->id,
-                                                  $this->course->id,
-                                                  $editingteacherrole->id);
-            groups_add_member($this->groups[$i % self::GROUP_COUNT], $editingteacher);
-        }
-
-        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
-        foreach ($this->extrastudents as $i => $student) {
-            $this->getDataGenerator()->enrol_user($student->id,
-                                                  $this->course->id,
-                                                  $studentrole->id);
-            if ($i < (self::EXTRA_STUDENT_COUNT / 2)) {
-                groups_add_member($this->groups[$i % self::GROUP_COUNT], $student);
-            }
-        }
-
-    }
-
-    private function create_instance($params=array()) {
-        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
-        $params['course'] = $this->course->id;
-        $instance = $generator->create_instance($params);
-        $cm = get_coursemodule_from_instance('assign', $instance->id);
-        $context = context_module::instance($cm->id);
-        return new testable_assign($context, $cm, $this->course);
-    }
+class mod_assign_locallib_testcase extends mod_assign_base_testcase {
 
     public function test_return_links() {
+        global $PAGE;
         $this->setUser($this->editingteachers[0]);
         $returnaction = 'RETURNACTION';
-        $returnparams = array('param'=>1);
+        $returnparams = array('param'=>'1');
         $assign = $this->create_instance();
+        $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
         $assign->register_return_link($returnaction, $returnparams);
         $this->assertEquals($returnaction, $assign->get_return_action());
         $this->assertEquals($returnparams, $assign->get_return_params());
@@ -201,13 +54,21 @@ class mod_assign_locallib_testcase extends advanced_testcase {
     public function test_get_feedback_plugins() {
         $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
-        $this->assertEquals(3, count($assign->get_feedback_plugins()));
+        $installedplugins = array_keys(get_plugin_list('assignfeedback'));
+
+        foreach ($assign->get_feedback_plugins() as $plugin) {
+            $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
+        }
     }
 
     public function test_get_submission_plugins() {
         $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
-        $this->assertEquals(3, count($assign->get_submission_plugins()));
+        $installedplugins = array_keys(get_plugin_list('assignsubmission'));
+
+        foreach ($assign->get_submission_plugins() as $plugin) {
+            $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
+        }
     }
 
     public function test_is_blind_marking() {
@@ -739,10 +600,11 @@ class mod_assign_locallib_testcase extends advanced_testcase {
     }
 
     public function test_show_student_summary() {
-        global $CFG;
+        global $CFG, $PAGE;
 
         $this->setUser($this->editingteachers[0]);
         $assign = $this->create_instance();
+        $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
 
         // No feedback should be available because this student has not been graded.
         $this->setUser($this->students[0]);
@@ -797,49 +659,3 @@ class mod_assign_locallib_testcase extends advanced_testcase {
 
 }
 
-/**
- * Test subclass that makes all the protected methods we want to test public.
- */
-class testable_assign extends assign {
-
-    public function testable_process_reveal_identities() {
-        return parent::process_reveal_identities();
-    }
-
-    public function testable_show_intro() {
-        return parent::show_intro();
-    }
-
-    public function testable_delete_grades() {
-        return parent::delete_grades();
-    }
-
-    public function testable_apply_grade_to_user($formdata, $userid) {
-        return parent::apply_grade_to_user($formdata, $userid);
-    }
-
-    public function testable_get_grading_userid_list() {
-        return parent::get_grading_userid_list();
-    }
-
-    public function testable_is_graded($userid) {
-        return parent::is_graded($userid);
-    }
-
-    public function testable_update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
-        return parent::update_submission($submission, $userid, $updatetime, $teamsubmission);
-    }
-
-    public function testable_submissions_open($userid = 0) {
-        return parent::submissions_open($userid);
-    }
-
-    public function testable_save_user_extension($userid, $extensionduedate) {
-        return parent::save_user_extension($userid, $extensionduedate);
-    }
-
-    public function testable_get_graders($userid) {
-        // Changed method from protected to public.
-        return parent::get_graders($userid);
-    }
-}
index d11c19f..0147c12 100644 (file)
@@ -30,6 +30,7 @@ global $CFG;
 require_once($CFG->dirroot . '/mod/assign/locallib.php');
 require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
 require_once($CFG->dirroot . '/mod/assignment/lib.php');
+require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
 
 /**
  * Unit tests for (some of) mod/assign/upgradelib.php.
@@ -37,78 +38,7 @@ require_once($CFG->dirroot . '/mod/assignment/lib.php');
  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class mod_assign_upgradelib_testcase extends advanced_testcase {
-
-    /** @var stdClass $course New course created to hold the assignments */
-    protected $course = null;
-
-    /** @var array $teachers List of 5 default teachers in the course*/
-    protected $teachers = null;
-
-    /** @var array $editingteachers List of 5 default editing teachers in the course*/
-    protected $editingteachers = null;
-
-    /** @var array $students List of 100 default students in the course*/
-    protected $students = null;
-
-    /** @var array $groups List of 10 groups in the course */
-    protected $groups = null;
-
-    /**
-     * Setup function - we will create a course and add users to it.
-     */
-    protected function setUp() {
-        global $DB, $CFG;
-
-        $this->resetAfterTest(true);
-
-        $this->course = $this->getDataGenerator()->create_course();
-        $this->teachers = array();
-        for ($i = 0; $i < 5; $i++) {
-            array_push($this->teachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->editingteachers = array();
-        for ($i = 0; $i < 5; $i++) {
-            array_push($this->editingteachers, $this->getDataGenerator()->create_user());
-        }
-
-        $this->students = array();
-        for ($i = 0; $i < 100; $i++) {
-            array_push($this->students, $this->getDataGenerator()->create_user());
-        }
-
-        $this->groups = array();
-        for ($i = 0; $i < 10; $i++) {
-            array_push($this->groups, $this->getDataGenerator()->create_group(array('courseid'=>$this->course->id)));
-        }
-
-        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
-        foreach ($this->teachers as $i => $teacher) {
-            $this->getDataGenerator()->enrol_user($teacher->id,
-                                                  $this->course->id,
-                                                  $teacherrole->id);
-            groups_add_member($this->groups[$i % 10], $teacher);
-        }
-
-        $editingteacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
-        foreach ($this->editingteachers as $i => $editingteacher) {
-            $this->getDataGenerator()->enrol_user($editingteacher->id,
-                                                  $this->course->id,
-                                                  $editingteacherrole->id);
-            groups_add_member($this->groups[$i % 10], $editingteacher);
-        }
-
-        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
-        foreach ($this->students as $i => $student) {
-            $this->getDataGenerator()->enrol_user($student->id,
-                                                  $this->course->id,
-                                                  $studentrole->id);
-            if ($i < 80) {
-                groups_add_member($this->groups[$i % 10], $student);
-            }
-        }
-    }
+class mod_assign_upgradelib_testcase extends mod_assign_base_testcase {
 
     public function test_upgrade_upload_assignment() {
         global $DB;
index 3be88b0..9fa7e24 100644 (file)
@@ -26,7 +26,13 @@ require_once('../../config.php');
 require_once($CFG->dirroot . '/mod/assign/locallib.php');
 
 $id = required_param('id', PARAM_INT);
-$url = new moodle_url('/mod/assign/view.php', array('id' => $id));
+
+$urlparams = array('id' => $id,
+                  'action' => optional_param('action', '', PARAM_TEXT),
+                  'rownum' => optional_param('rownum', 0, PARAM_INT),
+                  'useridlistid' => optional_param('action', 0, PARAM_INT));
+
+$url = new moodle_url('/mod/assign/view.php', $urlparams);
 $cm = get_coursemodule_from_id('assign', $id, 0, false, MUST_EXIST);
 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
 
index f9b93d4..d01a4b6 100644 (file)
@@ -1123,7 +1123,13 @@ class assignment_upload extends assignment_base {
      * @return bool                 Indicates if the submission was found to be complete
      */
     public function is_submitted_with_required_data($submission) {
-        return ($submission->timemodified AND $submission->data2);
+        if ($this->drafts_tracked()) {
+            $submitted = $submission->timemodified > 0 &&
+                         $submission->data2 == ASSIGNMENT_STATUS_SUBMITTED;
+        } else {
+            $submitted = $submission->numfiles > 0;
+        }
+        return $submitted;
     }
 }
 
index e941699..e6108ee 100644 (file)
@@ -78,7 +78,9 @@ class data_field_latlong extends data_field_base {
 
         $options = array();
         foreach ($latlongsrs as $latlong) {
-            $options[$latlong->la . ',' . $latlong->lo] = $latlong->la . ',' . $latlong->lo;
+            $latitude = format_float($latlong->la, 4);
+            $longitude = format_float($latlong->lo, 4);
+            $options[$latlong->la . ',' . $latlong->lo] = $latitude . ' ' . $longitude;
         }
         $latlongsrs->close();
 
@@ -120,15 +122,16 @@ class data_field_latlong extends data_field_base {
             if (strlen($long) < 1) {
                 return false;
             }
+            // We use format_float to display in the regional format.
             if($lat < 0) {
-                $compasslat = sprintf('%01.4f', -$lat) . '°S';
+                $compasslat = format_float(-$lat, 4) . '°S';
             } else {
-                $compasslat = sprintf('%01.4f', $lat) . '°N';
+                $compasslat = format_float($lat, 4) . '°N';
             }
             if($long < 0) {
-                $compasslong = sprintf('%01.4f', -$long) . '°W';
+                $compasslong = format_float(-$long, 4) . '°W';
             } else {
-                $compasslong = sprintf('%01.4f', $long) . '°E';
+                $compasslong = format_float($long, 4) . '°E';
             }
 
             // Now let's create the jump-to-services link
@@ -149,7 +152,7 @@ class data_field_latlong extends data_field_base {
             if(sizeof($servicesshown)==1 && $servicesshown[0]) {
                 $str = " <a href='"
                           . str_replace(array_keys($urlreplacements), array_values($urlreplacements), $this->linkoutservices[$servicesshown[0]])
-                          ."' title='$servicesshown[0]'>$compasslat, $compasslong</a>";
+                          ."' title='$servicesshown[0]'>$compasslat $compasslong</a>";
             } elseif (sizeof($servicesshown)>1) {
                 $str = '<form id="latlongfieldbrowse">';
                 $str .= "$compasslat, $compasslong\n";
@@ -180,6 +183,9 @@ class data_field_latlong extends data_field_base {
         $content = new stdClass();
         $content->fieldid = $this->field->id;
         $content->recordid = $recordid;
+        // When updating these values (which might be region formatted) we should format
+        // the float to allow for a consistent float format in the database.
+        $value = unformat_float($value);
         $value = trim($value);
         if (strlen($value) > 0) {
             $value = floatval($value);
@@ -213,6 +219,7 @@ class data_field_latlong extends data_field_base {
     }
 
     function export_text_value($record) {
+        // The content here is from the database and does not require location formating.
         return sprintf('%01.4f', $record->content) . ' ' . sprintf('%01.4f', $record->content1);
     }
 
index 5a4526b..9b7425b 100644 (file)
@@ -156,7 +156,12 @@ if (!$formdata = $form->get_data()) {
                     // for now, only for "latlong" and "url" fields, but that should better be looked up from
                     // $CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'
                     // once there is stored how many contents the field can have.
-                    if (preg_match("/^(latlong|url)$/", $field->type)) {
+                    if ($field->type == 'latlong') {
+                        $values = explode(" ", $value, 2);
+                        // The lat, long values might be in a different float format.
+                        $content->content  = unformat_float($values[0]);
+                        $content->content1 = unformat_float($values[1]);
+                    } else if ($field->type == 'url') {
                         $values = explode(" ", $value, 2);
                         $content->content  = $values[0];
                         // The url field doesn't always have two values (unforced autolinking).
index 1ee4f0c..a7bb672 100644 (file)
@@ -39,7 +39,7 @@ class backup_folder_activity_structure_step extends backup_activity_structure_st
         // Define each element separated
         $folder = new backup_nested_element('folder', array('id'), array(
             'name', 'intro', 'introformat', 'revision',
-            'timemodified'));
+            'timemodified', 'display'));
 
         // Build the tree
         // (nice mono-tree, lol)
index abbe143..9481f05 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/folder/db" VERSION="20120122" COMMENT="XMLDB file for Folder module"
+<XMLDB PATH="mod/folder/db" VERSION="20130121" COMMENT="XMLDB file for Folder module"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
@@ -13,6 +13,7 @@
         <FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="revision" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="incremented when after each file changes, solves browser caching issues"/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="display" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Display type of folder contents - on a separate page or inline"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index c7b3e4b..9db0090 100644 (file)
@@ -62,6 +62,20 @@ function xmldb_folder_upgrade($oldversion) {
     // Moodle v2.4.0 release upgrade line
     // Put any upgrade step following this
 
+    if ($oldversion < 2013012100) {
+
+        // Define field display to be added to folder
+        $table = new xmldb_table('folder');
+        $field = new xmldb_field('display', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'timemodified');
+
+        // Conditionally launch add field display
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // folder savepoint reached
+        upgrade_mod_savepoint(true, 2013012100, 'folder');
+    }
 
     return true;
 }
index f6aec48..687181f 100644 (file)
@@ -31,7 +31,7 @@ require_once("$CFG->dirroot/repository/lib.php");
 
 $id = required_param('id', PARAM_INT);  // Course module ID
 
-$cm = get_coursemodule_from_id('folder', $id, 0, false, MUST_EXIST);
+$cm = get_coursemodule_from_id('folder', $id, 0, true, MUST_EXIST);
 $context = context_module::instance($cm->id, MUST_EXIST);
 $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST);
 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
@@ -52,14 +52,19 @@ $options = array('subdirs'=>1, 'maxbytes'=>$CFG->maxbytes, 'maxfiles'=>-1, 'acce
 file_prepare_standard_filemanager($data, 'files', $options, $context, 'mod_folder', 'content', 0);
 
 $mform = new mod_folder_edit_form(null, array('data'=>$data, 'options'=>$options));
+if ($folder->display == FOLDER_DISPLAY_INLINE) {
+    $redirecturl = course_get_url($cm->course, $cm->sectionnum);
+} else {
+    $redirecturl = new moodle_url('/mod/folder/view.php', array('id' => $cm->id));
+}
 
 if ($mform->is_cancelled()) {
-    redirect(new moodle_url('/mod/folder/view.php', array('id'=>$cm->id)));
+    redirect($redirecturl);
 
 } else if ($formdata = $mform->get_data()) {
     $formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $context, 'mod_folder', 'content', 0);
     $DB->set_field('folder', 'revision', $folder->revision+1, array('id'=>$folder->id));
-    redirect(new moodle_url('/mod/folder/view.php', array('id'=>$cm->id)));
+    redirect($redirecturl);
 }
 
 echo $OUTPUT->header();
index 9197ba0..b713063 100644 (file)
@@ -44,3 +44,10 @@ $string['page-mod-folder-x'] = 'Any folder module page';
 $string['page-mod-folder-view'] = 'Folder module main page';
 $string['pluginadministration'] = 'Folder administration';
 $string['pluginname'] = 'Folder';
+$string['display'] = 'Display folder contents';
+$string['display_help'] = 'If you choose to display the folder contents on a course page, there  will be no link to a separate page and the title will not be displayed.
+The description will be displayed only if "Display description on course page" is checked.<br />
+Also note that participants view actions can not be logged in this case.';
+$string['displaypage'] = 'On a separate page';
+$string['displayinline'] = 'Inline on a course page';
+$string['noautocompletioninline'] = 'Automatic completion on viewing of activity can not be selected together with "Display inline" option';
index f349146..7e602cb 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+/** Display folder contents on a separate page */
+define('FOLDER_DISPLAY_PAGE', 0);
+/** Display folder contents inline in a course */
+define('FOLDER_DISPLAY_INLINE', 1);
+
 /**
  * List of features supported in Folder module
  * @param string $feature FEATURE_xx constant for requested feature
@@ -406,3 +411,86 @@ function folder_dndupload_handle($uploadinfo) {
     $DB->delete_records('folder', array('id' => $data->id));
     return false;
 }
+
+/**
+ * Given a coursemodule object, this function returns the extra
+ * information needed to print this activity in various places.
+ *
+ * If folder needs to be displayed inline we store additional information
+ * in customdata, so functions {@link folder_cm_info_dynamic()} and
+ * {@link folder_cm_info_view()} do not need to do DB queries
+ *
+ * @param cm_info $cm
+ * @return cached_cm_info info
+ */
+function folder_get_coursemodule_info($cm) {
+    global $DB;
+    if (!($folder = $DB->get_record('folder', array('id' => $cm->instance),
+            'id, name, display, intro, introformat'))) {
+        return NULL;
+    }
+    $cminfo = new cached_cm_info();
+    $cminfo->name = $folder->name;
+    if ($folder->display == FOLDER_DISPLAY_INLINE) {
+        // prepare folder object to store in customdata
+        $fdata = new stdClass();
+        if ($cm->showdescription && strlen(trim($folder->intro))) {
+            $fdata->intro = $folder->intro;
+            if ($folder->introformat != FORMAT_MOODLE) {
+                $fdata->introformat = $folder->introformat;
+            }
+        }
+        $cminfo->customdata = $fdata;
+    } else {
+        if ($cm->showdescription) {
+            // Convert intro to html. Do not filter cached version, filters run at display time.
+            $cminfo->content = format_module_intro('folder', $folder, $cm->id, false);
+        }
+    }
+    return $cminfo;
+}
+
+/**
+ * Sets dynamic information about a course module
+ *
+ * This function is called from cm_info when displaying the module
+ * mod_folder can be displayed inline on course page and therefore have no course link
+ *
+ * @param cm_info $cm
+ */
+function folder_cm_info_dynamic(cm_info $cm) {
+    if ($cm->get_custom_data()) {
+        // the field 'customdata' is not empty IF AND ONLY IF we display contens inline
+        $cm->set_no_view_link();
+    }
+}
+
+/**
+ * Overwrites the content in the course-module object with the folder files list
+ * if folder.display == FOLDER_DISPLAY_INLINE
+ *
+ * @param cm_info $cm
+ */
+function folder_cm_info_view(cm_info $cm) {
+    global $PAGE;
+    if ($cm->uservisible && $cm->get_custom_data() &&
+            has_capability('mod/folder:view', $cm->context)) {
+        // Restore folder object from customdata.
+        // Note the field 'customdata' is not empty IF AND ONLY IF we display contens inline.
+        // Otherwise the content is default.
+        $folder = $cm->get_custom_data();
+        $folder->id = (int)$cm->instance;
+        $folder->course = (int)$cm->course;
+        $folder->display = FOLDER_DISPLAY_INLINE;
+        $folder->name = $cm->name;
+        if (empty($folder->intro)) {
+            $folder->intro = '';
+        }
+        if (empty($folder->introformat)) {
+            $folder->introformat = FORMAT_MOODLE;
+        }
+        // display folder
+        $renderer = $PAGE->get_renderer('mod_folder');
+        $cm->set_content($renderer->display_folder($folder));
+    }
+}
index 358ddc3..70af9e1 100644 (file)
@@ -49,6 +49,10 @@ class mod_folder_mod_form extends moodleform_mod {
         //-------------------------------------------------------
         $mform->addElement('header', 'content', get_string('contentheader', 'folder'));
         $mform->addElement('filemanager', 'files', get_string('files'), null, array('subdirs'=>1, 'accepted_types'=>'*'));
+        $mform->addElement('select', 'display', get_string('display', 'mod_folder'),
+                array(FOLDER_DISPLAY_PAGE => get_string('displaypage', 'mod_folder'),
+                    FOLDER_DISPLAY_INLINE => get_string('displayinline', 'mod_folder')));
+        $mform->addHelpButton('display', 'display', 'mod_folder');
         $mform->setExpanded('content');
 
 
@@ -72,4 +76,20 @@ class mod_folder_mod_form extends moodleform_mod {
             $default_values['files'] = $draftitemid;
         }
     }
+
+    function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+
+        // Completion: Automatic on-view completion can not work together with
+        // "display inline" option
+        if (empty($errors['completion']) &&
+                array_key_exists('completion', $data) &&
+                $data['completion'] == COMPLETION_TRACKING_AUTOMATIC &&
+                !empty($data['completionview']) &&
+                $data['display'] == FOLDER_DISPLAY_INLINE) {
+            $errors['completion'] = get_string('noautocompletioninline', 'mod_folder');
+        }
+
+        return $errors;
+    }
 }
index db6f387..415d167 100644 (file)
@@ -24,9 +24,9 @@
 
 M.mod_folder = {};
 
-M.mod_folder.init_tree = function(Y, expand_all) {
+M.mod_folder.init_tree = function(Y, id, expand_all) {
     Y.use('yui2-treeview', function(Y) {
-        var tree = new Y.YUI2.widget.TreeView("folder_tree");
+        var tree = new Y.YUI2.widget.TreeView(id);
 
         tree.subscribe("clickEvent", function(node, event) {
             // we want normal clicking which redirects to url
index c570d8d..c9dbbb7 100644 (file)
@@ -28,23 +28,58 @@ defined('MOODLE_INTERNAL') || die();
 class mod_folder_renderer extends plugin_renderer_base {
 
     /**
-     * Prints file folder tree view
-     * @param object $folder instance
-     * @param object $cm instance
-     * @param object $course
-     * @return void
+     * Returns html to display the content of mod_folder
+     * (Description, folder files and optionally Edit button)
+     *
+     * @param stdClass $folder record from 'folder' table (please note
+     *     it may not contain fields 'revision' and 'timemodified')
+     * @return string
      */
-    public function folder_tree($folder, $cm, $course) {
-        $this->render(new folder_tree($folder, $cm, $course));
+    public function display_folder(stdClass $folder) {
+        $output = '';
+        $folderinstances = get_fast_modinfo($folder->course)->get_instances_of('folder');
+        if (!isset($folderinstances[$folder->id]) ||
+                !($cm = $folderinstances[$folder->id]) ||
+                !$cm->uservisible ||
+                !($context = context_module::instance($cm->id)) ||
+                !has_capability('mod/folder:view', $context)) {
+            // some error in parameters or module is not visible to the user
+            // don't throw any errors in renderer, just return empty string
+            return $output;
+        }
+
+        if (trim($folder->intro)) {
+            if ($folder->display != FOLDER_DISPLAY_INLINE) {
+                $output .= $this->output->box(format_module_intro('folder', $folder, $cm->id),
+                        'generalbox', 'intro');
+            } else if ($cm->showdescription) {
+                // for "display inline" do not filter, filters run at display time.
+                $output .= format_module_intro('folder', $folder, $cm->id, false);
+            }
+        }
+
+        $output .= $this->output->box($this->render(new folder_tree($folder, $cm)),
+                'generalbox foldertree');
+
+        if (has_capability('mod/folder:managefiles', $context)) {
+            $output .= $this->output->container(
+                    $this->output->single_button(new moodle_url('/mod/folder/edit.php',
+                    array('id' => $cm->id)), get_string('edit')),
+                    'mdl-align folder-edit-button');
+        }
+        return $output;
     }
 
     public function render_folder_tree(folder_tree $tree) {
-        global $PAGE;
+        static $treecounter = 0;
 
-        echo '<div id="folder_tree" class="filemanager">';
-        echo $this->htmllize_tree($tree, array('files' => array(), 'subdirs' => array($tree->dir)));
-        echo '</div>';
-        $this->page->requires->js_init_call('M.mod_folder.init_tree', array(true));
+        $content = '';
+        $id = 'folder_tree'. ($treecounter++);
+        $content .= '<div id="'.$id.'" class="filemanager">';
+        $content .= $this->htmllize_tree($tree, array('files' => array(), 'subdirs' => array($tree->dir)));
+        $content .= '</div>';
+        $this->page->requires->js_init_call('M.mod_folder.init_tree', array($id, true));
+        return $content;
     }
 
     /**
@@ -91,13 +126,11 @@ class folder_tree implements renderable {
     public $context;
     public $folder;
     public $cm;
-    public $course;
     public $dir;
 
-    public function __construct($folder, $cm, $course) {
+    public function __construct($folder, $cm) {
         $this->folder = $folder;
         $this->cm     = $cm;
-        $this->course = $course;
 
         $this->context = context_module::instance($cm->id);
         $fs = get_file_storage();
index 1e04cfd..1e168a3 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2012112900;       // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2013012100;       // The current module version (Date: YYYYMMDDXX)
 $module->requires  = 2012112900;    // Requires this Moodle version
 $module->component = 'mod_folder';     // Full name of the plugin (used for diagnostics)
 $module->cron      = 0;
index 4a8960a..19c16a7 100644 (file)
@@ -34,10 +34,10 @@ $f  = optional_param('f', 0, PARAM_INT);   // Folder instance id
 
 if ($f) {  // Two ways to specify the module
     $folder = $DB->get_record('folder', array('id'=>$f), '*', MUST_EXIST);
-    $cm = get_coursemodule_from_instance('folder', $folder->id, $folder->course, false, MUST_EXIST);
+    $cm = get_coursemodule_from_instance('folder', $folder->id, $folder->course, true, MUST_EXIST);
 
 } else {
-    $cm = get_coursemodule_from_id('folder', $id, 0, false, MUST_EXIST);
+    $cm = get_coursemodule_from_id('folder', $id, 0, true, MUST_EXIST);
     $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST);
 }
 
@@ -46,6 +46,9 @@ $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
 require_course_login($course, true, $cm);
 $context = context_module::instance($cm->id);
 require_capability('mod/folder:view', $context);
+if ($folder->display == FOLDER_DISPLAY_INLINE) {
+    redirect(course_get_url($folder->course, $cm->sectionnum));
+}
 
 add_to_log($course->id, 'folder', 'view', 'view.php?id='.$cm->id, $folder->id, $cm->id);
 
@@ -66,20 +69,6 @@ echo $output->header();
 
 echo $output->heading(format_string($folder->name), 2);
 
-if (trim(strip_tags($folder->intro))) {
-    echo $output->box_start('mod_introbox', 'pageintro');
-    echo format_module_intro('folder', $folder, $cm->id);
-    echo $output->box_end();
-}
-
-echo $output->box_start('generalbox foldertree');
-echo $output->folder_tree($folder, $cm, $course);
-echo $output->box_end();
-
-if (has_capability('mod/folder:managefiles', $context)) {
-    echo $output->container_start('mdl-align');
-    echo $output->single_button(new moodle_url('/mod/folder/edit.php', array('id'=>$id)), get_string('edit'));
-    echo $output->container_end();
-}
+echo $output->display_folder($folder);
 
 echo $output->footer();
index 149aa87..dc06db9 100644 (file)
@@ -212,6 +212,14 @@ function forum_update_instance($forum, $mform) {
 
     $DB->update_record('forum', $forum);
 
+    $modcontext = context_module::instance($forum->coursemodule);
+    if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
+        $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
+        foreach ($users as $user) {
+            forum_subscribe($user->id, $forum->id);
+        }
+    }
+
     forum_grade_item_update($forum);
 
     return true;
index 7ea5cd7..1bd5277 100644 (file)
@@ -111,6 +111,12 @@ if (!is_null($mode) and has_capability('mod/forum:managesubscriptions', $context
             redirect($returnto, get_string("everyoneisnowsubscribed", "forum"), 1);
             break;
         case FORUM_INITIALSUBSCRIBE : // 2
+            if ($forum->forcesubscribe <> FORUM_INITIALSUBSCRIBE) {
+                $users = forum_get_potential_subscribers($context, 0, 'u.id, u.email', '');
+                foreach ($users as $user) {
+                    forum_subscribe($user->id, $forum->id);
+                }
+            }
             forum_forcesubscribe($forum->id, FORUM_INITIALSUBSCRIBE);
             redirect($returnto, get_string("everyoneisnowsubscribed", "forum"), 1);
             break;
index 9929473..4b97c6b 100644 (file)
@@ -47,13 +47,14 @@ list($sort, $sortparams) = users_order_by_sql('u');
 $params = array_merge($params, $sortparams);
 // TODO: Improve this. Fetching all students always is crazy!
 if (!empty($cm->groupingid)) {
-    $params["groupid"] = $cm->groupingid;
+    $params["groupingid"] = $cm->groupingid;
     $sql = "SELECT DISTINCT $ufields
                 FROM {lesson_attempts} a
                     INNER JOIN {user} u ON u.id = a.userid
                     INNER JOIN {groups_members} gm ON gm.userid = u.id
-                    INNER JOIN {groupings_groups} gg ON gm.groupid = :groupid
-                WHERE a.lessonid = :lessonid
+                    INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid
+                WHERE a.lessonid = :lessonid AND
+                      gg.groupingid = :groupingid
                 ORDER BY $sort";
 } else {
     $sql = "SELECT DISTINCT $ufields
index a390e00..2a08a4b 100644 (file)
@@ -243,7 +243,7 @@ table#categoryquestions {width: 100%;overflow: hidden;table-layout: fixed;}
 #page-mod-quiz-edit div.question div.qnum {display:block;float:left;width:1.4em;padding-right:0.3em;padding-left:0;z-index:99;text-align:right;color:#333;}
 #page-mod-quiz-edit div.question div.questioncontainer{background-color:#ffc;}
 #page-mod-quiz-edit div.editq div.question div.content{width:87%;float:left;position:relative;-webkit-border-radius:0.6em;-webkit-border-radius-bottomleft:0;-webkit-border-radius-topleft:0;border-radius:0.6em;border-radius-bottomleft:0;border-radius-topleft:0;line-height:1.4em;padding:0.5em;}
-#page-mod-quiz-edit div.question div.content div.points{top:0.5em;border-left:0.4em solid #FFF;width:8.5em;padding:0.2em;line-height:1em;max-width:30%;position:absolute;right:50px;-webkit-border-radius:0.2em;-webkit-border-radius-bottomleft:0;-webkit-border-radius-topleft:0;border-radius:0.2em;border-radius-bottomleft:0;border-radius-topleft:0;z-index:900;display:block;margin:0;background-color:#ddf;}
+#page-mod-quiz-edit div.question div.content div.points{top:0.5em;border-left:0.4em solid #FFF;width:8.5em;padding:0.2em;line-height:1em;max-width:30%;position:absolute;right:60px;-webkit-border-radius:0.2em;-webkit-border-radius-bottomleft:0;-webkit-border-radius-topleft:0;border-radius:0.2em;border-radius-bottomleft:0;border-radius-topleft:0;z-index:900;display:block;margin:0;background-color:#ddf;}
 #page-mod-quiz-edit div.question div.content div.points input{width:2em;padding:0;}
 #page-mod-quiz-edit div.question div.content div.points input.pointssubmitbutton{width:auto;}
 #page-mod-quiz-edit div.question div.content div.qorder {line-height:1em;max-width:30%;position:absolute;right:50px;-webkit-border-radius:0.2em;-webkit-border-radius-bottomleft:0;-webkit-border-radius-topleft:0;border-radius:0.2em;border-radius-bottomleft:0;border-radius-topleft:0;z-index:900;display:block;margin:0;background-color:#ddf;}
@@ -256,7 +256,7 @@ table#categoryquestions {width: 100%;overflow: hidden;table-layout: fixed;}
 #page-mod-quiz-edit div.question div.content .questionpreview {display:block;float:left;margin-left:0.3em;padding-left:0.2em;padding-right:0.2em;}
 #page-mod-quiz-edit div.question div.content .questionpreview a{background-color:#eee;}
 #page-mod-quiz-edit div.question div.content div.quiz_randomquestion .questionpreview{display:inline;float:none;}
-#page-mod-quiz-edit div.question div.content div.questioncontrols{float:right;width:45px;position:absolute;right:0.3em;top:0;z-index:900;display:block;padding:0.2em;background-color:#F9F9F9;text-align:right;}
+#page-mod-quiz-edit div.question div.content div.questioncontrols{float:right;width:55px;position:absolute;right:0.3em;top:0;z-index:900;display:block;padding:0.2em;background-color:#F9F9F9;text-align:right;}
 #page-mod-quiz-edit div.question div.content div.questioncontrols img.upwithoutdown{padding-right:12px;display:inline;}
 #page-mod-quiz-edit div.question div.content .questiontext {font-weight:bold;}
 #page-mod-quiz-edit div.question div.content .questiontype{font-style:italic;}
@@ -406,7 +406,7 @@ bank window's title is prominent enough*/
 #page-mod-quiz-edit.dir-rtl div.question {clear: right;}
 #page-mod-quiz-edit.dir-rtl div.question div.qnum {float: right;}
 #page-mod-quiz-edit.dir-rtl div.editq div.question div.content {float: right;height: 40px;}
-#page-mod-quiz-edit.dir-rtl div.question div.content div.points {left: 50px;right:auto;}
+#page-mod-quiz-edit.dir-rtl div.question div.content div.points {left: 60px;right:auto;}
 #page-mod-quiz-edit.dir-rtl div.question div.content div.questioncontrols {float: left;left: 0.3em; right:auto;}
 #page-mod-quiz-edit.dir-rtl .editq div.question div.content .singlequestion .questioneditbutton .questionname,
 #page-mod-quiz-edit.dir-rtl .editq div.question div.content .singlequestion .questioneditbutton .questiontext {float: right; padding-right: 0.3em;}
index 43cd3fc..e7b797a 100644 (file)
@@ -58,6 +58,9 @@ class workshop_assessment_form extends moodleform {
         $this->workshop = $this->_customdata['workshop'];   // instance of the workshop api class
         $this->options  = $this->_customdata['options'];    // array with addiotional options
 
+        // Disable shortforms
+        $mform->setDisableShortforms();
+
         // add the strategy-specific fields
         $this->definition_inner($mform);
 
index 687daf4..11138df 100644 (file)
@@ -41,9 +41,7 @@ if (empty($CFG->enablenotes)) {
 if (data_submitted() && confirm_sesskey()) {
 //if data was submitted and is valid, then delete note
     $returnurl = $CFG->wwwroot . '/notes/index.php?course=' . $course->id . '&amp;user=' . $note->userid;
-    if (note_delete($noteid)) {
-        add_to_log($note->courseid, 'notes', 'delete', 'index.php?course='.$note->courseid.'&amp;user='.$note->userid . '#note-' . $note->id , 'delete note');
-    } else {
+    if (!note_delete($noteid)) {
         print_error('cannotdeletepost', 'notes', $returnurl);
     }
     redirect($returnurl);
index 05fe4f5..ddccbc3 100644 (file)
@@ -70,9 +70,7 @@ if ($noteform->is_cancelled()) {
 
 /// if data was submitted and validated, then save it to database
 if ($note = $noteform->get_data()){
-    if (note_save($note)) {
-        add_to_log($note->courseid, 'notes', 'update', 'index.php?course='.$note->courseid.'&amp;user='.$note->userid . '#note-' . $note->id, 'update note');
-    }
+    note_save($note);
     // redirect to notes list that contains this note
     redirect($CFG->wwwroot . '/notes/index.php?course=' . $note->courseid . '&amp;user=' . $note->userid);
 }
index 44b378f..8b230e6 100644 (file)
@@ -53,11 +53,7 @@ class core_notes_external extends external_api {
                             'publishstate' => new external_value(PARAM_ALPHA, '\'personal\', \'course\' or \'site\''),
                             'courseid' => new external_value(PARAM_INT, 'course id of the note (in Moodle a note can only be created into a course, even for site and personal notes)'),
                             'text' => new external_value(PARAM_RAW, 'the text of the message - text or HTML'),
-                            'format' => new external_value(PARAM_ALPHANUMEXT, // For backward compatibility it can not be PARAM_INT, so we don't use external_format_value.
-                                    'text format (' . FORMAT_HTML . ' = HTML, '
-                                    . FORMAT_MOODLE . ' = MOODLE, '
-                                    . FORMAT_PLAIN . ' = PLAIN or '
-                                    . FORMAT_MARKDOWN . ' = MARKDOWN)', VALUE_DEFAULT, FORMAT_HTML),
+                            'format' => new external_format_value('text', VALUE_DEFAULT),
                             'clientnoteid' => new external_value(PARAM_ALPHANUMEXT, 'your own client id for the note. If this id is provided, the fail message id will be returned to you', VALUE_OPTIONAL),
                         )
                     )
@@ -81,19 +77,19 @@ class core_notes_external extends external_api {
 
         $params = self::validate_parameters(self::create_notes_parameters(), array('notes' => $notes));
 
-        //check if note system is enabled
+        // Check if note system is enabled.
         if (!$CFG->enablenotes) {
             throw new moodle_exception('notesdisabled', 'notes');
         }
 
-        //retrieve all courses
+        // Retrieve all courses.
         $courseids = array();
         foreach($params['notes'] as $note) {
             $courseids[] = $note['courseid'];
         }
         $courses = $DB->get_records_list("course", "id", $courseids);
 
-        //retrieve all users of the notes
+        // Retrieve all users of the notes.
         $userids = array();
         foreach($params['notes'] as $note) {
             $userids[] = $note['userid'];
@@ -105,32 +101,32 @@ class core_notes_external extends external_api {
         foreach ($params['notes'] as $note) {
 
             $success = true;
-            $resultnote = array(); //the infos about the success of the operation
+            $resultnote = array(); // The infos about the success of the operation.
 
-            //check the course exists
+            // Check the course exists.
             if (empty($courses[$note['courseid']])) {
                 $success = false;
                 $errormessage = get_string('invalidcourseid', 'error');
             } else {
-                // Ensure the current user is allowed to run this function
+                // Ensure the current user is allowed to run this function.
                 $context = context_course::instance($note['courseid']);
                 self::validate_context($context);
                 require_capability('moodle/notes:manage', $context);
             }
 
-            //check the user exists
+            // Check the user exists.
             if (empty($users[$note['userid']])) {
                 $success = false;
                 $errormessage = get_string('invaliduserid', 'notes', $note['userid']);
             }
 
-            //build the resultnote
+            // Build the resultnote.
             if (isset($note['clientnoteid'])) {
                 $resultnote['clientnoteid'] = $note['clientnoteid'];
             }
 
             if ($success) {
-                //now we can create the note
+                // Now we can create the note.
                 $dbnote = new stdClass;
                 $dbnote->courseid = $note['courseid'];
                 $dbnote->userid = $note['userid'];
@@ -148,7 +144,7 @@ class core_notes_external extends external_api {
                 $dbnote->content = $note['text'];
                 $dbnote->format = $textformat;
 
-                //get the state ('personal', 'course', 'site')
+                // Get the state ('personal', 'course', 'site').
                 switch ($note['publishstate']) {
                     case 'personal':
                         $dbnote->publishstate = NOTES_STATE_DRAFT;
@@ -164,11 +160,8 @@ class core_notes_external extends external_api {
                         break;
                 }
 
-                //TODO MDL-31119 performance improvement - if possible create a bulk functions for saving multiple notes at once
-                if (note_save($dbnote)) { //note_save attribut an id in case of success
-                    add_to_log($dbnote->courseid, 'notes', 'add',
-                            'index.php?course='.$dbnote->courseid.'&amp;user='.$dbnote->userid
-                            . '#note-' . $dbnote->id , 'add note');
+                // TODO MDL-31119 performance improvement - if possible create a bulk functions for saving multiple notes at once
+                if (note_save($dbnote)) { // Note_save attribut an id in case of success.
                     $success = $dbnote->id;
                 }
 
@@ -198,13 +191,258 @@ class core_notes_external extends external_api {
             new external_single_structure(
                 array(
                     'clientnoteid' => new external_value(PARAM_ALPHANUMEXT, 'your own id for the note', VALUE_OPTIONAL),
-                    'noteid' => new external_value(PARAM_INT, 'test this to know if it success:  id of the created note when successed, -1 when failed'),
+                    'noteid' => new external_value(PARAM_INT, 'test this to know if it success: id of the created note when successed, -1 when failed'),
                     'errormessage' => new external_value(PARAM_TEXT, 'error message - if failed', VALUE_OPTIONAL)
                 )
             )
         );
     }
 
+    /**
+     * Returns description of delete_notes parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.5
+     */
+    public static function delete_notes_parameters() {
+        return new external_function_parameters(
+            array(
+                "notes"=> new external_multiple_structure(
+                    new external_value(PARAM_INT, 'ID of the note to be deleted'), 'Array of Note Ids to be deleted.'
+                )
+            )
+        );
+    }
+
+    /**
+     * Delete notes about users.
+     * Note: code should be matching the /notes/delete.php checks.
+     *
+     * @param array $notes An array of ids for the notes to delete.
+     * @return null
+     * @since Moodle 2.5
+     */
+    public static function delete_notes($notes = array()) {
+        global $CFG;
+        require_once($CFG->dirroot . "/notes/lib.php");
+
+        $params = self::validate_parameters(self::delete_notes_parameters(), $notes);
+
+        // Check if note system is enabled.
+        if (!$CFG->enablenotes) {
+            throw new moodle_exception('notesdisabled', 'notes');
+        }
+        $warnings = array();
+        foreach ($params['notes'] as $noteid) {
+            $note = note_load($noteid);
+            if (isset($note->id)) {
+                // Ensure the current user is allowed to run this function.
+                $context = context_course::instance($note->courseid);
+                self::validate_context($context);
+                require_capability('moodle/notes:manage', $context);
+                if (!note_delete($note)) {
+                    $warnings[] = array(array('item'=>'note', 'itemid'=>$noteid, 'warningcode'=>'savedfailed', 'message'=>'Note could not be modified'));
+                }
+            } else {
+                $warnings[] = array('item'=>'note', 'itemid'=>$noteid, 'warningcode'=>'badid', 'message'=>'Note does not exist');
+            }
+        }
+        return $warnings;
+    }
+
+    /**
+     * Returns description of delete_notes result value.
+     *
+     * @return external_description
+     * @since Moodle 2.5
+     */
+    public static function delete_notes_returns() {
+        return  new external_warnings('item is always \'note\'',
+                            'When errorcode is savedfailed the note could not be modified.' .
+                            'When errorcode is badparam, an incorrect parameter was provided.' .
+                            'When errorcode is badid, the note does not exist',
+                            'errorcode can be badparam (incorrect parameter), savedfailed (could not be modified), or badid (note does not exist)');
+
+    }
+
+    /**
+     * Returns description of get_notes parameters.
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.5
+     */
+    public static function get_notes_parameters() {
+        return new external_function_parameters(
+            array(
+                "notes"=> new external_multiple_structure(
+                    new external_value(PARAM_INT, 'ID of the note to be retrieved'), 'Array of Note Ids to be retrieved.'
+                )
+            )
+        );
+    }
+
+    /**
+     * Get notes about users.
+     *
+     * @param array $notes An array of ids for the notes to retrieve.
+     * @return null
+     * @since Moodle 2.5
+     */
+    public static function get_notes($notes) {
+        global $CFG;
+        require_once($CFG->dirroot . "/notes/lib.php");
+
+        $params = self::validate_parameters(self::get_notes_parameters(), $notes);
+        // Check if note system is enabled.
+        if (!$CFG->enablenotes) {
+            throw new moodle_exception('notesdisabled', 'notes');
+        }
+        $resultnotes = array();
+        foreach ($params['notes'] as $noteid) {
+            $resultnote = array();
+
+            $note = note_load($noteid);
+            if (isset($note->id)) {
+                // Ensure the current user is allowed to run this function.
+                $context = context_course::instance($note->courseid);
+                self::validate_context($context);
+                require_capability('moodle/notes:view', $context);
+                list($gotnote['text'], $gotnote['format']) = external_format_text($note->content, $note->format, $context->id, 'notes', '', '');
+                $gotnote['noteid'] = $note->id;
+                $gotnote['userid'] = $note->userid;
+                $gotnote['publishstate'] = $note->publishstate;
+                $gotnote['courseid'] = $note->courseid;
+                $resultnotes["notes"][] = $gotnote;
+            } else {
+                $resultnotes["warnings"][] = array('item'=>'note', 'itemid'=>$noteid, 'warningcode'=>'badid', 'message'=>'Note does not exist');
+            }
+        }
+        return $resultnotes;
+    }
+
+    /**
+     * Returns description of get_notes result value.
+     *
+     * @return external_description
+     * @since Moodle 2.5
+     */
+    public static function get_notes_returns() {
+        return new external_single_structure(
+            array(
+                'notes' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'noteid' => new external_value(PARAM_INT, 'id of the note', VALUE_OPTIONAL),
+                            'userid' => new external_value(PARAM_INT, 'id of the user the note is about', VALUE_OPTIONAL),
+                            'publishstate' => new external_value(PARAM_ALPHA, '\'personal\', \'course\' or \'site\'', VALUE_OPTIONAL),
+                            'courseid' => new external_value(PARAM_INT, 'course id of the note', VALUE_OPTIONAL),
+                            'text' => new external_value(PARAM_RAW, 'the text of the message - text or HTML', VALUE_OPTIONAL),
+                            'format' => new external_format_value('text', VALUE_OPTIONAL),
+                        ), 'note'
+                    )
+                 ),
+                 'warnings' => new external_warnings('item is always \'note\'',
+                        'When errorcode is savedfailed the note could not be modified.' .
+                        'When errorcode is badparam, an incorrect parameter was provided.' .
+                        'When errorcode is badid, the note does not exist',
+                        'errorcode can be badparam (incorrect parameter), savedfailed (could not be modified), or badid (note does not exist)')
+            )
+        );
+    }
+
+    /**
+     * Returns description of update_notes parameters.
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.5
+     */
+    public static function update_notes_parameters() {
+        return new external_function_parameters(
+            array(
+                'notes' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_INT, 'id of the note'),
+                            'publishstate' => new external_value(PARAM_ALPHA, '\'personal\', \'course\' or \'site\''),
+                            'text' => new external_value(PARAM_RAW, 'the text of the message - text or HTML'),
+                            'format' => new external_format_value('text', VALUE_DEFAULT),
+                        )
+                    ), "Array of Notes", VALUE_DEFAULT, array()
+                )
+            )
+        );
+    }
+
+    /**
+     * Update notes about users.
+     *
+     * @param array $notes An array of ids for the notes to update.
+     * @return array fail infos.
+     * @since Moodle 2.2
+     */
+    public static function update_notes($notes = array()) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/notes/lib.php");
+
+        $params = self::validate_parameters(self::update_notes_parameters(), array('notes' => $notes));
+
+        // Check if note system is enabled.
+        if (!$CFG->enablenotes) {
+            throw new moodle_exception('notesdisabled', 'notes');
+        }
+
+        $warnings = array();
+        foreach ($params['notes'] as $note) {
+            $notedetails = note_load($note['id']);
+            if (isset($notedetails->id)) {
+                // Ensure the current user is allowed to run this function.
+                $context = context_course::instance($notedetails->courseid);
+                self::validate_context($context);
+                require_capability('moodle/notes:manage', $context);
+
+                $dbnote = new stdClass;
+                $dbnote->id = $note['id'];
+                $dbnote->content = $note['text'];
+                $dbnote->format = external_validate_format($note['format']);
+                // Get the state ('personal', 'course', 'site').
+                switch ($note['publishstate']) {
+                    case 'personal':
+                        $dbnote->publishstate = NOTES_STATE_DRAFT;
+                        break;
+                    case 'course':
+                        $dbnote->publishstate = NOTES_STATE_PUBLIC;
+                        break;
+                    case 'site':
+                        $dbnote->publishstate = NOTES_STATE_SITE;
+                        $dbnote->courseid = SITEID;
+                        break;
+                    default:
+                        $warnings[] = array('item'=>'note', 'itemid'=>$note["id"], 'warningcode'=>'badparam', 'message'=>'Provided publishstate incorrect');
+                        break;
+                }
+                if (!note_save($dbnote)) {
+                    $warnings[] = array('item'=>'note', 'itemid'=>$note["id"], 'warningcode'=>'savedfailed', 'message'=>'Note could not be modified');
+                }
+            } else {
+                $warnings[] = array('item'=>'note', 'itemid'=>$note["id"], 'warningcode'=>'badid', 'message'=>'Note does not exist');
+            }
+        }
+        return $warnings;
+    }
+
+    /**
+     * Returns description of update_notes result value.
+     *
+     * @return external_description
+     * @since Moodle 2.5
+     */
+    public static function update_notes_returns() {
+        return new external_warnings('item is always \'note\'',
+                            'When errorcode is savedfailed the note could not be modified.' .
+                            'When errorcode is badparam, an incorrect parameter was provided.' .
+                            'When errorcode is badid, the note does not exist',
+                            'errorcode can be badparam (incorrect parameter), savedfailed (could not be modified), or badid (note does not exist)');
+    }
 }
 
 /**
index b204219..ce720e3 100644 (file)
@@ -100,10 +100,18 @@ function note_save(&$note) {
         // insert new note
         $note->created = $note->lastmodified;
         $id = $DB->insert_record('post', $note);
-        $note = $DB->get_record('post', array('id'=>$id));
+        $note = note_load($id);
+        $logurl = new moodle_url('index.php', array('course'=> $note->courseid, 'user'=>$note->userid));
+        $logurl->set_anchor('note-' . $id);
+
+        add_to_log($note->courseid, 'notes', 'add', $logurl, 'add note');
     } else {
         // update old note
         $DB->update_record('post', $note);
+        $note = note_load($note->id);
+        $logurl = new moodle_url('index.php', array('course'=> $note->courseid, 'user'=>$note->userid));
+        $logurl->set_anchor('note-' . $note->id);
+        add_to_log($note->courseid, 'notes', 'update', $logurl , 'update note');
     }
     unset($note->module);
     return true;
@@ -112,13 +120,19 @@ function note_save(&$note) {
 /**
  * Deletes a note object based on its id.
  *
- * @param int    $note_id id of the note to delete
+ * @param int|object    $note id of the note to delete, or a note object which is to be deleted.
  * @return boolean true if the object was deleted; false otherwise
  */
-function note_delete($noteid) {
+function note_delete($note) {
     global $DB;
-
-    return $DB->delete_records('post', array('id'=>$noteid, 'module'=>'notes'));
+    if (is_int($note)) {
+        $note = note_load($note);
+        debugging('Warning: providing note_delete with a note object would improve performance.',DEBUG_DEVELOPER);
+    }
+    $logurl = new moodle_url('index.php', array('course'=> $note->courseid, 'user'=>$note->userid));
+    $logurl->set_anchor('note-' . $note->id);
+    add_to_log($note->courseid, 'notes', 'delete', $logurl, 'delete note');
+    return $DB->delete_records('post', array('id'=>$note->id, 'module'=>'notes'));
 }
 
 /**
index d36724f..7e85630 100644 (file)
@@ -37,13 +37,13 @@ class core_notes_external_testcase extends externallib_advanced_testcase {
      */
     public function test_create_notes() {
 
-        global $DB, $USER, $DB;
+        global $DB, $USER;
 
         $this->resetAfterTest(true);
 
-        $course  = self::getDataGenerator()->create_course();
+        $course = self::getDataGenerator()->create_course();
 
-        // Set the required capabilities by the external function
+        // Set the required capabilities by the external function.
         $contextid = context_course::instance($course->id)->id;
         $roleid = $this->assignUserCapability('moodle/notes:manage', $contextid);
         $this->assignUserCapability('moodle/course:view', $contextid, $roleid);
@@ -58,7 +58,6 @@ class core_notes_external_testcase extends externallib_advanced_testcase {
         $notes = array($note1);
 
         $creatednotes = core_notes_external::create_notes($notes);
-
         // We need to execute the return values cleaning process to simulate the web service server.
         $creatednotes = external_api::clean_returnvalue(core_notes_external::create_notes_returns(), $creatednotes);
 
@@ -71,10 +70,174 @@ class core_notes_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals($thenote->content, $note1['text']);
         $this->assertEquals($creatednotes[0]['clientnoteid'], $note1['clientnoteid']);
 
-        // Call without required capability
+        // Call without required capability.
         $this->unassignUserCapability('moodle/notes:manage', $contextid, $roleid);
         $this->setExpectedException('required_capability_exception');
         $creatednotes = core_notes_external::create_notes($notes);
+    }
+
+    public function test_delete_notes() {
+
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        // Set the required capabilities by the external function.
+        $contextid = context_course::instance($course->id)->id;
+        $roleid = $this->assignUserCapability('moodle/notes:manage', $contextid);
+        $this->assignUserCapability('moodle/course:view', $contextid, $roleid);
+
+        // Create test note data.
+        $cnote = array();
+        $cnote['userid'] = $USER->id;
+        $cnote['publishstate'] = 'personal';
+        $cnote['courseid'] = $course->id;
+        $cnote['text'] = 'the text';
+        $cnote['clientnoteid'] = 4;
+        $cnotes = array($cnote);
+        $creatednotes = core_notes_external::create_notes($cnotes);
+        $creatednotes = external_api::clean_returnvalue(core_notes_external::create_notes_returns(), $creatednotes);
+
+        $dnotes1 = array("notes"=>array($creatednotes[0]['noteid']));
+        $deletednotes1 = core_notes_external::delete_notes($dnotes1);
+        $deletednotes1 = external_api::clean_returnvalue(core_notes_external::delete_notes_returns(), $deletednotes1);
+
+        // Confirm that base note data was deleted correctly.
+        $notdeletedcount = $DB->count_records_select('post', 'id = ' . $creatednotes[0]['noteid']);
+        $this->assertEquals(0, $notdeletedcount);
+
+        $dnotes2 = array("notes"=>array(33)); // This note does not exist.
+        $deletednotes2 = core_notes_external::delete_notes($dnotes2);
+        $deletednotes2 = external_api::clean_returnvalue(core_notes_external::delete_notes_returns(), $deletednotes2);
+
+        $this->assertEquals("note", $deletednotes2[0]["item"]);
+        $this->assertEquals(33, $deletednotes2[0]["itemid"]);
+        $this->assertEquals("badid", $deletednotes2[0]["warningcode"]);
+        $this->assertEquals("Note does not exist", $deletednotes2[0]["message"]);
+
+        // Call without required capability.
+        $creatednotes = core_notes_external::create_notes($cnotes);
+        $dnotes3 = array("notes"=>array($creatednotes[0]['noteid']));
+
+        $this->unassignUserCapability('moodle/notes:manage', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        $deletednotes = core_notes_external::delete_notes($dnotes3);
+    }
+
+    public function test_get_notes() {
+
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        // Set the required capabilities by the external function.
+        $contextid = context_course::instance($course->id)->id;
+        $roleid = $this->assignUserCapability('moodle/notes:manage', $contextid);
+        $this->assignUserCapability('moodle/notes:view', $contextid, $roleid);
+        $this->assignUserCapability('moodle/course:view', $contextid, $roleid);
 
+        // Create test note data.
+        $cnote = array();
+        $cnote['userid'] = $USER->id;
+        $cnote['publishstate'] = 'personal';
+        $cnote['courseid'] = $course->id;
+        $cnote['text'] = 'the text';
+        $cnotes = array($cnote);
+
+        $creatednotes1 = core_notes_external::create_notes($cnotes);
+        $creatednotes2 = core_notes_external::create_notes($cnotes);
+        $creatednotes3 = core_notes_external::create_notes($cnotes);
+
+        $creatednotes1 = external_api::clean_returnvalue(core_notes_external::create_notes_returns(), $creatednotes1);
+        $creatednotes2 = external_api::clean_returnvalue(core_notes_external::create_notes_returns(), $creatednotes2);
+        $creatednotes3 = external_api::clean_returnvalue(core_notes_external::create_notes_returns(), $creatednotes3);
+
+        // Note 33 does not exist.
+        $gnotes = array("notes"=>array($creatednotes1[0]['noteid'], $creatednotes2[0]['noteid'], $creatednotes3[0]['noteid'], 33));
+        $getnotes = core_notes_external::get_notes($gnotes);
+        $getnotes = external_api::clean_returnvalue(core_notes_external::get_notes_returns(), $getnotes);
+
+        $this->unassignUserCapability('moodle/notes:manage', $contextid, $roleid);
+        // Confirm that base note data was retrieved correctly.
+        $this->assertEquals($cnote['userid'], $getnotes["notes"][0]["userid"]);
+        $this->assertEquals($cnote['text'], $getnotes["notes"][0]["text"]);
+        $this->assertEquals($cnote['userid'], $getnotes["notes"][1]["userid"]);
+        $this->assertEquals($cnote['text'], $getnotes["notes"][1]["text"]);
+        $this->assertEquals($cnote['userid'], $getnotes["notes"][2]["userid"]);
+        $this->assertEquals($cnote['text'], $getnotes["notes"][2]["text"]);
+        $this->assertEquals("note", $getnotes["warnings"][0]["item"]);
+        $this->assertEquals(33, $getnotes["warnings"][0]["itemid"]);
+        $this->assertEquals("badid", $getnotes["warnings"][0]["warningcode"]);
+        $this->assertEquals("Note does not exist", $getnotes["warnings"][0]["message"]);
+
+        // Call without required capability.
+        $this->unassignUserCapability('moodle/notes:view', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        $creatednotes = core_notes_external::get_notes($gnotes);
+    }
+
+    public function test_update_notes() {
+
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        // Set the required capabilities by the external function.
+        $contextid = context_course::instance($course->id)->id;
+        $roleid = $this->assignUserCapability('moodle/notes:manage', $contextid);
+        $this->assignUserCapability('moodle/course:view', $contextid, $roleid);
+
+        // Create test note data.
+        $note1 = array();
+        $note1['userid'] = $USER->id;
+        $note1['publishstate'] = 'personal';
+        $note1['courseid'] = $course->id;
+        $note1['text'] = 'the text';
+        $note2['userid'] = $USER->id;
+        $note2['publishstate'] = 'course';
+        $note2['courseid'] = $course->id;
+        $note2['text'] = 'the text';
+        $note3['userid'] = $USER->id;
+        $note3['publishstate'] = 'site';
+        $note3['courseid'] = $course->id;
+        $note3['text'] = 'the text';
+        $notes1 = array($note1, $note2, $note3);
+
+        $creatednotes = core_notes_external::create_notes($notes1);
+        $creatednotes = external_api::clean_returnvalue(core_notes_external::create_notes_returns(), $creatednotes);
+
+        $note2 = array();
+        $note2["id"] = $creatednotes[0]['noteid'];
+        $note2['publishstate'] = 'personal';
+        $note2['text'] = 'the new text';
+        $note2['format'] = FORMAT_HTML;
+        $notes2 = array($note2);
+
+        $updatednotes = core_notes_external::update_notes($notes2);
+
+        $updatednotes = external_api::clean_returnvalue(core_notes_external::update_notes_returns(), $updatednotes);
+        $thenote = $DB->get_record('post', array('id' => $creatednotes[0]['noteid']));
+
+        // Confirm that base note data was updated correctly.
+        $this->assertEquals($thenote->publishstate, NOTES_STATE_DRAFT);
+        $this->assertEquals($note2['text'], $thenote->content);
+
+        // Call without required capability.
+        $creatednotes = core_notes_external::create_notes($notes1);
+        $this->unassignUserCapability('moodle/notes:manage', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        $note2 = array();
+        $note2["id"] = $creatednotes[0]['noteid'];
+        $note2['publishstate'] = 'personal';
+        $note2['text'] = 'the new text';
+        $note2['format'] = FORMAT_HTML;
+        $notes2 = array($note2);
+        $updatednotes = core_notes_external::update_notes($notes2);
     }
 }
index 959f34e..cee6d85 100644 (file)
@@ -216,4 +216,13 @@ class repository_coursefiles extends repository {
         // this should be realtime
         return 0;
     }
+
+    /**
+     * Is this repository accessing private data?
+     *
+     * @return bool
+     */
+    public function contains_private_data() {
+        return false;
+    }
 }
index d29378e..b54806b 100644 (file)
@@ -437,4 +437,13 @@ class repository_equella extends repository {
             return get_string('lostsource', 'repository', '');
         }
     }
+
+    /**
+     * Is this repository accessing private data?
+     *
+     * @return bool
+     */
+    public function contains_private_data() {
+        return false;
+    }
 }
index 3cb0374..e8d4701 100644 (file)
@@ -175,15 +175,24 @@ YUI.add('moodle-core_filepicker', function(Y) {
         /** return the name of the file (different attributes in FileManager and FilePicker) */
         var file_get_filename = function(node) {
             return node.title ? node.title : node.fullname;
-        }
+        };
         /** return display name of the file (different attributes in FileManager and FilePicker) */
         var file_get_displayname = function(node) {
-            return node.shorttitle ? node.shorttitle : file_get_filename(node);
-        }
+            var displayname = node.shorttitle ? node.shorttitle : file_get_filename(node);
+            return Y.Escape.html(displayname);
+        };
         /** return file description (different attributes in FileManager and FilePicker) */
         var file_get_description = function(node) {
-            return node.description ? node.description : (node.thumbnail_title ? node.thumbnail_title : file_get_filename(node));
-        }
+            var description = '';
+            if (node.description) {
+                description = node.description;
+            } else if (node.thumbnail_title) {
+                description = node.thumbnail_title;
+            } else {
+                description = file_get_filename(node);
+            }
+            return Y.Escape.html(description);
+        };
         /** help funciton for tree view */
         var build_tree = function(node, level) {
             // prepare file name with icon
@@ -401,7 +410,7 @@ YUI.add('moodle-core_filepicker', function(Y) {
                 imgdiv.setStyleAdv('width', width).setStyleAdv('height', height);
                 var img = Y.Node.create('<img/>').setAttrs({
                         title: file_get_description(node),
-                        alt: node.thumbnail_alt ? node.thumbnail_alt : file_get_filename(node)}).
+                        alt: Y.Escape.html(node.thumbnail_alt ? node.thumbnail_alt : file_get_filename(node))}).
                     setStyle('maxWidth', ''+width+'px').
                     setStyle('maxHeight', ''+height+'px');
                 img.setImgSrc(src, node.realthumbnail, lazyloading);
@@ -738,7 +747,7 @@ M.core_filepicker.init = function(Y, options) {
             this.fpnode.one('.fp-content').setContent(M.core_filepicker.templates.error);
             this.fpnode.one('.fp-content .fp-error').
                 addClass(errorcode).
-                setContent(errortext);
+                setContent(Y.Escape.html(errortext));
         },
         /** displays message in a popup */
         print_msg: function(msg, type) {
@@ -768,7 +777,7 @@ M.core_filepicker.init = function(Y, options) {
 
             this.msg_dlg.set('headerContent', header);
             this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
-            this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
+            this.msg_dlg_node.one('.fp-msg-text').setContent(Y.Escape.html(msg));
             this.msg_dlg.show();
         },
         view_files: function(appenditems) {
@@ -1106,7 +1115,7 @@ M.core_filepicker.init = function(Y, options) {
                 if (selectnode.one('.fp-'+attrs[i])) {
                     var value = (args[attrs[i]+'_f']) ? args[attrs[i]+'_f'] : (args[attrs[i]] ? args[attrs[i]] : '');
                     selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
-                        .one('.fp-value').setContent(value);
+                        .one('.fp-value').setContent(Y.Escape.html(value));
                 }
             }
         },
@@ -1354,7 +1363,7 @@ M.core_filepicker.init = function(Y, options) {
                             this.hide_header();
                             this.list({'repo_id':repository_id});
                         }, this /*handler running scope*/, repository.id/*second argument of handler*/);
-                    node.one('.fp-repo-name').setContent(repository.name);
+                    node.one('.fp-repo-name').setContent(Y.Escape.html(repository.name));
                     node.one('.fp-repo-icon').set('src', repository.icon);
                     if (i==0) {
                         node.addClass('first');
@@ -1609,7 +1618,7 @@ M.core_filepicker.init = function(Y, options) {
                 var option = Y.Node.create('<option/>').
                     set('selected', (this.options.defaultlicense==licenses[i].shortname)).
                     set('value', licenses[i].shortname).
-                    setContent(licenses[i].fullname);
+                    setContent(Y.Escape.html(licenses[i].fullname));
                 node.appendChild(option)
             }
         },
@@ -1869,7 +1878,7 @@ M.core_filepicker.init = function(Y, options) {
                     } else {
                         el.addClass('odd');
                     }
-                    el.all('.fp-path-folder-name').setContent(p[i].name);
+                    el.all('.fp-path-folder-name').setContent(Y.Escape.html(p[i].name));
                     el.on('click',
                             function(e, path) {
                                 e.preventDefault();
index 781f77b..bd19ef7 100644 (file)
@@ -142,12 +142,12 @@ case 'search':
             if (isset($item['thumbnail_width'])) {
                 $style .= 'max-width:'.$item['thumbnail_width'].'px;';
             }
-            echo html_writer::empty_tag('img', array('src' => $item['thumbnail'], 'style' => $style));
+            echo html_writer::empty_tag('img', array('src' => $item['thumbnail'], 'alt' => '', 'style' => $style));
             echo '</td><td>';
             if (!empty($item['url'])) {
-                echo html_writer::link($item['url'], $item['title'], array('target'=>'_blank'));
+                echo html_writer::link($item['url'], s($item['title']), array('target'=>'_blank'));
             } else {
-                echo $item['title'];
+                echo s($item['title']);
             }
             echo '</td>';
             echo '<td>';
@@ -204,7 +204,7 @@ case 'sign':
                         'draftpath'=>$draftpath,
                         'savepath'=>$savepath
                         ));
-                    echo '<strong>' . html_writer::link($pathurl, $p['name']) . '</strong>';
+                    echo '<strong>' . html_writer::link($pathurl, s($p['name'])) . '</strong>';
                     echo '<span> / </span>';
                 }
             }
@@ -241,9 +241,9 @@ case 'sign':
                 echo html_writer::empty_tag('img', array('src' => $item['thumbnail'], 'style' => $style));
                 echo '</td><td>';
                 if (!empty($item['url'])) {
-                    echo html_writer::link($item['url'], $item['title'], array('target'=>'_blank'));
+                    echo html_writer::link($item['url'], s($item['title']), array('target'=>'_blank'));
                 } else {
-                    echo $item['title'];
+                    echo s($item['title']);
                 }
                 echo '</td>';
                 echo '<td>';
@@ -393,8 +393,8 @@ case 'plugins':
         $aurl->params(array('savepath'=>$savepath, 'action' => 'list', 'repo_id' => $info->id, 'draftpath'=>$draftpath));
 
         echo '<li>';
-        echo '<img src="'.$info->icon.'" alt="'.$info->name.'" width="16" height="16" /> ';
-        echo html_writer::link($aurl, $info->name);
+        echo html_writer::empty_tag('img', array('src'=>$info->icon, 'alt'=>$info->name, 'class'=>'icon icon-pre'));
+        echo html_writer::link($aurl, s($info->name));
         echo '</li>';
     }
     echo '</ul>';
index 432e79d..c856371 100644 (file)
@@ -333,4 +333,13 @@ class repository_filesystem extends repository {
             send_file_not_found();
         }
     }
+
+    /**
+     * Is this repository accessing private data?
+     *
+     * @return bool
+     */
+    public function contains_private_data() {
+        return false;
+    }
 }
index 6b11cfc..04d1f07 100644 (file)
@@ -550,4 +550,13 @@ class repository_flickr_public extends repository {
     public function get_file_source_info($photoid) {
         return $this->build_photo_url($photoid);
     }
+
+    /**
+     * Is this repository accessing private data?
+     *
+     * @return bool
+     */
+    public function contains_private_data() {
+        return false;
+    }
 }
index 714e1b9..0a2bf47 100644 (file)
@@ -487,6 +487,9 @@ abstract class repository {
     public $returntypes;
     /** @var stdClass repository instance database record */
     public $instance;
+    /** @var string Type of repository (webdav, google_docs, dropbox, ...). Read from $this->get_typename(). */
+    protected $typename;
+
     /**
      * Constructor
      *
@@ -558,6 +561,24 @@ abstract class repository {
         }
     }
 
+    /**
+     * Returns the type name of the repository.
+     *
+     * @return string type name of the repository.
+     * @since  2.5
+     */
+    public function get_typename() {
+        if (empty($this->typename)) {
+            $matches = array();
+            if (!preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
+                throw new coding_exception('The class name of a repository should be repository_<typeofrepository>, '.
+                        'e.g. repository_dropbox');
+            }
+            $this->typename = $matches[1];
+        }
+        return $this->typename;
+    }
+
     /**
      * Get a repository type object by a given type name.
      *
@@ -620,39 +641,59 @@ abstract class repository {
     }
 
     /**
-     * Checks if user has a capability to view the current repository in current context
+     * Checks if user has a capability to view the current repository.
      *
-     * @return bool
+     * @return bool true when the user can, otherwise throws an exception.
+     * @throws repository_exception when the user does not meet the requirements.
      */
     public final function check_capability() {
-        $capability = false;
-        if (preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
-            $type = $matches[1];
-            $capability = has_capability('repository/'.$type.':view', $this->context);
+        global $USER;
+
+        // Ensure that the user can view the repository in the current context.
+        $can = has_capability('repository/'.$this->get_typename().':view', $this->context);
+
+        // Context in which the repository has been created.
+        $repocontext = context::instance_by_id($this->instance->contextid);
+
+        // Prevent access to private repositories when logged in as.
+        if ($can && session_is_loggedinas()) {
+            if ($this->contains_private_data() || $repocontext->contextlevel == CONTEXT_USER) {
+                $can = false;
+            }
         }
-        if (!$capability) {
-            throw new repository_exception('nopermissiontoaccess', 'repository');
+
+        // Ensure that the user can view the repository in the context of the repository.
+        // We need to perform the check when already disallowed.
+        if ($can) {
+            if ($repocontext->contextlevel == CONTEXT_USER && $repocontext->instanceid != $USER->id) {
+                // Prevent URL hijack to access someone else's repository.
+                $can = false;
+            } else {
+                $can = has_capability('repository/'.$this->get_typename().':view', $repocontext);
+            }
         }
+
+        if ($can) {
+            return true;
+        }
+
+        throw new repository_exception('nopermissiontoaccess', 'repository');
     }
 
     /**
-     * Check if file already exists in draft area
+     * Check if file already exists in draft area.
      *
      * @static
-     * @param int $itemid
-     * @param string $filepath
-     * @param string $filename
+     * @param int $itemid of the draft area.
+     * @param string $filepath path to the file.
+     * @param string $filename file name.
      * @return bool
      */
     public static function draftfile_exists($itemid, $filepath, $filename) {
         global $USER;
         $fs = get_file_storage();
         $usercontext = context_user::instance($USER->id);
-        if ($fs->get_file($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename)) {
-            return true;
-        } else {
-            return false;
-        }
+        return $fs->file_exists($usercontext->id, 'user', 'draft', $itemid, $filepath, $filename);
     }
 
     /**
@@ -769,31 +810,34 @@ abstract class repository {
     }
 
     /**
-     * Get unused filename by appending suffix
+     * Get an unused filename from the current draft area.
+     *
+     * Will check if the file ends with ([0-9]) and increase the number.
      *
      * @static
-     * @param int $itemid
-     * @param string $filepath
-     * @param string $filename
-     * @return string
+     * @param int $itemid draft item ID.
+     * @param string $filepath path to the file.
+     * @param string $filename name of the file.
+     * @return string an unused file name.
      */
     public static function get_unused_filename($itemid, $filepath, $filename) {
         global $USER;
+        $contextid = context_user::instance($USER->id)->id;
         $fs = get_file_storage();
-        while (repository::draftfile_exists($itemid, $filepath, $filename)) {
-            $filename = repository::append_suffix($filename);
-        }
-        return $filename;
+        return $fs->get_unused_filename($contextid, 'user', 'draft', $itemid, $filepath, $filename);
     }
 
     /**
-     * Append a suffix to filename
+     * Append a suffix to filename.
      *
      * @static
      * @param string $filename
      * @return string
+     * @deprecated since 2.5
      */
     public static function append_suffix($filename) {
+        debugging('The function repository::append_suffix() has been deprecated. Use repository::get_unused_filename() instead.',
+            DEBUG_DEVELOPER);
         $pathinfo = pathinfo($filename);
         if (empty($pathinfo['extension'])) {
             return $filename . RENAME_SUFFIX;
@@ -1768,13 +1812,36 @@ abstract class repository {
      */
     public function get_name() {
         global $DB;
-        if ( $name = $this->instance->name ) {
+        if ($name = $this->instance->name) {
             return $name;
         } else {
-            return get_string('pluginname', 'repository_' . $this->options['type']);
+            return get_string('pluginname', 'repository_' . $this->get_typename());
         }
     }
 
+    /**
+     * Is this repository accessing private data?
+     *
+     * This function should return true for the repositories which access external private
+     * data from a user. This is the case for repositories such as Dropbox, Google Docs or Box.net
+     * which authenticate the user and then store the auth token.
+     *
+     * Of course, many repositories store 'private data', but we only want to set
+     * contains_private_data()&nb