Merge branch 'MDL-33621_m23' of git://github.com/rwijaya/moodle into MOODLE_23_STABLE
authorDan Poltawski <dan@moodle.com>
Mon, 5 Nov 2012 03:12:41 +0000 (11:12 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 5 Nov 2012 03:12:41 +0000 (11:12 +0800)
148 files changed:
admin/cli/install.php
admin/renderer.php
admin/tool/customlang/locallib.php
admin/tool/customlang/styles.css
admin/tool/xmldb/lang/en/tool_xmldb.php
admin/webservice/service_users.php
auth/mnet/auth.php
auth/shibboleth/index.php
auth/shibboleth/lang/en/auth_shibboleth.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/tests/cronhelper_test.php
blocks/community/forms.php
blocks/dock.js
blocks/html/lib.php
blocks/online_users/block_online_users.php
blocks/search_forums/block_search_forums.php
cohort/lib.php
comment/locallib.php
course/dndupload.js
course/dnduploadlib.php
course/externallib.php
course/lib.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/view.php
course/yui/dragdrop/dragdrop.js
course/yui/modchooser/modchooser.js
course/yui/toolboxes/toolboxes.js
enrol/ajax.php
enrol/externallib.php
enrol/manual/ajax.php
enrol/manual/externallib.php
enrol/manual/manage.php
enrol/yui/notification/assets/skins/sam/notification.css
enrol/yui/notification/notification.js
grade/lib.php
grade/report/grader/lib.php
install/lang/bg/install.php
install/lang/en_fix/langconfig.php [new file with mode: 0644]
install/lang/en_kids/langconfig.php [new file with mode: 0644]
install/lang/fi/install.php
install/lang/he/error.php
install/lang/he/install.php
install/lang/it/langconfig.php
install/lang/ja_kids/langconfig.php [new file with mode: 0644]
install/lang/ms/admin.php [new file with mode: 0644]
install/lang/ms/install.php [new file with mode: 0644]
install/lang/ms/moodle.php [new file with mode: 0644]
install/lang/ro/install.php
install/lang/sr_cr/install.php
install/lang/zh_tw/admin.php
install/lang/zh_tw/error.php
install/lang/zh_tw/install.php
lang/en/admin.php
lang/en/form.php
lang/en/mimetypes.php
lang/en/role.php
lib/adminlib.php
lib/bennu/bennu.class.php
lib/bennu/iCalendar_parameters.php
lib/blocklib.php
lib/completionlib.php
lib/db/access.php
lib/db/upgrade.php
lib/enrollib.php
lib/filebrowser/file_browser.php
lib/filebrowser/file_info.php
lib/filebrowser/file_info_context_course.php
lib/filebrowser/file_info_context_coursecat.php
lib/filebrowser/file_info_context_module.php
lib/filebrowser/file_info_stored.php
lib/filelib.php
lib/form/yui/dateselector/dateselector.js
lib/formslib.php
lib/grade/grade_category.php
lib/grade/tests/grade_category_test.php
lib/javascript-static.js
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/phpunit/classes/data_generator.php
lib/phpunit/classes/util.php
lib/phpunit/tests/generator_test.php
lib/pluginlib.php
lib/tablelib.php
lib/tests/moodlelib_test.php
lib/upgradelib.php
mnet/service/enrol/course.php
mod/assign/gradeform.php
mod/assign/gradingoptionsform.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/upgradelib.php
mod/assignment/type/upload/assignment.class.php
mod/book/db/upgrade.php
mod/book/db/upgradelib.php [new file with mode: 0644]
mod/book/lang/en/book.php
mod/book/locallib.php
mod/book/tool/importhtml/index.php
mod/book/version.php
mod/book/view.php
mod/data/field/date/field.class.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/locallib.php
mod/data/tests/fixtures/test_data_records.csv
mod/data/tests/search_test.php
mod/data/view.php
mod/forum/locallib.php
mod/forum/styles.css
mod/glossary/locallib.php
mod/imscp/locallib.php
mod/lesson/importppt.php [deleted file]
mod/lesson/importpptlib.php [deleted file]
mod/lesson/renderer.php
mod/lesson/version.php
mod/quiz/styles.css
mod/scorm/lib.php
mod/upgrade.txt
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
mod/wiki/locallib.php
mod/wiki/pagelib.php
mod/workshop/fileinfolib.php
pix/f/epub.png [new file with mode: 0644]
question/type/calculated/styles.css
question/type/multianswer/module.js
question/type/multianswer/renderer.php
question/type/multichoice/edit_multichoice_form.php
question/type/numerical/styles.css
repository/filepicker.js
repository/lib.php
repository/local/lib.php
repository/upload/lang/en/repository_upload.php
theme/base/style/core.css
theme/canvas/layout/frontpage.php
theme/canvas/layout/general.php
theme/canvas/layout/report.php
theme/formal_white/lang/en/theme_formal_white.php
theme/magazine/style/core.css
theme/serenity/style/core.css
user/edit.php
user/filters/text.php
user/selector/module.js
user/selector/search.php
version.php
webservice/lib.php

index 79ff0b7..38aa06e 100644 (file)
@@ -240,18 +240,8 @@ echo get_string('cliinstallheader', 'install', $CFG->target_release)."\n";
 if ($interactive) {
     cli_separator();
     $languages = get_string_manager()->get_list_of_translations();
-    // format the langs nicely - 3 per line
-    $c = 0;
-    $langlist = '';
-    foreach ($languages as $key=>$lang) {
-        $c++;
-        $length = iconv_strlen($lang, 'UTF-8');
-        $padded = $lang.str_repeat(' ', 38-$length);
-        $langlist .= $padded;
-        if ($c % 3 == 0) {
-            $langlist .= "\n";
-        }
-    }
+    // Do not put the langs into columns because it is not compatible with RTL.
+    $langlist = implode("\n", $languages);
     $default = $CFG->lang;
     cli_heading(get_string('availablelangs', 'install'));
     echo $langlist."\n";
index 3a3c2bd..56c9056 100644 (file)
@@ -470,7 +470,7 @@ class core_admin_renderer extends plugin_renderer_base {
         $copyrighttext = '<a href="http://moodle.org/">Moodle</a> '.
                          '<a href="http://docs.moodle.org/dev/Releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
                          'Copyright &copy; 1999 onwards, Martin Dougiamas<br />'.
-                         'and <a href="http://docs.moodle.org/dev/Credits">many other contributors</a>.<br />'.
+                         'and <a href="http://moodle.org/dev">many other contributors</a>.<br />'.
                          '<a href="http://docs.moodle.org/dev/License">GNU Public License</a>';
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
index 47bbdfa..878eb4c 100644 (file)
@@ -488,7 +488,7 @@ class tool_customlang_translator implements renderable {
         list($insql, $inparams) = $DB->get_in_or_equal($filter->component, SQL_PARAMS_NAMED);
 
         $csql = "SELECT COUNT(*)";
-        $fsql = "SELECT s.id, s.*, c.name AS component";
+        $fsql = "SELECT s.*, c.name AS component";
         $sql  = "  FROM {tool_customlang_components} c
                    JOIN {tool_customlang} s ON s.componentid = c.id
                   WHERE s.lang = :lang
index c743bcf..963b289 100644 (file)
@@ -67,3 +67,7 @@
 #page-admin-tool-customlang-index .continuebutton {
     margin-top: 1em;
 }
+
+.path-admin-tool-customlang #translator .standard.master.cell.c2 {
+    word-break: break-all;
+}
index 7b384b2..c1475be 100644 (file)
@@ -111,10 +111,13 @@ $string['checkindexes'] = 'Check indexes';
 $string['check_indexes'] = 'Look for missing DB indexes';
 $string['checkoraclesemantics'] = 'Check semantics';
 $string['check_oracle_semantics'] = 'Look for incorrect length semantics';
+$string['duplicateindexname'] = 'Duplicate index name';
 $string['incorrectfieldname'] = 'Incorrect name';
 $string['index'] = 'Index';
 $string['indexes'] = 'Indexes';
+$string['indexnameempty'] = 'Index name is empty';
 $string['integerincorrectlength'] = 'Incorrect length for integer field';
+$string['incorrectindexname'] = 'Incorrect index name';
 $string['incorrectkeyname'] = 'Incorrect key name';
 $string['incorrecttablename'] = 'Incorrect table name';
 $string['key'] = 'Key';
index 12db2b0..ad67c66 100644 (file)
@@ -97,7 +97,7 @@ $usersmissingcaps = $webservicemanager->get_missing_capabilities_by_users($allow
 
 //add the missing capabilities to the allowed users object to be displayed by renderer
 foreach ($allowedusers as &$alloweduser) {
-    if (!is_siteadmin($alloweduser->id) and key_exists($alloweduser->id, $usersmissingcaps)) {
+    if (!is_siteadmin($alloweduser->id) and array_key_exists($alloweduser->id, $usersmissingcaps)) {
         $alloweduser->missingcapabilities = implode(', ', $usersmissingcaps[$alloweduser->id]);
     }
 }
index 0a136e8..14a42ac 100644 (file)
@@ -378,23 +378,28 @@ class auth_plugin_mnet extends auth_plugin_base {
                 $extra = $DB->get_records_sql($sql);
 
                 $keys = array_keys($courses);
-                $defaultrole = reset(get_archetype_roles('student'));
-                //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!!
-                foreach ($keys AS $id) {
-                    if ($courses[$id]->visible == 0) {
-                        unset($courses[$id]);
-                        continue;
+                $studentroles = get_archetype_roles('student');
+                if (!empty($studentroles)) {
+                    $defaultrole = reset($studentroles);
+                    //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!!
+                    foreach ($keys AS $id) {
+                        if ($courses[$id]->visible == 0) {
+                            unset($courses[$id]);
+                            continue;
+                        }
+                        $courses[$id]->cat_id          = $courses[$id]->category;
+                        $courses[$id]->defaultroleid   = $defaultrole->id;
+                        unset($courses[$id]->category);
+                        unset($courses[$id]->visible);
+
+                        $courses[$id]->cat_name        = $extra[$id]->cat_name;
+                        $courses[$id]->cat_description = $extra[$id]->cat_description;
+                        $courses[$id]->defaultrolename = $defaultrole->name;
+                        // coerce to array
+                        $courses[$id] = (array)$courses[$id];
                     }
-                    $courses[$id]->cat_id          = $courses[$id]->category;
-                    $courses[$id]->defaultroleid   = $defaultrole->id;
-                    unset($courses[$id]->category);
-                    unset($courses[$id]->visible);
-
-                    $courses[$id]->cat_name        = $extra[$id]->cat_name;
-                    $courses[$id]->cat_description = $extra[$id]->cat_description;
-                    $courses[$id]->defaultrolename = $defaultrole->name;
-                    // coerce to array
-                    $courses[$id] = (array)$courses[$id];
+                } else {
+                    throw new moodle_exception('unknownrole', 'error', '', 'student');
                 }
             } else {
                 // if the array is empty, send it anyway
index a1e4a1c..49b510f 100644 (file)
@@ -38,9 +38,9 @@
 
     /// Check if the user has actually submitted login data to us
 
-        if ($shibbolethauth->user_login($frm->username, $frm->password)) {
+        if ($shibbolethauth->user_login($frm->username, $frm->password)
+                && $user = authenticate_user_login($frm->username, $frm->password)) {
 
-            $user = authenticate_user_login($frm->username, $frm->password);
             enrol_check_plugins($user);
             session_set_user($user);
 
@@ -84,7 +84,8 @@
         }
 
         else {
-            // For some weird reason the Shibboleth user couldn't be authenticated
+            // The Shibboleth user couldn't be mapped to a valid Moodle user
+            print_error('shib_invalid_account_error', 'auth_shibboleth');
         }
     }
 
index ca0f251..aace9a1 100644 (file)
@@ -51,6 +51,7 @@ $string['auth_shib_no_organizations_warning'] = 'If you want to use the integrat
 $string['auth_shib_only'] = 'Shibboleth only';
 $string['auth_shib_only_description'] = 'Check this option if a Shibboleth authentication shall be enforced';
 $string['auth_shib_username_description'] = 'Name of the webserver Shibboleth environment variable that shall be used as Moodle username';
+$string['shib_invalid_account_error'] = 'You seem to be Shibboleth authenticated but Moodle has no valid account for your username. Your account may not exist or it may have been suspended.';
 $string['shib_no_attributes_error'] = 'You seem to be Shibboleth authenticated but Moodle didn\'t receive any user attributes. Please check that your Identity Provider releases the necessary attributes ({$a}) to the Service Provider Moodle is running on or inform the webmaster of this server.';
 $string['shib_not_all_attributes_error'] = 'Moodle needs certain Shibboleth attributes which are not present in your case. The attributes are: {$a}<br />Please contact the webmaster of this server or your Identity Provider.';
 $string['shib_not_set_up_error'] = 'Shibboleth authentication doesn\'t seem to be set up correctly because no Shibboleth environment variables are present for this page. Please consult the <a href="README.txt">README</a> for further instructions on how to set up Shibboleth authentication or contact the webmaster of this Moodle installation.';
index fb7beb3..e856ead 100644 (file)
@@ -1505,7 +1505,8 @@ abstract class restore_dbops {
             }
             $currentfullname = $fullname.$suffixfull;
             $currentshortname = substr($shortname, 0, 100 - strlen($suffixshort)).$suffixshort; // < 100cc
-            $coursefull  = $DB->get_record_select('course', 'fullname = ? AND id != ?', array($currentfullname, $courseid));
+            $coursefull  = $DB->get_record_select('course', 'fullname = ? AND id != ?',
+                    array($currentfullname, $courseid), '*', IGNORE_MULTIPLE);
             $courseshort = $DB->get_record_select('course', 'shortname = ? AND id != ?', array($currentshortname, $courseid));
             $counter++;
         } while ($coursefull || $courseshort);
index 7afd1be..d0e92af 100644 (file)
@@ -281,9 +281,13 @@ class backup_cron_helper_testcase extends advanced_testcase {
         $this->assertEquals(date('w-15:00', strtotime('tomorrow')), date('w-H:i', $next));
 
         // Let's have a Belgian beer! (UTC+1 / UTC+2 DST).
+        // Warning: Some of these tests will fail if executed "around"
+        // 'Europe/Brussels' DST changes (last Sunday in March and
+        // last Sunday in October right now - 2012). Once Moodle
+        // moves to PHP TZ support this could be fixed properly.
         date_default_timezone_set('Europe/Brussels');
         $now = strtotime('18:00:00');
-        $dst = date('I');
+        $dst = date('I', $now);
 
         $timezone = -10.0; // 7am for the user.
         $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
@@ -321,9 +325,13 @@ class backup_cron_helper_testcase extends advanced_testcase {
         $this->assertEquals($expected, date('w-H:i', $next));
 
         // The big apple! (UTC-5 / UTC-4 DST).
+        // Warning: Some of these tests will fail if executed "around"
+        // 'America/New_York' DST changes (2nd Sunday in March and
+        // 1st Sunday in November right now - 2012). Once Moodle
+        // moves to PHP TZ support this could be fixed properly.
         date_default_timezone_set('America/New_York');
         $now = strtotime('18:00:00');
-        $dst = date('I');
+        $dst = date('I', $now);
 
         $timezone = -10.0; // 1pm for the user.
         $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
@@ -365,9 +373,14 @@ class backup_cron_helper_testcase extends advanced_testcase {
         set_config('backup_auto_hour', '20', 'backup');
         set_config('backup_auto_minute', '00', 'backup');
 
+        // Note: These tests should not fail because they are "unnafected"
+        // by DST changes, as far as execution always happens on Monday and
+        // Saturday and those week days are not, right now, the ones rulez
+        // to peform the DST changes (Sunday is). This may change if rules
+        // are modified in the future.
         date_default_timezone_set('Europe/Brussels');
         $now = strtotime('next Monday 18:00:00');
-        $dst = date('I');
+        $dst = date('I', $now);
 
         $timezone = -12.0;  // 1pm for the user.
         $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
@@ -404,9 +417,14 @@ class backup_cron_helper_testcase extends advanced_testcase {
         set_config('backup_auto_hour', '02', 'backup');
         set_config('backup_auto_minute', '00', 'backup');
 
+        // Note: These tests should not fail because they are "unnafected"
+        // by DST changes, as far as execution always happens on Monday and
+        // Saturday and those week days are not, right now, the ones rulez
+        // to peform the DST changes (Sunday is). This may change if rules
+        // are modified in the future.
         date_default_timezone_set('America/New_York');
         $now = strtotime('next Monday 04:00:00');
-        $dst = date('I');
+        $dst = date('I', $now);
 
         $timezone = -12.0;  // 8pm for the user.
         $next = backup_cron_automated_helper::calculate_next_automated_backup($timezone, $now);
index fafab8a..5026d5b 100644 (file)
@@ -140,7 +140,7 @@ class community_hub_search_form extends moodleform {
             $options = array();
             $firsthub = false;
             foreach ($hubs as $hub) {
-                if (key_exists('id', $hub)) {
+                if (array_key_exists('id', $hub)) {
                     $params = array('hubid' => $hub['id'],
                         'filetype' => HUB_HUBSCREENSHOT_FILE_TYPE);
                     $imgurl = new moodle_url(HUB_HUBDIRECTORYURL .
index 4eff68c..a759913 100644 (file)
@@ -505,7 +505,7 @@ M.core_dock.fixTitleOrientation = function(item, title, text) {
             break;
     }
 
-    if (Y.UA.ie > 7) {
+    if (Y.UA.ie == 8) {
         // IE8 can flip the text via CSS but not handle SVG
         title.setContent(text);
         title.setAttribute('style', 'writing-mode: tb-rl; filter: flipV flipH;display:inline;');
@@ -958,7 +958,7 @@ M.core_dock.genericblock.prototype = {
         placeholder.replace(this.Y.Node.getDOMNode(this.cachedcontentnode));
         this.cachedcontentnode = this.Y.one('#'+this.cachedcontentnode.get('id'));
 
-        var commands = this.cachedcontentnode.one('.title .commands');
+        var commands = dockitem.commands;
         if (commands) {
             commands.all('.hidepanelicon').remove();
             commands.all('.moveto').remove();
index a2555c3..e0469d3 100644 (file)
  * @param bool $forcedownload whether or not force download
  * @param array $options additional options affecting the file serving
  * @return bool
+ * @todo MDL-36050 improve capability check on stick blocks, so we can check user capability before sending images.
  */
 function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
-    global $SCRIPT;
+    global $DB, $CFG;
 
     if ($context->contextlevel != CONTEXT_BLOCK) {
         send_file_not_found();
     }
 
-    require_course_login($course);
+    // If block is in course context, then check if user has capability to access course.
+    if ($context->get_course_context(false)) {
+        require_course_login($course);
+    } else if ($CFG->forcelogin) {
+        require_login();
+    } else {
+        // Get parent context and see if user have proper permission.
+        $parentcontext = $context->get_parent_context();
+        if ($parentcontext->contextlevel === CONTEXT_COURSECAT) {
+            // Check if category is visible and user can view this category.
+            $category = $DB->get_record('course_categories', array('id' => $parentcontext->instanceid), '*', MUST_EXIST);
+            if (!$category->visible) {
+                require_capability('moodle/category:viewhiddencategories', $parentcontext);
+            }
+        }
+        // At this point there is no way to check SYSTEM or USER context, so ignoring it.
+    }
 
     if ($filearea !== 'content') {
         send_file_not_found();
index cbf1dac..2efe9a2 100644 (file)
@@ -31,7 +31,8 @@ class block_online_users extends block_base {
         if (isset($CFG->block_online_users_timetosee)) {
             $timetoshowusers = $CFG->block_online_users_timetosee * 60;
         }
-        $timefrom = 100 * floor((time()-$timetoshowusers) / 100); // Round to nearest 100 seconds for better query cache
+        $now = time();
+        $timefrom = 100 * floor(($now - $timetoshowusers) / 100); // Round to nearest 100 seconds for better query cache
 
         //Calculate if we are in separate groups
         $isseparategroups = ($this->page->course->groupmode == SEPARATEGROUPS
@@ -53,18 +54,23 @@ class block_online_users extends block_base {
         }
 
         $userfields = user_picture::fields('u', array('username'));
-
+        $params['now'] = $now;
+        $params['timefrom'] = $timefrom;
         if ($this->page->course->id == SITEID or $this->page->context->contextlevel < CONTEXT_COURSE) {  // Site-level
             $sql = "SELECT $userfields, MAX(u.lastaccess) AS lastaccess
                       FROM {user} u $groupmembers
-                     WHERE u.lastaccess > $timefrom
+                     WHERE u.lastaccess > :timefrom
+                           AND u.lastaccess <= :now
+                           AND u.deleted = 0
                            $groupselect
                   GROUP BY $userfields
                   ORDER BY lastaccess DESC ";
 
            $csql = "SELECT COUNT(u.id)
                       FROM {user} u $groupmembers
-                     WHERE u.lastaccess > $timefrom
+                     WHERE u.lastaccess > :timefrom
+                           AND u.lastaccess <= :now
+                           AND u.deleted = 0
                            $groupselect";
 
         } else {
@@ -77,9 +83,11 @@ class block_online_users extends block_base {
             $sql = "SELECT $userfields, MAX(ul.timeaccess) AS lastaccess
                       FROM {user_lastaccess} ul $groupmembers, {user} u
                       JOIN ($esqljoin) euj ON euj.id = u.id
-                     WHERE ul.timeaccess > $timefrom
+                     WHERE ul.timeaccess > :timefrom
                            AND u.id = ul.userid
                            AND ul.courseid = :courseid
+                           AND ul.timeaccess <= :now
+                           AND u.deleted = 0
                            $groupselect
                   GROUP BY $userfields
                   ORDER BY lastaccess DESC";
@@ -87,9 +95,11 @@ class block_online_users extends block_base {
            $csql = "SELECT COUNT(u.id)
                       FROM {user_lastaccess} ul $groupmembers, {user} u
                       JOIN ($esqljoin) euj ON euj.id = u.id
-                     WHERE ul.timeaccess > $timefrom
+                     WHERE ul.timeaccess > :timefrom
                            AND u.id = ul.userid
                            AND ul.courseid = :courseid
+                           AND ul.timeaccess <= :now
+                           AND u.deleted = 0
                            $groupselect";
 
             $params['courseid'] = $this->page->course->id;
@@ -138,7 +148,7 @@ class block_online_users extends block_base {
             }
             foreach ($users as $user) {
                 $this->content->text .= '<li class="listentry">';
-                $timeago = format_time(time() - $user->lastaccess); //bruno to calculate correctly on frontpage
+                $timeago = format_time($now - $user->lastaccess); //bruno to calculate correctly on frontpage
 
                 if (isguestuser($user)) {
                     $this->content->text .= '<div class="user">'.$OUTPUT->user_picture($user, array('size'=>16));
index cf22705..0e780f1 100644 (file)
@@ -27,6 +27,7 @@ class block_search_forums extends block_base {
 
         $this->content->text  = '<div class="searchform">';
         $this->content->text .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline"><fieldset class="invisiblefieldset">';
+        $this->content->text .= '<legend class="accesshide">'.$strsearch.'</legend>';
         $this->content->text .= '<input name="id" type="hidden" value="'.$this->page->course->id.'" />';  // course
         $this->content->text .= '<label class="accesshide" for="searchform_search">'.$strsearch.'</label>'.
                                 '<input id="searchform_search" name="search" type="text" size="16" />';
index 367b19e..4ab8ab7 100644 (file)
@@ -126,7 +126,7 @@ function cohort_delete_category($category) {
 }
 
 /**
- * Remove cohort member
+ * Add cohort member
  * @param  int $cohortid
  * @param  int $userid
  * @return void
@@ -143,7 +143,7 @@ function cohort_add_member($cohortid, $userid) {
 }
 
 /**
- * Add cohort member
+ * Remove cohort member
  * @param  int $cohortid
  * @param  int $userid
  * @return void
index 5a8b0d7..31b2799 100644 (file)
@@ -94,7 +94,7 @@ class comment_manager {
      */
     private function setup_course($courseid) {
         global $PAGE, $DB;
-        if (!empty($this->course)) {
+        if (!empty($this->course) && $this->course->id == $courseid) {
             // already set, stop
             return;
         }
index 7bfef1d..2dcaa13 100644 (file)
@@ -445,6 +445,7 @@ M.course_dndupload = {
             a: document.createElement('a'),
             icon: document.createElement('img'),
             namespan: document.createElement('span'),
+            groupingspan: document.createElement('span'),
             progressouter: document.createElement('span'),
             progress: document.createElement('span')
         };
@@ -469,6 +470,9 @@ M.course_dndupload = {
 
         resel.div.appendChild(document.createTextNode(' '));
 
+        resel.groupingspan.className = 'groupinglabel';
+        resel.div.appendChild(resel.groupingspan);
+
         resel.progressouter.className = 'dndupload-progress-outer';
         resel.progress.className = 'dndupload-progress-inner';
         resel.progress.innerHTML = '&nbsp;';
@@ -724,6 +728,13 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+
+                            if (result.groupingname) {
+                                resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                            } else {
+                                resel.div.removeChild(resel.groupingspan);
+                            }
+
                             resel.div.removeChild(resel.progressouter);
                             resel.li.id = result.elementid;
                             resel.div.innerHTML += result.commands;
@@ -904,6 +915,13 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+
+                            if (result.groupingname) {
+                                resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                            } else {
+                                resel.div.removeChild(resel.groupingspan);
+                            }
+
                             resel.div.removeChild(resel.progressouter);
                             resel.li.id = result.elementid;
                             resel.div.innerHTML += result.commands;
index 816924b..038c09b 100644 (file)
@@ -667,6 +667,12 @@ class dndupload_ajax_processor {
         $resp->commands = make_editing_buttons($mod, true, true, 0, $mod->sectionnum);
         $resp->onclick = $mod->get_on_click();
 
+        // if using groupings, then display grouping name
+        if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', $this->context)) {
+            $groupings = groups_get_all_groupings($this->course->id);
+            $resp->groupingname = format_string($groupings[$mod->groupingid]->name);
+        }
+
         echo $OUTPUT->header();
         echo json_encode($resp);
         die();
index b18c0a4..88fd0fa 100644 (file)
@@ -285,7 +285,7 @@ class core_course_external extends external_api {
                         array('options' => $options));
 
         //retrieve courses
-        if (!key_exists('ids', $params['options'])
+        if (!array_key_exists('ids', $params['options'])
                 or empty($params['options']['ids'])) {
             $courses = $DB->get_records('course');
         } else {
@@ -523,12 +523,12 @@ class core_course_external extends external_api {
             require_capability('moodle/course:create', $context);
 
             // Make sure lang is valid
-            if (key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
+            if (array_key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
                 throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
             }
 
             // Make sure theme is valid
-            if (key_exists('forcetheme', $course)) {
+            if (array_key_exists('forcetheme', $course)) {
                 if (!empty($CFG->allowcoursethemes)) {
                     if (empty($availablethemes[$course['forcetheme']])) {
                         throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
@@ -547,10 +547,10 @@ class core_course_external extends external_api {
             //set default value for completion
             $courseconfig = get_config('moodlecourse');
             if (completion_info::is_enabled_for_site()) {
-                if (!key_exists('enablecompletion', $course)) {
+                if (!array_key_exists('enablecompletion', $course)) {
                     $course['enablecompletion'] = $courseconfig->enablecompletion;
                 }
-                if (!key_exists('completionstartonenrol', $course)) {
+                if (!array_key_exists('completionstartonenrol', $course)) {
                     $course['completionstartonenrol'] = $courseconfig->completionstartonenrol;
                 }
             } else {
index 955b58c..51b4b0c 100644 (file)
@@ -531,7 +531,7 @@ function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC",
 
 function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
                         $modid, $modaction, $groupid) {
-    global $DB;
+    global $DB, $CFG;
 
     $text = get_string('course')."\t".get_string('time')."\t".get_string('ip_address')."\t".
             get_string('fullnameuser')."\t".get_string('action')."\t".get_string('info');
@@ -600,6 +600,8 @@ function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
         $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
         $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
         $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action, $log->info);
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);\r
+        $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action.' ('.$actionurl.')', $log->info);
         $text = implode("\t", $row);
         echo $text." \n";
     }
@@ -710,7 +712,8 @@ function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
         $myxls->write($row, 2, $log->ip, '');
         $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
         $myxls->write($row, 3, $fullname, '');
-        $myxls->write($row, 4, $log->module.' '.$log->action, '');
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $myxls->write($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')', '');
         $myxls->write($row, 5, $log->info, '');
 
         $row++;
@@ -823,7 +826,8 @@ function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
         $myxls->write_string($row, 2, $log->ip);
         $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
         $myxls->write_string($row, 3, $fullname);
-        $myxls->write_string($row, 4, $log->module.' '.$log->action);
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $myxls->write_string($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')');
         $myxls->write_string($row, 5, $log->info);
 
         $row++;
@@ -1544,8 +1548,8 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                 $linkclasses = '';
                 $textclasses = '';
                 if ($accessiblebutdim) {
-                    $linkclasses .= ' dimmed';
-                    $textclasses .= ' dimmed_text';
+                    $linkclasses .= ' dimmed conditionalhidden';
+                    $textclasses .= ' dimmed_text conditionalhidden';
                     $accesstext = '<span class="accesshide">'.
                         get_string('hiddenfromstudents').': </span>';
                 } else {
@@ -1572,7 +1576,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     // Display link itself
                     echo '<a ' . $linkcss . $mod->extra . $onclick .
                             ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
-                            '" class="activityicon" alt="" /> ' .
+                            '" class="activityicon" alt="' . $modulename . '" /> ' .
                             $accesstext . '<span class="instancename">' .
                             $instancename . $altname . '</span></a>';
 
@@ -1727,11 +1731,15 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
             // see the activity itself, or for staff)
             if (!$mod->uservisible) {
                 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
-            } else if ($canviewhidden && !empty($CFG->enableavailability) && $mod->visible) {
+            } else if ($canviewhidden && !empty($CFG->enableavailability)) {
+                $visibilityclass = '';
+                if (!$mod->visible) {
+                    $visibilityclass = 'accesshide';
+                }
                 $ci = new condition_info($mod);
                 $fullinfo = $ci->get_full_information();
                 if($fullinfo) {
-                    echo '<div class="availabilityinfo">'.get_string($mod->showavailability
+                    echo '<div class="availabilityinfo '.$visibilityclass.'">'.get_string($mod->showavailability
                         ? 'userrestriction_visible'
                         : 'userrestriction_hidden','condition',
                         $fullinfo).'</div>';
@@ -1966,7 +1974,7 @@ function get_module_metadata($course, $modnames, $sectionreturn = null) {
                 if ($sm->string_exists('modulename_link', $modname)) {  // Link to further info in Moodle docs
                     $link = get_string('modulename_link', $modname);
                     $linktext = get_string('morehelp');
-                    $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
+                    $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
                 }
             }
             $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
index 42b8057..acf5963 100644 (file)
@@ -36,7 +36,7 @@ class courselib_testcase extends advanced_testcase {
         $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
         $oldsections = array();
         $sections = array();
-        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
             $oldsections[$section->section] = $section->id;
             $sections[$section->id] = $section->section;
         }
index 605c5d8..984b9ed 100644 (file)
@@ -580,6 +580,5 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         // Check that the course has been duplicated.
         $this->assertEquals($newcourse['shortname'], $duplicate['shortname']);
-        gc_collect_cycles();
     }
 }
index cb978d8..18f0313 100644 (file)
@@ -20,6 +20,7 @@
     $marker      = optional_param('marker',-1 , PARAM_INT);
     $switchrole  = optional_param('switchrole',-1, PARAM_INT);
     $modchooser  = optional_param('modchooser', -1, PARAM_BOOL);
+    $return      = optional_param('return', 0, PARAM_LOCALURL);
 
     $params = array();
     if (!empty($name)) {
             // Redirect to site root if Editing is toggled on frontpage
             if ($course->id == SITEID) {
                 redirect($CFG->wwwroot .'/?redirect=0');
+            } else if (!empty($return)) {
+                redirect($CFG->wwwroot . $return);
             } else {
                 $url = new moodle_url($PAGE->url, array('notifyeditingon' => 1));
                 redirect($url);
             // Redirect to site root if Editing is toggled on frontpage
             if ($course->id == SITEID) {
                 redirect($CFG->wwwroot .'/?redirect=0');
+            } else if (!empty($return)) {
+                redirect($CFG->wwwroot . $return);
             } else {
                 redirect($PAGE->url);
             }
index f52497e..bd35bd9 100644 (file)
@@ -294,13 +294,13 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                     var resources = Y.Node.create('<ul></ul>');
                     resources.addClass(CSS.SECTION);
                     sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
-                    // Define empty ul as droptarget, so that item could be moved to empty list
-                    var tar = new Y.DD.Drop({
-                        node: resources,
-                        groups: this.groups,
-                        padding: '20 0 20 0'
-                    });
                 }
+                // Define empty ul as droptarget, so that item could be moved to empty list
+                var tar = new Y.DD.Drop({
+                    node: resources,
+                    groups: this.groups,
+                    padding: '20 0 20 0'
+                });
 
                 // Initialise each resource/activity in this section
                 this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
index aea4040..f87a5ec 100644 (file)
@@ -36,36 +36,6 @@ YUI.add('moodle-course-modchooser', function(Y) {
 
             // Catch the page toggle
             Y.all('.block_settings #settingsnav .type_course .modchoosertoggle a').on('click', this.toggle_mod_chooser, this);
-
-            // Ensure that help links are opened in an appropriate popup
-            this.container.all('div.helpdoclink a').on('click', function(e) {
-                var anchor = e.target.ancestor('a', true);
-
-                var args = {
-                    'name'          : 'popup',
-                    'url'           : anchor.getAttribute('href'),
-                    'option'        : ''
-                };
-                var options = [
-                    'height=600',
-                    'width=800',
-                    'top=0',
-                    'left=0',
-                    'menubar=0',
-                    'location=0',
-                    'scrollbars',
-                    'resizable',
-                    'toolbar',
-                    'status',
-                    'directories=0',
-                    'fullscreen=0',
-                    'dependent'
-                ]
-                args.options = options.join(',');
-
-                // Note: openpopup is provided by lib/javascript-static.js
-                openpopup(e, args);
-            });
         },
         /**
          * Update any section areas within the scope of the specified
index 0f70c2c..db7dae8 100644 (file)
@@ -33,7 +33,11 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         SECTIONIDPREFIX : 'section-',
         SECTIONLI : 'li.section',
         SHOW : 'a.editing_show',
-        SHOWHIDE : 'a.editing_showhide'
+        SHOWHIDE : 'a.editing_showhide',
+        CONDITIONALHIDDEN : 'conditionalhidden',
+        AVAILABILITYINFODIV : 'div.availabilityinfo',
+        SHOWCLASS : 'editing_show',
+        ACCESSHIDECLASS : 'accesshide'
     };
 
     /**
@@ -68,18 +72,14 @@ YUI.add('moodle-course-toolboxes', function(Y) {
 
             var status = '';
             var value;
-            if (dimarea.hasClass(toggle_class)) {
+            if (button.hasClass(CSS.SHOWCLASS)) {
                 status = 'hide';
                 value = 1;
             } else {
                 status = 'show';
                 value = 0;
             }
-
-            // Change the UI
-            dimarea.toggleClass(toggle_class);
-            // We need to toggle dimming on the description too
-            element.all(CSS.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
+            // Update button info.
             var newstring = M.util.get_string(status, 'moodle');
             hideicon.setAttrs({
                 'alt' : newstring,
@@ -88,6 +88,19 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             button.set('title', newstring);
             button.set('className', 'editing_'+status);
 
+            // If activity is conditionally hidden, then don't toggle.
+            if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
+                // Change the UI.
+                dimarea.toggleClass(toggle_class);
+                // We need to toggle dimming on the description too.
+                element.all(CSS.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
+            }
+            // Toggle availablity info for conditional activities.
+            var availabilityinfo = element.one(CSS.AVAILABILITYINFODIV);
+
+            if (availabilityinfo) {
+                availabilityinfo.toggleClass(CSS.ACCESSHIDECLASS);
+            }
             return value;
         },
         /**
index 8a0bdfc..48b3fca 100644 (file)
@@ -54,9 +54,9 @@ echo $OUTPUT->header(); // send headers
 
 $manager = new course_enrolment_manager($PAGE, $course);
 
-$outcome = new stdClass;
+$outcome = new stdClass();
 $outcome->success = true;
-$outcome->response = new stdClass;
+$outcome->response = new stdClass();
 $outcome->error = '';
 
 switch ($action) {
@@ -99,6 +99,10 @@ switch ($action) {
             $user->fullname = fullname($user);
             unset($user->id);
         }
+        // Chrome will display users in the order of the array keys, so we need
+        // to ensure that the results ordered array keys. Fortunately, the JavaScript
+        // does not care what the array keys are. It uses user.id where necessary.
+        $outcome->response['users'] = array_values($outcome->response['users']);
         $outcome->success = true;
         break;
     default:
index 7d2fb5a..7e8dd4c 100644 (file)
@@ -388,7 +388,7 @@ class core_role_external extends external_api {
             // throw an exception if user is not able to assign the role in this context
             $roles = get_assignable_roles($context, ROLENAME_SHORT);
 
-            if (!key_exists($assignment['roleid'], $roles)) {
+            if (!array_key_exists($assignment['roleid'], $roles)) {
                 throw new invalid_parameter_exception('Can not assign roleid='.$assignment['roleid'].' in contextid='.$assignment['contextid']);
             }
 
@@ -451,7 +451,7 @@ class core_role_external extends external_api {
 
             // throw an exception if user is not able to unassign the role in this context
             $roles = get_assignable_roles($context, ROLENAME_SHORT);
-            if (!key_exists($unassignment['roleid'], $roles)) {
+            if (!array_key_exists($unassignment['roleid'], $roles)) {
                 throw new invalid_parameter_exception('Can not unassign roleid='.$unassignment['roleid'].' in contextid='.$unassignment['contextid']);
             }
 
index a9d5bb7..b599b95 100644 (file)
@@ -53,9 +53,9 @@ echo $OUTPUT->header(); // send headers
 
 $manager = new course_enrolment_manager($PAGE, $course);
 
-$outcome = new stdClass;
+$outcome = new stdClass();
 $outcome->success = true;
-$outcome->response = new stdClass;
+$outcome->response = new stdClass();
 $outcome->error = '';
 
 switch ($action) {
@@ -79,6 +79,10 @@ switch ($action) {
             }
             $user->extrafields = implode(', ', $fieldvalues);
         }
+        // Chrome will display users in the order of the array keys, so we need
+        // to ensure that the results ordered array keys. Fortunately, the JavaScript
+        // does not care what the array keys are. It uses user.id where necessary.
+        $outcome->response['users'] = array_values($outcome->response['users']);
         $outcome->success = true;
         break;
     case 'enrol':
@@ -135,4 +139,4 @@ switch ($action) {
         throw new enrol_ajax_exception('unknowajaxaction');
 }
 
-echo json_encode($outcome);
\ No newline at end of file
+echo json_encode($outcome);
index 44d90ac..81907a8 100644 (file)
@@ -101,7 +101,7 @@ class enrol_manual_external extends external_api {
 
             //throw an exception if user is not able to assign the role
             $roles = get_assignable_roles($context);
-            if (!key_exists($enrolment['roleid'], $roles)) {
+            if (!array_key_exists($enrolment['roleid'], $roles)) {
                 $errorparams = new stdClass();
                 $errorparams->roleid = $enrolment['roleid'];
                 $errorparams->courseid = $enrolment['courseid'];
index f7ea7e2..f458a91 100644 (file)
@@ -64,7 +64,7 @@ $PAGE->set_heading($course->fullname);
 navigation_node::override_active_url(new moodle_url('/enrol/users.php', array('id'=>$course->id)));
 
 // Create the user selector objects.
-$options = array('enrolid' => $enrolid);
+$options = array('enrolid' => $enrolid, 'accesscontext' => $context);
 
 $potentialuserselector = new enrol_manual_potential_participant('addselect', $options);
 $currentuserselector = new enrol_manual_current_participant('removeselect', $options);
index f49a799..66cddda 100644 (file)
@@ -6,7 +6,7 @@
 .moodle-dialogue-base .moodle-dialogue-hd {font-size:110%;color:inherit;font-weight:bold;text-align:left;padding:5px 6px;margin:0;border-bottom:1px solid #ccc;background-color:#f6f6f6;}
 .moodle-dialogue-base .closebutton {background-image:url(sprite.png);width:25px;height:15px;background-repeat:no-repeat;float:right;vertical-align:middle;display:inline-block;cursor:pointer;}
 .moodle-dialogue-base .moodle-dialogue-bd {padding:5px; overflow: auto;}
-.moodle-dialogue-base .moodle-dialogue-fd {}
+.moodle-dialogue-base .moodle-dialogue-ft {}
 
 .moodle-dialogue-confirm .confirmation-dialogue {text-align:center;}
 .moodle-dialogue-confirm .confirmation-message {margin:0.5em 1em;}
index 8b91443..2ed9af0 100644 (file)
@@ -21,7 +21,7 @@ var DIALOGUE_NAME = 'Moodle dialogue',
         HEADER : 'moodle-dialogue-hd',
         BODY : 'moodle-dialogue-bd',
         CONTENT : 'moodle-dialogue-content',
-        FOOTER : 'moodle-dialogue-fd',
+        FOOTER : 'moodle-dialogue-ft',
         HIDDEN : 'hidden',
         LIGHTBOX : 'moodle-dialogue-lightbox'
     };
@@ -35,7 +35,7 @@ var DIALOGUE = function(config) {
             .append(C('<div id="'+id+'" class="'+CSS.WRAP+'"></div>')
                 .append(C('<div class="'+CSS.HEADER+' yui3-widget-hd"></div>'))
                 .append(C('<div class="'+CSS.BODY+' yui3-widget-bd"></div>'))
-                .append(C('<div class="'+CSS.CONTENT+' yui3-widget-ft"></div>')));
+                .append(C('<div class="'+CSS.FOOTER+' yui3-widget-ft"></div>')));
     Y.one(document.body).append(config.notificationBase);
     config.srcNode =    '#'+id;
     config.width =      config.width || '400px';
index d7d3b5b..a1e634b 100644 (file)
@@ -1503,7 +1503,7 @@ class grade_structure {
 
             $url->param('action', 'show');
 
-            $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'iconsmall')));
+            $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon')));
 
         } else {
             $url->param('action', 'hide');
index ea8f076..3dbac53 100644 (file)
@@ -225,7 +225,7 @@ class grade_report_grader extends grade_report {
                 $changedgrades = true;
 
             } else if ($datatype === 'feedback') {
-                if ($oldvalue->feedback === $postedvalue) {
+                if (($oldvalue->feedback === $postedvalue) or ($oldvalue->feedback === NULL and empty($postedvalue))) {
                     continue;
                 }
             }
index f3bdac4..ff283b1 100644 (file)
@@ -51,4 +51,5 @@ $string['pathssubdataroot'] = 'Тази директория е място, къ
 $string['pathssubdirroot'] = 'Пълен път до директорията на Moodle.';
 $string['pathssubwwwroot'] = 'Пълен интернет адрес, на който ще се отваря Moodle. Не е възможно Moodle да се отваря чрез различни адреси. Ако Вашият сайт има няколко адреса трябва на всеки от другите адреси да направите пренасочване към този. Ако Вашият сайт се отваря както глобално от Интернет, така и от локална мрежа, настройте DNS, така че потребителите от локалната мрежа също да могат да ползват глобалния адрес. Ако адресът не е коректен, моля, променете адреса в браузъра си и започнете инсталирането с правилния адрес.';
 $string['phpextension'] = '{$a} разширение на PHP';
+$string['phpversionhelp'] = '<p>Moodle изисква версия на PHP най-малко 4.3.0 или 5.1.0 (5.0.x има значителен брой известни проблеми).</p> <p>Вие използвате в момента версия {$a}</p> <p>Трябва да обновите PHP или да се преместите на нов хост (сървър) с по-нова версия на PHP!<br /> (В случая с 5.0.x може да опитате да инсталирате по-старата версия 4.4.x)</p>';
 $string['wwwroot'] = 'Уеб адрес';
diff --git a/install/lang/en_fix/langconfig.php b/install/lang/en_fix/langconfig.php
new file mode 100644 (file)
index 0000000..d563203
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thislanguage'] = 'English (fixes)';
diff --git a/install/lang/en_kids/langconfig.php b/install/lang/en_kids/langconfig.php
new file mode 100644 (file)
index 0000000..7a195a4
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thislanguage'] = 'English for kids';
index 57673d4..006fcb9 100644 (file)
@@ -54,14 +54,14 @@ $string['memorylimithelp'] = '<p>PHP muistiraja palvelimellesi on tällä hetkel
 
 <p>Tämä saattaa aiheuttaa Moodlelle muistiongelmia myöhemmin, varsinkin jos sinulla on paljon mahdollisia moduuleita ja/tai paljon käyttäjiä.</p>
 
-<p>Suosittelemme, että valitset asetuksiksi PHP:n korkeimmalla mahdollisella raja-arvolla, esimerkiksi 16M.
+<p>Suosittelemme, että valitset asetuksiksi PHP:n korkeimmalla mahdollisella raja-arvolla, esimerkiksi 40M.
 On olemassa monia tapoja joilla voit yrittää tehdä tämän:</p>
 <ol>
 <li>Jos pystyt, uudelleenkäännä PHP <i>--enable-memory-limit</i>. :llä.
 Tämä sallii Moodlen asettaa muistirajan itse.</li>
-<li>Jos sinulla on pääsy php.ini tiedostoosi, voit muuttaa <b>memory_limit</b> asetuksen siellä johonkin kuten 16M. Jos sinulla ei ole pääsyoikeutta, voit kenties pyytää ylläpitäjää tekemään tämän puolestasi.</li>
+<li>Jos sinulla on pääsy php.ini tiedostoosi, voit muuttaa <b>memory_limit</b> asetuksen siellä johonkin kuten 40M. Jos sinulla ei ole pääsyoikeutta, voit kenties pyytää ylläpitäjää tekemään tämän puolestasi.</li>
 <li>Joillain PHP palvelimilla voit luoda a .htaccess tiedoston Moodle hakemistossa, sisältäen tämän rivin:
-<p><blockquote>php_value memory_limit 16M</blockquote></p>
+<p><blockquote>php_value memory_limit 40M</blockquote></p>
 <p>Kuitenkin, joillain palvelimilla tämä estää  <b>kaikkia</b> PHP sivuja toimimasta (näet virheet, kun katsot sivuja), joten sinun täytyy poistaa .htaccess tiedosto.</p></li>
 </ol>';
 $string['paths'] = 'Polut';
index 6a2e548..fe6d20a 100644 (file)
@@ -33,13 +33,13 @@ defined('MOODLE_INTERNAL') || die();
 $string['cannotcreatelangdir'] = 'לא ניתן ליצור סיפריית שפה.';
 $string['cannotcreatetempdir'] = 'לא ניתן ליצור סיפרייה זמנית.';
 $string['cannotdownloadcomponents'] = 'לא ניתן להוריד רכיבים.';
-$string['cannotdownloadzipfile'] = 'לא ניתן להוריד קובץ ZIP.';
+$string['cannotdownloadzipfile'] = 'לא ניתן להוריד קובץ 7Zip';
 $string['cannotfindcomponent'] = 'הרכיב לא נמצא.';
 $string['cannotsavemd5file'] = 'לא ניתן לשמור קובץ md5.';
 $string['cannotsavezipfile'] = 'לא ניתן לשמור קובץ ZIP.';
 $string['cannotunzipfile'] = 'לא ניתן לפתוח את קובץ ה-ZIP.';
 $string['componentisuptodate'] = 'הרכיב מעודכן.';
-$string['downloadedfilecheckfailed'] = '× ×\9bש×\9c×\94 ×\91×\93×\99קת ×\94ק×\95×\91×¥ ×\94×\9e×\95ר×\93.';
+$string['downloadedfilecheckfailed'] = '×\94ק×\95×\91×¥ ×\90שר ×\99ר×\93 × ×\9eצ×\90 ×©×\92×\95×\99';
 $string['invalidmd5'] = 'md5 לא חוקי';
 $string['missingrequiredfield'] = 'חסר שדה נדרש כלשהו';
 $string['remotedownloaderror'] = 'הורדת הרכיב לשרת שלך כשלה, אנא וודא את הגדרות ה-proxy שלך. תוספת PHP cURL מומלצת מאוד להתקנה.
index d6cb0ae..52508ae 100644 (file)
@@ -101,11 +101,9 @@ $string['welcomep40'] = 'החבילה כוללת בנוסף
 $string['welcomep50'] = 'השימוש בכל היישומים בחבילה זו מפוקח ע"י הרשיונות המתאימים להם. החבילה
 <strong>{$a->installername}</strong>
 השלמה היא
-<a href="http://www.opensource.org/docs/definition_plain.html"> קוד פתוח
-</a>
-והיא מבוזרת תחת רישיון
-<a>
-href="http://www.gnu.org/copyleft/gpl.html">GPL</a>';
+<a href="http://www.opensource.org/docs/definition_plain.html">קוד פתוח</a>
+והיא מופצת תחת רשיון
+<a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>';
 $string['welcomep60'] = 'העמודים הבאים יובילו אותך בצורה פשוטה דרך כמה צעדים לעיצוב הגדרות <strong>Moodle</strong> במחשבך.
 תוכל לאשר את הגדרות  ברירת המחדל או, באפשרותך, לשנותם לפי צרכיך.';
 $string['welcomep70'] = 'הקש על לחצן ה"המשך" למטה כדי להמשיך עם הגדרת ה-<strong>Moodle</strong>';
index 1b8da44..4e2760f 100644 (file)
@@ -30,5 +30,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['parentlanguage'] = '';
 $string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'Italiano';
diff --git a/install/lang/ja_kids/langconfig.php b/install/lang/ja_kids/langconfig.php
new file mode 100644 (file)
index 0000000..6eb3957
--- /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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['parentlanguage'] = 'ja';
+$string['thisdirection'] = 'ltr';
+$string['thislanguage'] = '日本語 小学生';
diff --git a/install/lang/ms/admin.php b/install/lang/ms/admin.php
new file mode 100644 (file)
index 0000000..3673ceb
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['clianswerno'] = 't';
+$string['cliansweryes'] = 'y';
+$string['cliincorrectvalueerror'] = 'Ralat, nilai tidak betul "{$a->value}" untuk "{$a->option}"';
+$string['cliincorrectvalueretry'] = 'Nilai yang salah, sila cuba lagi';
+$string['clitypevalue'] = 'taip nilai';
+$string['clitypevaluedefault'] = 'taip nilai, tekan Enter untuk gunakan nilai lalai ({$a})';
+$string['cliunknowoption'] = 'Pilihan yang tidak diketahui:
+  {$a}
+Sila gunakan pilihan --help';
+$string['cliyesnoprompt'] = 'tapi y (ya) atau t (tidak)';
diff --git a/install/lang/ms/install.php b/install/lang/ms/install.php
new file mode 100644 (file)
index 0000000..a9240fc
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['admindirname'] = 'Direktori admin';
+$string['availablelangs'] = 'Senarai bahasa yang tersedia';
+$string['chooselanguagehead'] = 'Pilih bahasa';
+$string['chooselanguagesub'] = 'Sila pilih bahasa untuk pemasangan. Bahasa ini juga akan digunakan sebagai bahasa lalai untuk laman, ia boleh diubah kemudian.';
+$string['clialreadyinstalled'] = 'config.php fail sudah wujud, sila gunakan admin/cli/upgrade.php jika anda mahu menaik taraf laman web anda.';
+$string['cliinstallheader'] = 'Baris arahan pemasangan program Moodle {$a}';
+$string['databasehost'] = 'Hos pangkalan data';
+$string['databasename'] = 'Nama pangkalan data';
+$string['databasetypehead'] = 'Pilih pemacu pangkalan data';
+$string['dataroot'] = 'Direktori data';
+$string['dbprefix'] = 'Awalan jadual';
+$string['dirroot'] = 'Direktori Moodle';
+$string['environmenthead'] = 'Memeriksa persekitaran anda ...';
+$string['environmentsub2'] = 'Setiap pelepasan Moodle mempunyai keperluan minimum versi PHP dan beberapa sambungan mandatori PHP. Persekitaran penuh disemak sebelum setiap pemasangan dan naik taraf. Sila hubungi pentadbir pelayan jika anda tidak tahu bagaimana untuk memasang versi baru atau membolehkan sambungan PHP.';
+$string['errorsinenvironment'] = 'Semakan persekitaran gagal!';
+$string['installation'] = 'Pemasangan';
+$string['langdownloaderror'] = 'Malangnya bahasa "{$a}" tidak boleh dimuat turun. Proses pemasangan akan diteruskan dalam bahasa Bahasa Inggeris.';
+$string['memorylimithelp'] = '<p> Had memori PHP untuk pelayan anda  ditetapkan kepada {$a}. </p>
+
+<p> Hal ini akan menyebabkan Moodle untuk mendapat masalah memori di kemudian hari, terutama jika anda mempunyai banyak modul yang diaktifkan dan/atau ramai pengguna. </p>
+
+<p> Kami menyarankan anda mengkonfigurasikan PHP dengan had yang lebih tinggi jika memungkinkan, seperti 40M. Ada beberapa cara untuk melakukan hal ini yang anda boleh cuba: </p>
+<ol>
+<li> Jika anda mampu, kompil semula PHP dengan <i>--enable-memory-limit.</i> Hal ini akan membolehkan Moodle untuk menetapkan batas memori itu sendiri. </li>
+<li> Jika anda mempunyai akses ke fail php.ini, anda boleh menukar setting <b>memory_limit</b> di sana untuk sesuatu seperti 40M. Jika anda tidak mempunyai akses anda mungkin boleh meminta pentadbir anda untuk melakukan ini untuk anda. </li>
+<li> Pada beberapa server PHP anda boleh mencipta baris fail .htaccess di direktori Moodle mengandungi ini: <blockquote><div> php_value memory_limit 40M </div></blockquote><p> Namun, pada beberapa server ini akan mencegah <b>semua</b> laman PHP dari berjalan (anda akan melihat ralat ketika anda melihat halaman), sehingga anda hapuskan file .htaccess. </p></li></ol>';
+$string['paths'] = 'Laluan';
+$string['pathserrcreatedataroot'] = 'Direktori data ({$a->dataroot}) tidak boleh diwujudkan oleh pemasang.';
+$string['pathshead'] = 'Sahkan laluan';
+$string['pathsrodataroot'] = 'Direktori dataroot tidak dapat ditulis.';
+$string['pathsroparentdataroot'] = 'Direktori induk ({$a->parent}) tidak boleh tulis. Direktori data ({$a->dataroot}) tidak boleh diwujudkan oleh pemasang.';
+$string['pathssubadmindir'] = 'Beberapa webhos menggunakan /admin sebagai URL khas untuk anda akses panel kawalan atau sesuatu yang lain. Malangnya, ini bertentangan dengan lokasi standard untuk halaman pentadbiran Moodle. Anda boleh memperbaiki ini dengan menamakan semula direktori admin dalam pemasangan anda, dan meletakkan nama baru di sini. Sebagai contoh: <br /> <br /> <b> moodleadmin </ b> <br /> <br />
+Ini akan memulihkan pautan admin dalam Moodle.';
+$string['pathssubdataroot'] = 'Anda perlukan lokasi di mana Moodle boleh menyimpan fail yang dimuat naik. Direktori ini sepatutnya boleh dibaca DAN BOLEH DITULIS oleh pengguna web server (biasanya \'nobody\' atau \'apache\'), tetapi ia mestilah tidak boleh diakses secara langsung melalui laman web. Pemasang akan cuba untuk mewujudkannya jika belum ada.';
+$string['pathssubdirroot'] = 'Direktori laluan penuh untuk pemasangan Moodle.';
+$string['pathssubwwwroot'] = 'Alamat penuh web di mana Moodle akan dicapai. Adalah tidak mungkin untuk mengakses Moodle menggunakan pelbagai alamat. Jika laman anda mempunyai beberapa alamat awam, anda mesti menubuhkan pelencongan kekal kepada semua mereka kecuali yang satu ini. Jika laman web anda boleh diakses kedua-duanya dari Intranet dan Internet menggunakan alamat awam di sini dan menubuhkan DNS supaya pengguna Intranet boleh menggunakan alamat awam juga. Jika alamat itu tidak betul, sila ubah URL di pelayar anda untuk memulakan semula pemasangan dengan nilai yang berbeza.';
+$string['pathsunsecuredataroot'] = 'Lokasi dataroot tidak selamat';
+$string['pathswrongadmindir'] = 'Direktori admin tidak wujud';
+$string['phpextension'] = 'Sambungan PHP {$a}';
+$string['phpversion'] = 'Versi PHP';
+$string['phpversionhelp'] = '<p> Moodle memerlukan versi PHP minimal 4.3.0 atau 5.1.0 (5.0.x memiliki sejumlah masalah yang diketahui). </p>
+<p> Anda sedang menjalankan versi {$a} </p>
+<p> Anda harus upgrade PHP atau berpindah ke host dengan versi terbaru PHP! <br /> (Dalam kes 5.0.x Anda juga boleh turun taraf ke versi 4.4.x) </p>';
+$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
+$string['welcomep20'] = 'Anda melihat halaman ini kerana anda telah berjaya memasang dan melancarkan pakej <strong>{$a->packname} {$a->packversion}</strong> di komputer anda. Tahniah!';
+$string['welcomep30'] = 'Keluaran ini <strong>{$a->installername}</strong> termasuk aplikasi untuk mencipta sebuah persekitaran di mana <strong>Moodle</strong> akan beroperasi, iaitu:';
+$string['welcomep40'] = 'Pakej ini juga termasuk <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
+$string['welcomep50'] = 'Penggunaan semua aplikasi di dalam pakej ini dikawal oleh lesen masing-masing. Pakej lengkap <strong>{$a->installername}</strong> adalah <a href="http://www.opensource.org/docs/definition_plain.html">sumber terbuka</a> dan diedarkan di bawah lesen <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>.';
+$string['welcomep60'] = 'Laman-laman berikut akan membawa anda melalui beberapa langkah-langkah mudah diikuti untuk mengkonfigurasi dan menetapkan <strong>Moodle</strong> pada komputer anda. Anda mungkin menerima tetapan lalai atau, dengan pilihan, meminda untuk disesuaikan dengan keperluan anda.';
+$string['welcomep70'] = 'Klik butang "Seterusnya" di bawah untuk meneruskan penubuhan <strong>Moodle</strong>.';
+$string['wwwroot'] = 'Alamat Web';
diff --git a/install/lang/ms/moodle.php b/install/lang/ms/moodle.php
new file mode 100644 (file)
index 0000000..145ec13
--- /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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['language'] = 'Bahasa';
+$string['next'] = 'Seterusnya';
+$string['previous'] = 'Sebelum';
index 1971fb3..ec01ae5 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['admindirname'] = 'Admin Directory';
+$string['admindirname'] = 'Director Admin';
 $string['availablelangs'] = 'Pachete de limbă disponibile';
 $string['chooselanguagehead'] = 'Selectare limbă';
 $string['chooselanguagesub'] = 'Vă rugăm selectaţi limba pentru interfaţa de instalare, limba selectată va fi folosită EXCLUSIV în cadrul procedurii de instalare. Ulterior veţi putea selecta limba în care doriţi să fie afişată interfaţa.';
-$string['dataroot'] = 'Data Directory';
+$string['databasehost'] = 'Gazdă baza de date';
+$string['databasename'] = 'Nume baza de date';
+$string['databasetypehead'] = 'Alegere driver baza de date';
+$string['dataroot'] = 'Director date';
+$string['datarootpermission'] = 'Permisiuni directoare date';
 $string['dbprefix'] = 'Prefix tabele';
 $string['dirroot'] = 'Director Moodle';
 $string['environmenthead'] = 'Se verifică mediul...';
 $string['installation'] = 'Instalare';
+$string['paths'] = 'Căi';
+$string['pathshead'] = 'Confirmare căi';
+$string['phpextension'] = 'extensie PHP {$a}';
 $string['phpversion'] = 'Versiune PHP';
 $string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
 $string['wwwroot'] = 'Adresă Web';
index 1dc0ec5..6a243d6 100644 (file)
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Овлашћења над директориј
 $string['dbprefix'] = 'Префикс табеле';
 $string['dirroot'] = 'Moodle директоријум';
 $string['environmenthead'] = 'Проверавање Вашег окружења...';
-$string['environmentsub2'] = 'Свака верзија Moodlea има минимум захтева по питању одговарајуће PHP верѕије и неколико обавезних PHP екстензија.
+$string['environmentsub2'] = 'Свака верзија Moodlea има минимум захтева по питању одговарајуће PHP верзије и неколико обавезних PHP екстензија.
 Пуна провера окружења се врши пре сваке инсталације или ажурирања постојеће верзије. Уколико не знате како да инсталирате нову верзију или омогућите PHP ектензије контактирајте Вашег сервер администратора.';
 $string['errorsinenvironment'] = 'Провера окружења није прошла!';
 $string['installation'] = 'Инсталација';
@@ -75,7 +75,7 @@ $string['pathssubwwwroot'] = 'Пуна веб адреса путем које 
 Ако је адреса нетачна промените URL у свом веб читачу да бисте поново покренули инсталацију са другачијом вредношћу.';
 $string['pathsunsecuredataroot'] = 'Dataroot локација није безбедна';
 $string['pathswrongadmindir'] = 'Админ директоријум не постоји';
-$string['phpextension'] = '{$a} PHP екстенѕија';
+$string['phpextension'] = '{$a} PHP екстензија';
 $string['phpversion'] = 'PHP верзија';
 $string['phpversionhelp'] = '<p>Moodle захтева најмање PHP верзију 4.3.0 или 5.1.0 (5.0.x има  бројне уочене проблеме).</p>
 <p>Тренутно користите верзију {$a}</p>
index ef41512..43f6dd0 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['clianswerno'] = 'n';
+$string['cliansweryes'] = 'y';
+$string['cliincorrectvalueerror'] = '錯誤,不正確的值 "{$a->value}" 用於 "{$a->option}"';
+$string['cliincorrectvalueretry'] = '不正確的值,請再試一次';
+$string['clitypevalue'] = '輸入值';
+$string['clitypevaluedefault'] = '輸入值,按Enter可使用預設值({$a})';
+$string['cliunknowoption'] = '不認得的選項:  {$a}
+請使用 --幫助 選項。';
+$string['cliyesnoprompt'] = '輸入y(是) 或n(否)';
 $string['environmentrequireinstall'] = '必須安裝/啟用';
 $string['environmentrequireversion'] = '要求版本為 {$a->needed} ,您目前版本為 {$a->current}';
index e678417..28af033 100644 (file)
@@ -39,10 +39,10 @@ $string['cannotsavemd5file'] = '無法儲存 md5 檔案。';
 $string['cannotsavezipfile'] = '無法儲存 ZIP 檔案。';
 $string['cannotunzipfile'] = '無法解壓縮檔案。';
 $string['componentisuptodate'] = '元件已經是最新的了。';
-$string['downloadedfilecheckfailed'] = '下載檔案檢查錯誤。';
-$string['invalidmd5'] = '無效的 md5';
+$string['downloadedfilecheckfailed'] = '下載的檔案檢查失敗';
+$string['invalidmd5'] = '這檢查變項是錯的,再試一次。';
 $string['missingrequiredfield'] = '缺少部份必填欄位';
 $string['remotedownloaderror'] = '下載元件至伺服器失敗,檢查代理伺服器的設定、高度建議安裝PHP cURL,您必須手動下載<a href="{$a->url}">{$a->url}</a>,並且複製到伺服器"{$a->dest}" 解壓縮';
 $string['wrongdestpath'] = '錯誤的目的路徑。';
-$string['wrongsourcebase'] = '錯誤的來源網址基礎。';
+$string['wrongsourcebase'] = '錯誤的來源基礎網址。';
 $string['wrongzipfilename'] = '錯誤的 ZIP 檔名。';
index 07cb23e..32415de 100644 (file)
@@ -33,11 +33,21 @@ defined('MOODLE_INTERNAL') || die();
 $string['admindirname'] = '管理目錄';
 $string['availablelangs'] = '可使用的語言包';
 $string['chooselanguagehead'] = '選擇一種語言';
-$string['chooselanguagesub'] = '請選擇在安裝過程中使用的語言。稍後您可以根據需要重新選擇用於網站和使用者的語言。';
+$string['chooselanguagesub'] = '請選擇在安裝過程中使用的語言。這語言將成為此網站預設的語言。稍後您可以根據需要重新選擇。';
+$string['clialreadyconfigured'] = '檔案 config.php  已經存在,若你要安裝這一網站,請使用dmin/cli/install_database.php';
+$string['clialreadyinstalled'] = '檔案 config.php  已經存在,若你要升級這一網站,請使用admin/cli/upgrade.php';
+$string['cliinstallheader'] = 'Moodle {$a} 命令列安裝程式';
+$string['databasehost'] = '資料庫主機';
+$string['databasename'] = '資料庫名稱';
+$string['databasetypehead'] = '選擇資料庫裝置';
 $string['dataroot'] = '資料目錄';
+$string['datarootpermission'] = '資料目錄存取授權';
 $string['dbprefix'] = '資料表名稱的前置字元';
 $string['dirroot'] = 'Moodle目錄';
 $string['environmenthead'] = '檢查您的環境中...';
+$string['environmentsub2'] = '每一個Moodle版本都有一些PHP版本的最低要求和一堆強制開啟的PHP擴展。在進行安裝或升級之前都需要作完整的環境檢查。<br />
+若你不知道要怎樣新的PHP版本或啟用PHP擴展,請聯絡伺服器管理員。';
+$string['errorsinenvironment'] = '環境檢查失敗!';
 $string['installation'] = '安裝';
 $string['langdownloaderror'] = '很不幸地,語言“{$a}”並未安裝。安裝過程將以英文繼續。';
 $string['memorylimithelp'] = '<p>PHP記憶體上限目前設定為{$a}。</p>
@@ -45,11 +55,23 @@ $string['memorylimithelp'] = '<p>PHP記憶體上限目前設定為{$a}。</p>
 <p>建議您儘可能將PHP的上限設得高一點,比如16M。
 以下有幾種方式您可以試試:
 <ol>
-<li>如果可以的話,用<i>--enable-memory-limit</i>重新編譯PHP。讓Moodle自己設定記憶體上限.
-<li>如果您要使用php.ini檔, 您可以改變<b>memory_limit</b>這個設定值,例如到16M。如果您無法使用這個檔,您可以請您的管理者幫您做
-<li>在一些PHP伺服器上,您可以在Moodle目錄下,建立.htaccess檔,包含這行:<p><blockquote>php_value memory_limit 16M</blockquote></p>
+<li>如果可以的話,用<i>--enable-memory-limit</i>重新編譯PHP。讓 Moodle 自己設定記憶體上限。
+<li>如果您要使用 php.ini 檔,您可以改變<b>memory_limit</b>這個設定值,例如到16M。如果您無法使用這個檔,您可以請您的管理者幫您做
+<li>在一些PHP伺服器上,您可以在Moodle目錄下,建立 .htaccess 檔,包含這行:<p><blockquote>php_value memory_limit 16M</blockquote></p>
 <p>然而,在一些伺服器上,這將造成<b>所有的</b> PHP 網頁無法運作(當您看這些網頁時,您就會看到錯誤) 因此,您就必須將 .htaccess 檔案移除。
 </ol>';
+$string['paths'] = '路徑';
+$string['pathserrcreatedataroot'] = '資料目錄 ({$a->dataroot})無法由這安裝程式建立';
+$string['pathshead'] = '確認路徑';
+$string['pathsrodataroot'] = '資料根目錄是無法寫入的';
+$string['pathsroparentdataroot'] = '上層目錄({$a->parent})是不可寫入的。安裝程式無法建立資料目錄({$a->dataroot})。';
+$string['pathssubadmindir'] = '有些網站主機使用/admin這個網址來瀏覽控制面版或其他功能。很不幸,這個設定和Moodle管理頁面的標準路徑產生衝突。這個問題可以解決,只需在您的安裝目錄中把admin更換名稱,然後把新名稱輸入到這裡。例如<em>moodleadmin</em>這麼做會改變Moodle中的管理連接。';
+$string['pathssubdataroot'] = '你需要有一個地方讓Moodle可以儲存上傳的檔案。這一目錄對於網頁伺服器用戶(通常是"nobody"或"apache")而言,應該是可讀的和<b>可寫的</b>。但是它必須不能經由網頁直接存取。若此目錄不存在,這安裝程式將會試著建立它。';
+$string['pathssubdirroot'] = 'Moodle安裝的完整目錄路徑。';
+$string['pathssubwwwroot'] = '可以瀏覽到Moodle的完整網址。Moodle不支援透過多個網址瀏覽,如果您的往佔有多個公開網址,您必須把這個網址以外的網址都設定為永久重新導向。如果您的網站可以透過內部網址瀏覽,有可以透過這個公開網址瀏覽,那麼請設定DNS內部使網路使用者也能使用的空該網址。如果此網址不正確,請在瀏覽器中修改URL來重新安裝,並設定另一個網址。';
+$string['pathsunsecuredataroot'] = '資料根(Dataroot)目錄的位置不安全';
+$string['pathswrongadmindir'] = '管理目錄不存在';
+$string['phpextension'] = '{$a} PHP擴展';
 $string['phpversion'] = 'PHP版本';
 $string['phpversionhelp'] = '<p>Moodle 需要至少4.1.0.的PHP版本 </p>
 <p>您目前執行的是{$a} 版</p>
index 8a6a935..919a7a9 100644 (file)
@@ -258,7 +258,7 @@ $string['configmycoursesperpage'] = 'Maximum number of courses to display in any
 $string['configmymoodleredirect'] = 'This setting forces redirects to /my on login for non-admins and replaces the top level site navigation with /my';
 $string['configmypagelocked'] = 'This setting prevents the default page from being edited by any non-admins';
 $string['confignavcourselimit'] = 'Limits the number of courses shown to the user when they are either not logged in or are not enrolled in any courses.';
-$string['confignavshowallcourses'] = 'If enabled users will see courses they are enrolled in both within the My Courses branch and the course structure. When disabled users with enrolments will only see the My Courses branch of the navigaiton.';
+$string['confignavshowallcourses'] = 'If enabled users will see courses they are enrolled in both within the My Courses branch and the course structure. When disabled users with enrolments will only see the My Courses branch of the navigaiton. The number of course shown would still be limited by "Course limit(navcourselimit)" setting when user is either not logged in or not enrolled in any course.';
 $string['confignavshowcategories'] = 'Show course categories in the navigation bar and navigation blocks. This does not occur with courses the user is currently enrolled in, they will still be listed under mycourses without categories.';
 $string['confignotifyloginfailures'] = 'If login failures have been recorded, email notifications can be sent out.  Who should see these notifications?';
 $string['confignotifyloginthreshold'] = 'If notifications about failed logins are active, how many failed login attempts by one user or one IP address is it worth notifying about?';
index d55667d..d926cd2 100644 (file)
@@ -25,6 +25,7 @@
 
 $string['addfields'] = 'Add {$a} fields to form';
 $string['advancedelement'] = 'Advanced element';
+$string['close'] = 'Close';
 $string['day'] = 'Day';
 $string['display'] = 'Display';
 $string['err_alphanumeric'] = 'You must enter only letters or numbers here.';
index 9cb966d..64624fb 100644 (file)
@@ -46,6 +46,7 @@ $string['application/vnd.openxmlformats-officedocument.presentationml.slideshow'
 $string['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'] = 'Excel spreadsheet';
 $string['application/vnd.openxmlformats-officedocument.spreadsheetml.template'] = 'Excel template';
 $string['application/vnd.openxmlformats-officedocument.wordprocessingml.document'] = 'Word document';
+$string['application/epub_zip'] = 'EPUB ebook';
 $string['archive'] = 'Archive ({$a->EXT})';
 $string['audio'] = 'Audio file ({$a->EXT})';
 $string['default'] = '{$a->mimetype}';
index ffbdec3..f9ab6a9 100644 (file)
@@ -121,6 +121,7 @@ $string['course:changesummary'] = 'Change course summary';
 $string['course:enrolconfig'] = 'Configure enrol instances in courses';
 $string['course:enrolreview'] = 'Review course enrolments';
 $string['course:ignorefilesizelimits'] = 'Use files larger than any file size restrictions';
+$string['course:isincompletionreports'] = 'Be shown on completion reports';
 $string['course:manageactivities'] = 'Manage activities';
 $string['course:managefiles'] = 'Manage files';
 $string['course:managegrades'] = 'Manage grades';
index 2e8121b..c74e557 100644 (file)
@@ -7561,7 +7561,7 @@ class admin_setting_managewebservicetokens extends admin_setting {
                         array(array('id' => $token->userid)), $token->serviceid);
 
                 if (!is_siteadmin($token->userid) and
-                        key_exists($token->userid, $usermissingcaps)) {
+                        array_key_exists($token->userid, $usermissingcaps)) {
                     $missingcapabilities = implode(', ',
                             $usermissingcaps[$token->userid]);
                     if (!empty($missingcapabilities)) {
index 57d643f..b278c14 100644 (file)
  */
 
 class Bennu {
-    function timestamp_to_datetime($t = NULL) {
+    static function timestamp_to_datetime($t = NULL) {
         if($t === NULL) {
             $t = time();
         }
         return gmstrftime('%Y%m%dT%H%M%SZ', $t);
     }
 
-    function generate_guid() {
+    static function generate_guid() {
         // Implemented as per the Network Working Group draft on UUIDs and GUIDs
     
         // These two octets get special treatment
index 6614b39..d264083 100644 (file)
@@ -14,7 +14,7 @@
  */
 
 class iCalendar_parameter {
-    function multiple_values_allowed($parameter) {
+    static function multiple_values_allowed($parameter) {
         switch($parameter) {
             case 'DELEGATED-FROM':
             case 'DELEGATED-TO':
@@ -25,7 +25,7 @@ class iCalendar_parameter {
         }
     }
 
-    function default_value($parameter) {
+    static function default_value($parameter) {
         switch($parameter) {
             case 'CUTYPE':   return 'INDIVIDUAL';
             case 'FBTYPE':   return 'BUSY';
@@ -38,7 +38,7 @@ class iCalendar_parameter {
         }
     }
 
-    function is_valid_value(&$parent_property, $parameter, $value) {
+    static function is_valid_value(&$parent_property, $parameter, $value) {
         switch($parameter) {
             // These must all be a URI
             case 'ALTREP':
@@ -191,7 +191,7 @@ class iCalendar_parameter {
         }
     }
 
-    function do_value_formatting($parameter, $value) {
+    static function do_value_formatting($parameter, $value) {
         switch($parameter) {
             // Parameters of type CAL-ADDRESS or URI MUST be double-quoted
             case 'ALTREP':
@@ -232,7 +232,7 @@ class iCalendar_parameter {
         }
     }
 
-    function undo_value_formatting($parameter, $value) {
+    static function undo_value_formatting($parameter, $value) {
     }
 
 }
index 47885c9..9413201 100644 (file)
@@ -454,7 +454,9 @@ class block_manager {
      * @return bool True if all of the blocks within that region are docked
      */
     public function region_completely_docked($region, $output) {
-        if (!$this->page->theme->enable_dock) {
+        global $CFG;
+        // If theme doesn't allow docking or allowblockstodock is not set, then return.
+        if (!$this->page->theme->enable_dock || empty($CFG->allowblockstodock)) {
             return false;
         }
 
index fcd5910..fcc6f58 100644 (file)
@@ -1069,7 +1069,8 @@ class completion_info {
         global $DB;
 
         list($enrolledsql, $params) = get_enrolled_sql(
-                context_course::instance($this->course->id), '', $groupid, true);
+                context_course::instance($this->course->id),
+                'moodle/course:isincompletionreports', $groupid, true);
 
         $sql = 'SELECT u.id, u.firstname, u.lastname, u.idnumber';
         if ($extracontext) {
index 121750c..f2027df 100644 (file)
@@ -979,6 +979,14 @@ $capabilities = array(
         )
     ),
 
+    'moodle/course:isincompletionreports' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'student' => CAP_ALLOW,
+        ),
+    ),
+
     'moodle/course:viewscales' => array(
 
         'captype' => 'read',
index e617e1b..91263db 100644 (file)
@@ -969,5 +969,21 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012062502.03);
     }
 
+    if ($oldversion < 2012062502.07) {
+        // Find all orphaned blog associations that might exist.
+        $sql = "SELECT ba.id
+                  FROM {blog_association} ba
+             LEFT JOIN {post} p
+                    ON p.id = ba.blogid
+                 WHERE p.id IS NULL";
+        $orphanedrecordids = $DB->get_records_sql($sql);
+        // Now delete these associations.
+        foreach ($orphanedrecordids as $orphanedrecord) {
+            $DB->delete_records('blog_association', array('id' => $orphanedrecord->id));
+        }
+
+        upgrade_main_savepoint(true, 2012062502.07);
+    }
+
     return true;
 }
index b6736c4..bb609fc 100644 (file)
@@ -1626,7 +1626,7 @@ abstract class enrol_plugin {
         $participants->close();
 
         // now clean up all remainders that were not removed correctly
-        $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
+        $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$name));
         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
 
         // finally drop the enrol row
index f2105cb..d5ac755 100644 (file)
@@ -201,8 +201,13 @@ class file_browser {
     private function get_file_info_context_module($context, $component, $filearea, $itemid, $filepath, $filename) {
         global $COURSE, $DB, $CFG;
 
+        static $cachedmodules = array();
 
-        if (!$cm = get_coursemodule_from_id('', $context->instanceid)) {
+        if (!array_key_exists($context->instanceid, $cachedmodules)) {
+            $cachedmodules[$context->instanceid] = get_coursemodule_from_id('', $context->instanceid);
+        }
+
+        if (!($cm = $cachedmodules[$context->instanceid])) {
             return null;
         }
 
index d5bcade..a6af3f7 100644 (file)
@@ -88,6 +88,116 @@ abstract class file_info {
      */
     public abstract function get_children();
 
+    /**
+     * Builds SQL sub query (WHERE clause) for selecting files with the specified extensions
+     *
+     * If $extensions == '*' (any file), the result is array('', array())
+     * otherwise the result is something like array('AND filename ...', array(...))
+     *
+     * @param string|array $extensions - either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param string $prefix prefix for DB table files in the query (empty by default)
+     * @return array of two elements: $sql - sql where clause and $params - array of parameters
+     */
+    protected function build_search_files_sql($extensions, $prefix = null) {
+        global $DB;
+        if (strlen($prefix)) {
+            $prefix = $prefix.'.';
+        } else {
+            $prefix = '';
+        }
+        $sql = '';
+        $params = array();
+        if (is_array($extensions) && !in_array('*', $extensions)) {
+            $likes = array();
+            $cnt = 0;
+            foreach ($extensions as $ext) {
+                $cnt++;
+                $likes[] = $DB->sql_like($prefix.'filename', ':filename'.$cnt, false);
+                $params['filename'.$cnt] = '%'.$ext;
+            }
+            $sql .= ' AND (' . join(' OR ', $likes) . ')';
+        }
+        return array($sql, $params);
+     }
+
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * It is recommended to overwrite this function so it uses a proper SQL
+     * query and does not create unnecessary file_info objects (might require a lot of time
+     * and memory usage on big sites).
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        $list = $this->get_children();
+        $nonemptylist = array();
+        foreach ($list as $fileinfo) {
+            if ($fileinfo->is_directory()) {
+                if ($fileinfo->count_non_empty_children($extensions)) {
+                    $nonemptylist[] = $fileinfo;
+                }
+            } else if ($extensions === '*') {
+                $nonemptylist[] = $fileinfo;
+            } else {
+                $filename = $fileinfo->get_visible_name();
+                $extension = textlib::strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+                if (!empty($extension) && in_array('.' . $extension, $extensions)) {
+                    $nonemptylist[] = $fileinfo;
+                }
+            }
+        }
+        return $nonemptylist;
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * We usually don't need the exact number of non empty children if it is >=2 (see param $limit)
+     * This function is used by repository_local to evaluate if the folder is empty. But
+     * it also can be used to check if folder has only one subfolder because in some cases
+     * this subfolder can be skipped.
+     *
+     * It is strongly recommended to overwrite this function so it uses a proper SQL
+     * query and does not create file_info objects (later might require a lot of time
+     * and memory usage on big sites).
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        $list = $this->get_children();
+        $cnt = 0;
+        // first loop through files
+        foreach ($list as $fileinfo) {
+            if (!$fileinfo->is_directory()) {
+                if ($extensions !== '*') {
+                    $filename = $fileinfo->get_visible_name();
+                    $extension = textlib::strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+                    if (empty($extension) || !in_array('.' . $extension, $extensions)) {
+                        continue;
+                    }
+                }
+                if ((++$cnt) >= $limit) {
+                    return $cnt;
+                }
+            }
+        }
+        // now loop through directories
+        foreach ($list as $fileinfo) {
+            if ($fileinfo->is_directory() && $fileinfo->count_non_empty_children($extensions)) {
+                if ((++$cnt) >= $limit) {
+                    return $cnt;
+                }
+            }
+        }
+        return $cnt;
+    }
+
     /**
      * Returns parent file_info instance
      *
@@ -103,12 +213,12 @@ abstract class file_info {
     public function get_params_rawencoded() {
         $params = $this->get_params();
         $encoded = array();
-        $encoded[] = 'contextid='.$params['contextid'];
-        $encoded[] = 'component='.$params['component'];
-        $encoded[] = 'filearea='.$params['filearea'];
-        $encoded[] = 'itemid='.(is_null($params['itemid']) ? -1 : $params['itemid']);
-        $encoded[] = 'filepath='.(is_null($params['filepath']) ? '' : rawurlencode($params['filepath']));
-        $encoded[] = 'filename='.((is_null($params['filename']) or $params['filename'] === '.') ? '' : rawurlencode($params['filename']));
+        $encoded[] = 'contextid=' . $params['contextid'];
+        $encoded[] = 'component=' . $params['component'];
+        $encoded[] = 'filearea=' . $params['filearea'];
+        $encoded[] = 'itemid=' . (is_null($params['itemid']) ? -1 : $params['itemid']);
+        $encoded[] = 'filepath=' . (is_null($params['filepath']) ? '' : rawurlencode($params['filepath']));
+        $encoded[] = 'filename=' . ((is_null($params['filename']) or $params['filename'] === '.') ? '' : rawurlencode($params['filename']));
 
         return $encoded;
     }
index 50876b3..5e61f56 100644 (file)
@@ -350,48 +350,90 @@ class file_info_context_course extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
-        $children = array();
+        return $this->get_filtered_children('*', false, true);
+    }
 
-        if ($child = $this->get_area_course_summary(0, '/', '.')) {
-            $children[] = $child;
-        }
-        if ($child = $this->get_area_course_section(null, null, null)) {
-            $children[] = $child;
-        }
-        if ($child = $this->get_area_backup_section(null, null, null)) {
-            $children[] = $child;
-        }
-        if ($child = $this->get_area_backup_course(0, '/', '.')) {
-            $children[] = $child;
-        }
-        if ($child = $this->get_area_backup_automated(0, '/', '.')) {
-            $children[] = $child;
-        }
-        if ($child = $this->get_area_course_legacy(0, '/', '.')) {
-            $children[] = $child;
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
+        $areas = array(
+            array('course', 'summary'),
+            array('course', 'section'),
+            array('backup', 'section'),
+            array('backup', 'course'),
+            array('backup', 'automated'),
+            array('course', 'legacy')
+        );
+        $children = array();
+        foreach ($areas as $area) {
+            if ($child = $this->get_file_info($area[0], $area[1], 0, '/', '.')) {
+                if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
+                    $children[] = $child;
+                    if (($countonly !== false) && count($children) >= $countonly) {
+                        return $countonly;
+                    }
+                }
+            }
         }
 
         if (!has_capability('moodle/course:managefiles', $this->context)) {
             // 'managefiles' capability is checked in every activity module callback.
             // Don't even waste time on retrieving the modules if we can't browse the files anyway
-            return $children;
-        }
-
-        // now list all modules
-        $modinfo = get_fast_modinfo($this->course);
-        foreach ($modinfo->cms as $cminfo) {
-            if (empty($cminfo->uservisible)) {
-                continue;
-            }
-            $modcontext = get_context_instance(CONTEXT_MODULE, $cminfo->id);
-            if ($child = $this->browser->get_file_info($modcontext)) {
-                $children[] = $child;
+        } else {
+            // now list all modules
+            $modinfo = get_fast_modinfo($this->course);
+            foreach ($modinfo->cms as $cminfo) {
+                if (empty($cminfo->uservisible)) {
+                    continue;
+                }
+                $modcontext = context_module::instance($cminfo->id, IGNORE_MISSING);
+                if ($child = $this->browser->get_file_info($modcontext)) {
+                    if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
+                        $children[] = $child;
+                        if (($countonly !== false) && count($children) >= $countonly) {
+                            return $countonly;
+                        }
+                    }
+                }
             }
         }
 
+        if ($countonly !== false) {
+            return count($children);
+        }
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        return $this->get_filtered_children($extensions, false);
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      *
@@ -473,6 +515,37 @@ class file_info_area_course_legacy extends file_info_stored {
 
         return $result;
     }
+
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        if (!$this->lf->is_directory()) {
+            return array();
+        }
+
+        $result = array();
+        $fs = get_file_storage();
+
+        $storedfiles = $fs->get_directory_files($this->context->id, 'course', 'legacy', 0,
+                                                $this->lf->get_filepath(), false, true, "filepath, filename");
+        foreach ($storedfiles as $file) {
+            $extension = textlib::strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
+            if ($file->is_directory() || $extensions === '*' || (!empty($extension) && in_array('.'.$extension, $extensions))) {
+                $fileinfo = new file_info_area_course_legacy($this->browser, $this->context, $file, $this->urlbase, $this->topvisiblename,
+                                                 $this->itemidused, $this->readaccess, $this->writeaccess, false);
+                if (!$file->is_directory() || $fileinfo->count_non_empty_children($extensions)) {
+                    $result[] = $fileinfo;
+                }
+            }
+        }
+
+        return $result;
+    }
 }
 
 /**
@@ -578,6 +651,41 @@ class file_info_area_course_section extends file_info {
         return $children;
     }
 
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        global $DB;
+        $params1 = array(
+            'courseid' => $this->course->id,
+            'contextid' => $this->context->id,
+            'component' => 'course',
+            'filearea' => 'section',
+            'emptyfilename' => '.');
+        $sql1 = "SELECT DISTINCT cs.id FROM {files} f, {course_sections} cs
+            WHERE cs.course = :courseid
+            AND f.contextid = :contextid
+            AND f.component = :component
+            AND f.filearea = :filearea
+            AND f.itemid = cs.id
+            AND f.filename <> :emptyfilename";
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $rs = $DB->get_recordset_sql($sql1. ' '. $sql2, array_merge($params1, $params2));
+        $cnt = 0;
+        foreach ($rs as $record) {
+            if ((++$cnt) >= $limit) {
+                break;
+            }
+        }
+        $rs->close();
+        return $cnt;
+    }
+
     /**
      * Returns parent file_info instance
      *
@@ -689,6 +797,41 @@ class file_info_area_backup_section extends file_info {
         return $children;
     }
 
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        global $DB;
+        $params1 = array(
+            'courseid' => $this->course->id,
+            'contextid' => $this->context->id,
+            'component' => 'backup',
+            'filearea' => 'section',
+            'emptyfilename' => '.');
+        $sql1 = "SELECT DISTINCT cs.id sectionid FROM {files} f, {course_sections} cs
+            WHERE cs.course = :courseid
+            AND f.contextid = :contextid
+            AND f.component = :component
+            AND f.filearea = :filearea
+            AND f.itemid = cs.id
+            AND f.filename <> :emptyfilename";
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $rs = $DB->get_recordset_sql($sql1. ' '. $sql2, array_merge($params1, $params2));
+        $cnt = 0;
+        foreach ($rs as $record) {
+            if ((++$cnt) >= $limit) {
+                break;
+            }
+        }
+        $rs->close();
+        return $cnt;
+    }
+
     /**
      * Returns parent file_info instance
      *
index 99b6d1f..e8d542c 100644 (file)
@@ -188,6 +188,66 @@ class file_info_context_coursecat extends file_info {
         return $children;
     }
 
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        global $DB;
+        $cnt = 0;
+        if (($child = $this->get_area_coursecat_description(0, '/', '.'))
+                && $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) {
+            return $cnt;
+        }
+
+        $rs = $DB->get_recordset_sql('SELECT ctx.id contextid, c.visible
+                FROM {context} ctx, {course} c
+                WHERE ctx.instanceid = c.id
+                AND ctx.contextlevel = :courselevel
+                AND c.category = :categoryid
+                ORDER BY c.visible DESC', // retrieve visible courses first
+                array('categoryid' => $this->category->id, 'courselevel' => CONTEXT_COURSE));
+        foreach ($rs as $record) {
+            $context = context::instance_by_id($record->contextid);
+            if (!$record->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
+                continue;
+            }
+            if (($child = $this->browser->get_file_info($context))
+                    && $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) {
+                break;
+            }
+        }
+        $rs->close();
+        if ($cnt >= $limit) {
+            return $cnt;
+        }
+
+        $rs = $DB->get_recordset_sql('SELECT ctx.id contextid, cat.visible
+                FROM {context} ctx, {course_categories} cat
+                WHERE ctx.instanceid = cat.id
+                AND ctx.contextlevel = :catlevel
+                AND cat.parent = :categoryid
+                ORDER BY cat.visible DESC', // retrieve visible categories first
+                array('categoryid' => $this->category->id, 'catlevel' => CONTEXT_COURSECAT));
+        foreach ($rs as $record) {
+            $context = context::instance_by_id($record->contextid);
+            if (!$record->visible and !has_capability('moodle/category:viewhiddencategories', $context)) {
+                continue;
+            }
+            if (($child = $this->browser->get_file_info($context))
+                    && $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) {
+                break;
+            }
+        }
+        $rs->close();
+
+        return $cnt;
+    }
+
     /**
      * Returns parent file_info instance
      *
index 0357ee3..f4b2e52 100644 (file)
@@ -41,6 +41,8 @@ class file_info_context_module extends file_info {
     protected $modname;
     /** @var array Available file areas */
     protected $areas;
+    /** @var array caches the result of last call to get_non_empty_children() */
+    protected $nonemptychildren;
 
     /**
      * Constructor
@@ -58,6 +60,7 @@ class file_info_context_module extends file_info {
         $this->course  = $course;
         $this->cm      = $cm;
         $this->modname = $modname;
+        $this->nonemptychildren = null;
 
         include_once("$CFG->dirroot/mod/$modname/lib.php");
 
@@ -258,24 +261,91 @@ class file_info_context_module extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
-        $children = array();
+        return $this->get_filtered_children('*', false, true);
+    }
 
-        if ($child = $this->get_area_backup(0, '/', '.')) {
-            $children[] = $child;
-        }
-        if ($child = $this->get_area_intro(0, '/', '.')) {
-            $children[] = $child;
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
+        global $DB;
+        // prepare list of areas including intro and backup
+        $areas = array(
+            array('mod_'.$this->modname, 'intro'),
+            array('backup', 'activity')
+        );
+        foreach ($this->areas as $area => $desctiption) {
+            $areas[] = array('mod_'.$this->modname, $area);
         }
 
-        foreach ($this->areas as $area=>$desctiption) {
-            if ($child = $this->get_file_info('mod_'.$this->modname, $area, null, null, null)) {
-                $children[] = $child;
+        $params1 = array('contextid' => $this->context->id, 'emptyfilename' => '.');
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $children = array();
+        foreach ($areas as $area) {
+            if (!$returnemptyfolders) {
+                // fast pre-check if there are any files in the filearea
+                $params1['component'] = $area[0];
+                $params1['filearea'] = $area[1];
+                if (!$DB->record_exists_sql('SELECT 1 from {files}
+                        WHERE contextid = :contextid
+                        AND filename <> :emptyfilename
+                        AND component = :component
+                        AND filearea = :filearea '.$sql2,
+                        array_merge($params1, $params2))) {
+                    continue;
+                }
+            }
+            if ($child = $this->get_file_info($area[0], $area[1], null, null, null)) {
+                if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
+                    $children[] = $child;
+                    if ($countonly !== false && count($children) >= $countonly) {
+                        break;
+                    }
+                }
             }
         }
-
+        if ($countonly !== false) {
+            return count($children);
+        }
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        if ($this->nonemptychildren !== null) {
+            return $this->nonemptychildren;
+        }
+        $this->nonemptychildren = $this->get_filtered_children($extensions);
+        return $this->nonemptychildren;
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        if ($this->nonemptychildren !== null) {
+            return count($this->nonemptychildren);
+        }
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      *
index 44fa566..e9caf4c 100644 (file)
@@ -350,6 +350,84 @@ class file_info_stored extends file_info {
         return $result;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        $result = array();
+        if (!$this->lf->is_directory()) {
+            return $result;
+        }
+
+        $fs = get_file_storage();
+
+        $storedfiles = $fs->get_directory_files($this->context->id, $this->lf->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid(),
+                                                $this->lf->get_filepath(), false, true, "filepath, filename");
+        foreach ($storedfiles as $file) {
+            $extension = textlib::strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
+            if ($file->is_directory() || $extensions === '*' || (!empty($extension) && in_array('.'.$extension, $extensions))) {
+                $fileinfo = new file_info_stored($this->browser, $this->context, $file, $this->urlbase, $this->topvisiblename,
+                                                 $this->itemidused, $this->readaccess, $this->writeaccess, false);
+                if (!$file->is_directory() || $fileinfo->count_non_empty_children($extensions)) {
+                    $result[] = $fileinfo;
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        global $DB;
+        if (!$this->lf->is_directory()) {
+            return 0;
+        }
+
+        $filepath = $this->lf->get_filepath();
+        $length = textlib::strlen($filepath);
+        $sql = "SELECT filepath, filename
+                  FROM {files} f
+                 WHERE f.contextid = :contextid AND f.component = :component AND f.filearea = :filearea AND f.itemid = :itemid
+                       AND ".$DB->sql_substr("f.filepath", 1, $length)." = :filepath
+                       AND filename <> '.' ";
+        $params = array('contextid' => $this->context->id,
+            'component' => $this->lf->get_component(),
+            'filearea' => $this->lf->get_filearea(),
+            'itemid' => $this->lf->get_itemid(),
+            'filepath' => $filepath);
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $rs = $DB->get_recordset_sql($sql.' '.$sql2, array_merge($params, $params2));
+        $children = array();
+        foreach ($rs as $record) {
+            // we don't need to check access to individual files here, since the user can access parent
+            if ($record->filepath === $filepath) {
+                $children[] = $record->filename;
+            } else {
+                $path = explode('/', textlib::substr($record->filepath, $length));
+                if (!in_array($path[0], $children)) {
+                    $children[] = $path[0];
+                }
+            }
+            if (count($children) >= $limit) {
+                break;
+            }
+        }
+        $rs->close();
+        return count($children);
+    }
+
     /**
      * Returns parent file_info instance
      *
index 7c2087e..465ba24 100644 (file)
@@ -1404,6 +1404,7 @@ function &get_mimetypes_array() {
         'dir'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
         'dxr'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
         'eps'  => array ('type'=>'application/postscript', 'icon'=>'eps'),
+        'epub' => array ('type'=>'application/epub+zip', 'icon'=>'epub', 'groups'=>array('document')),
         'fdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
         'flv'  => array ('type'=>'video/x-flv', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
         'f4v'  => array ('type'=>'video/mp4', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
@@ -1817,10 +1818,14 @@ function get_mimetype_description($obj, $capitalise=false) {
         $a[strtoupper($key)] = strtoupper($value);
         $a[ucfirst($key)] = ucfirst($value);
     }
-    if (get_string_manager()->string_exists($mimetype, 'mimetypes')) {
-        $result = get_string($mimetype, 'mimetypes', (object)$a);
-    } else if (get_string_manager()->string_exists($mimetypestr, 'mimetypes')) {
-        $result = get_string($mimetypestr, 'mimetypes', (object)$a);
+
+    // MIME types may include + symbol but this is not permitted in string ids.
+    $safemimetype = str_replace('+', '_', $mimetype);
+    $safemimetypestr = str_replace('+', '_', $mimetypestr);
+    if (get_string_manager()->string_exists($safemimetype, 'mimetypes')) {
+        $result = get_string($safemimetype, 'mimetypes', (object)$a);
+    } else if (get_string_manager()->string_exists($safemimetypestr, 'mimetypes')) {
+        $result = get_string($safemimetypestr, 'mimetypes', (object)$a);
     } else if (get_string_manager()->string_exists('default', 'mimetypes')) {
         $result = get_string('default', 'mimetypes', (object)$a);
     } else {
index 1109b22..9414db0 100644 (file)
@@ -220,7 +220,7 @@ YUI.add('moodle-form-dateselector', function(Y) {
                     config.september,
                     config.october,
                     config.november,
-                    config.december ],
+                    config.december ]
             });
             this.calendar.changePageEvent.subscribe(function(){
                 this.fix_position();
index 23f65cb..866ad43 100644 (file)
@@ -81,25 +81,25 @@ function form_init_date_js() {
         $function = 'M.form.dateselector.init_date_selectors';
         $config = array(array(
             'firstdayofweek'    => get_string('firstdayofweek', 'langconfig'),
-            'mon'               => strftime('%a', strtotime("Monday")),
-            'tue'               => strftime('%a', strtotime("Tuesday")),
-            'wed'               => strftime('%a', strtotime("Wednesday")),
-            'thu'               => strftime('%a', strtotime("Thursday")),
-            'fri'               => strftime('%a', strtotime("Friday")),
-            'sat'               => strftime('%a', strtotime("Saturday")),
-            'sun'               => strftime('%a', strtotime("Sunday")),
-            'january'           => strftime('%B', strtotime("January 1")),
-            'february'          => strftime('%B', strtotime("February 1")),
-            'march'             => strftime('%B', strtotime("March 1")),
-            'april'             => strftime('%B', strtotime("April 1")),
-            'may'               => strftime('%B', strtotime("May 1")),
-            'june'              => strftime('%B', strtotime("June 1")),
-            'july'              => strftime('%B', strtotime("July 1")),
-            'august'            => strftime('%B', strtotime("August 1")),
-            'september'         => strftime('%B', strtotime("September 1")),
-            'october'           => strftime('%B', strtotime("October 1")),
-            'november'          => strftime('%B', strtotime("November 1")),
-            'december'          => strftime('%B', strtotime("December 1"))
+            'mon'               => date_format_string(strtotime("Monday"), '%a', 99),
+            'tue'               => date_format_string(strtotime("Tuesday"), '%a', 99),
+            'wed'               => date_format_string(strtotime("Wednesday"), '%a', 99),
+            'thu'               => date_format_string(strtotime("Thursday"), '%a', 99),
+            'fri'               => date_format_string(strtotime("Friday"), '%a', 99),
+            'sat'               => date_format_string(strtotime("Saturday"), '%a', 99),
+            'sun'               => date_format_string(strtotime("Sunday"), '%a', 99),
+            'january'           => date_format_string(strtotime("January 1"), '%B', 99),
+            'february'          => date_format_string(strtotime("February 1"), '%B', 99),
+            'march'             => date_format_string(strtotime("March 1"), '%B', 99),
+            'april'             => date_format_string(strtotime("April 1"), '%B', 99),
+            'may'               => date_format_string(strtotime("May 1"), '%B', 99),
+            'june'              => date_format_string(strtotime("June 1"), '%B', 99),
+            'july'              => date_format_string(strtotime("July 1"), '%B', 99),
+            'august'            => date_format_string(strtotime("August 1"), '%B', 99),
+            'september'         => date_format_string(strtotime("September 1"), '%B', 99),
+            'october'           => date_format_string(strtotime("October 1"), '%B', 99),
+            'november'          => date_format_string(strtotime("November 1"), '%B', 99),
+            'december'          => date_format_string(strtotime("December 1"), '%B', 99)
         ));
         $PAGE->requires->yui_module($module, $function, $config);
         $done = true;
@@ -1279,6 +1279,9 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
     /** @var bool Whether to display advanced elements (on page load) */
     var $_showAdvanced = null;
 
+    /** @var bool whether to automatically initialise M.formchangechecker for this form. */
+    protected $_use_form_change_checker = true;
+
     /**
      * The form name is derived from the class name of the wrapper minus the trailing form
      * It is a name with words joined by underscores whereas the id attribute is words joined by underscores.
@@ -1406,8 +1409,32 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
         return $this->_showAdvanced;
     }
 
+    /**
+     * Call this method if you don't want the formchangechecker JavaScript to be
+     * automatically initialised for this form.
+     */
+    public function disable_form_change_checker() {
+        $this->_use_form_change_checker = false;
+    }
 
-   /**
+    /**
+     * If you have called {@link disable_form_change_checker()} then you can use
+     * this method to re-enable it. It is enabled by default, so normally you don't
+     * need to call this.
+     */
+    public function enable_form_change_checker() {
+        $this->_use_form_change_checker = true;
+    }
+
+    /**
+     * @return bool whether this form should automatically initialise
+     *      formchangechecker for itself.
+     */
+    public function is_form_change_checker_enabled() {
+        return $this->_use_form_change_checker;
+    }
+
+    /**
     * Accepts a renderer
     *
     * @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
@@ -2312,13 +2339,15 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
             $this->_hiddenHtml .= $form->_pageparams;
         }
 
-        $PAGE->requires->yui_module('moodle-core-formchangechecker',
-                'M.core_formchangechecker.init',
-                array(array(
-                    'formid' => $form->getAttribute('id')
-                ))
-        );
-        $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
+        if ($form->is_form_change_checker_enabled()) {
+            $PAGE->requires->yui_module('moodle-core-formchangechecker',
+                    'M.core_formchangechecker.init',
+                    array(array(
+                        'formid' => $form->getAttribute('id')
+                    ))
+            );
+            $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
+        }
     }
 
     /**
index 95b99f8..3fa19ec 100644 (file)
@@ -911,7 +911,10 @@ class grade_category extends grade_object {
 
                 $i = 1;
                 while ($originalindex+$i < count($grade_keys)) {
+
                     $possibleitemid = $grade_keys[$originalindex+$i];
+                    $i++;
+
                     if ($grade_values[$founditemid] != $grade_values[$possibleitemid]) {
                         // The next grade item has a different grade. Stop looking.
                         break;
@@ -928,8 +931,6 @@ class grade_category extends grade_object {
                         $founditemid = $possibleitemid;
                         // Continue searching to see if there is an even higher grademax...
                     }
-
-                    $i++;
                 }
 
                 // Now drop whatever grade item we have found
index 727e698..e4281a0 100644 (file)
@@ -516,6 +516,21 @@ class grade_category_testcase extends grade_base_testcase {
         $this->assertEquals(count($grades), 1);
         $this->assertEquals($grades[$this->grade_items[2]->id], 6);
 
+        // MDL-35667 - There was an infinite loop if several items had the same grade and at least one was extra credit
+        $category = new grade_category();
+        $category->droplow     = 1;
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // simple weighted mean
+        $items[$this->grade_items[1]->id]->aggregationcoef = 1; // Mark grade item 1 as "extra credit"
+        $grades = array($this->grade_items[0]->id=>1, // 1 out of 110. Should be excluded from aggregation.
+                        $this->grade_items[1]->id=>1, // 1 out of 100. Extra credit. Should be retained.
+                        $this->grade_items[2]->id=>1, // 1 out of 6. Should be retained.
+                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be retained.
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 3);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 1);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 1);
+        $this->assertEquals($grades[$this->grade_items[4]->id], 1);
+
     }
 
     /**
index 4189c37..8c274cd 100644 (file)
@@ -1374,6 +1374,42 @@ function hide_item(itemid) {
     }
 }
 
+M.util.help_popups = {
+    setup : function(Y) {
+        Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
+    },
+    open_popup : function(e) {
+        // Prevent the default page action
+        e.preventDefault();
+
+        // Grab the anchor that was clicked
+        var anchor = e.target.ancestor('a', true);
+        var args = {
+            'name'          : 'popup',
+            'url'           : anchor.getAttribute('href'),
+            'options'       : ''
+        };
+        var options = [
+            'height=600',
+            'width=800',
+            'top=0',
+            'left=0',
+            'menubar=0',
+            'location=0',
+            'scrollbars',
+            'resizable',
+            'toolbar',
+            'status',
+            'directories=0',
+            'fullscreen=0',
+            'dependent'
+        ]
+        args.options = options.join(',');
+
+        openpopup(e, args);
+    }
+}
+
 M.util.help_icon = {
     Y : null,
     instance : null,
@@ -1388,16 +1424,17 @@ M.util.help_icon = {
         event.preventDefault();
         if (M.util.help_icon.instance === null) {
             var Y = M.util.help_icon.Y;
-            Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', function(Y) {
+            Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
                 var help_content_overlay = {
                     helplink : null,
                     overlay : null,
                     init : function() {
 
-                        var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
+                        var strclose = Y.Escape.html(M.str.form.close);
+                        var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
                         // Create an overlay from markup
                         this.overlay = new Y.Overlay({
-                            headerContent: closebtn,
+                            footerContent: footerbtn,
                             bodyContent: '',
                             id: 'helppopupbox',
                             width:'400px',
@@ -1406,7 +1443,7 @@ M.util.help_icon = {
                         });
                         this.overlay.render(Y.one(document.body));
 
-                        closebtn.on('click', this.overlay.hide, this.overlay);
+                        footerbtn.on('click', this.overlay.hide, this.overlay);
 
                         var boundingBox = this.overlay.get("boundingBox");
 
@@ -1422,9 +1459,6 @@ M.util.help_icon = {
                                 this.overlay.hide();
                             }
                         }, this);
-
-                        Y.on("key", this.close, closebtn , "down:13", this);
-                        closebtn.on('click', this.close, this);
                     },
 
                     close : function(e) {
@@ -1434,9 +1468,16 @@ M.util.help_icon = {
                     },
 
                     display : function(event, args) {
+                        if (Y.one('html').get('dir') == 'rtl') {
+                            var overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
+                        } else {
+                            var overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
+                        }
+
                         this.helplink = args.node;
+
                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
-                        this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
+                        this.overlay.set("align", {node:args.node, points: overlayPosition});
 
                         var fullurl = args.url;
                         if (!args.url.match(/https?:\/\//)) {
@@ -1464,8 +1505,6 @@ M.util.help_icon = {
 
                         Y.io(ajaxurl, cfg);
                         this.overlay.show();
-
-                        Y.one('#closehelpbox').focus();
                     },
 
                     display_callback : function(content) {
index 167a653..32697fa 100644 (file)
@@ -2071,13 +2071,7 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
     // (because it's impossible to specify UTF-8 to fetch locale info in Win32)
 
     if (abs($timezone) > 13) {   /// Server time
-        if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
-            $format = textlib::convert($format, 'utf-8', $localewincharset);
-            $datestring = strftime($format, $date);
-            $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
-        } else {
-            $datestring = strftime($format, $date);
-        }
+        $datestring = date_format_string($date, $format, $timezone);
         if ($fixday) {
             $daystring  = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
             $datestring = str_replace('DD', $daystring, $datestring);
@@ -2089,13 +2083,7 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
 
     } else {
         $date += (int)($timezone * 3600);
-        if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
-            $format = textlib::convert($format, 'utf-8', $localewincharset);
-            $datestring = gmstrftime($format, $date);
-            $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
-        } else {
-            $datestring = gmstrftime($format, $date);
-        }
+        $datestring = date_format_string($date, $format, $timezone);
         if ($fixday) {
             $daystring  = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
             $datestring = str_replace('DD', $daystring, $datestring);
@@ -2109,6 +2097,46 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
     return $datestring;
 }
 
+/**
+ * Returns a formatted date ensuring it is UTF-8.
+ *
+ * If we are running under Windows convert to Windows encoding and then back to UTF-8
+ * (because it's impossible to specify UTF-8 to fetch locale info in Win32).
+ *
+ * This function does not do any calculation regarding the user preferences and should
+ * therefore receive the final date timestamp, format and timezone. Timezone being only used
+ * to differenciate the use of server time or not (strftime() against gmstrftime()).
+ *
+ * @param int $date the timestamp.
+ * @param string $format strftime format.
+ * @param int|float $timezone the numerical timezone, typically returned by {@link get_user_timezone_offset()}.
+ * @return string the formatted date/time.
+ * @since 2.3.3
+ */
+function date_format_string($date, $format, $tz = 99) {
+    global $CFG;
+    if (abs($tz) > 13) {
+        if ($CFG->ostype == 'WINDOWS') {
+            $localewincharset = get_string('localewincharset', 'langconfig');
+            $format = textlib::convert($format, 'utf-8', $localewincharset);
+            $datestring = strftime($format, $date);
+            $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
+        } else {
+            $datestring = strftime($format, $date);
+        }
+    } else {
+        if ($CFG->ostype == 'WINDOWS') {
+            $localewincharset = get_string('localewincharset', 'langconfig');
+            $format = textlib::convert($format, 'utf-8', $localewincharset);
+            $datestring = gmstrftime($format, $date);
+            $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
+        } else {
+            $datestring = gmstrftime($format, $date);
+        }
+    }
+    return $datestring;
+}
+
 /**
  * Given a $time timestamp in GMT (seconds since epoch),
  * returns an array that represents the date in user time
@@ -8393,18 +8421,45 @@ function check_php_version($version='5.2.4') {
 
 
       case 'Gecko':   /// Gecko based browsers
-          if (empty($version) and substr_count($agent, 'Camino')) {
-              // MacOS X Camino support
-              $version = 20041110;
+          // Do not look for dates any more, we expect real Firefox version here.
+          if (empty($version)) {
+              $version = 1;
+          } else if ($version > 20000000) {
+              // This is just a guess, it is not supposed to be 100% accurate!
+              if (preg_match('/^201/', $version)) {
+                  $version = 3.6;
+              } else if (preg_match('/^200[7-9]/', $version)) {
+                  $version = 3;
+              } else if (preg_match('/^2006/', $version)) {
+                  $version = 2;
+              } else {
+                  $version = 1.5;
+              }
           }
-
-          // the proper string - Gecko/CCYYMMDD Vendor/Version
-          // Faster version and work-a-round No IDN problem.
-          if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
-              if ($match[1] > $version) {
-                      return true;
+          if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
+              // Use real Firefox version if specified in user agent string.
+              if (version_compare($match[2], $version) >= 0) {
+                  return true;
+              }
+          } else if (preg_match("/Gecko\/([0-9\.]+)/i", $agent, $match)) {
+              // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date.
+              $browserver = $match[1];
+              if ($browserver > 20000000) {
+                  // This is just a guess, it is not supposed to be 100% accurate!
+                  if (preg_match('/^201/', $browserver)) {
+                      $browserver = 3.6;
+                  } else if (preg_match('/^200[7-9]/', $browserver)) {
+                      $browserver = 3;
+                  } else if (preg_match('/^2006/', $version)) {
+                      $browserver = 2;
+                  } else {
+                      $browserver = 1.5;
                   }
               }
+              if (version_compare($browserver, $version) >= 0) {
+                  return true;
+              }
+          }
           break;
 
 
index b24c112..b95aae6 100644 (file)
@@ -1197,7 +1197,7 @@ class global_navigation extends navigation_node {
                 break;
             case CONTEXT_COURSECAT :
                 // This has already been loaded we just need to map the variable
-                if ($showcategories) {
+                if ($this->show_categories()) {
                     $this->load_all_categories($this->page->context->instanceid, true);
                 }
                 break;
@@ -3561,7 +3561,7 @@ class settings_navigation extends navigation_node {
                 $baseurl->param('sesskey', sesskey());
             } else {
                 // Edit on the main course page.
-                $baseurl = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
+                $baseurl = new moodle_url('/course/view.php', array('id'=>$course->id, 'return'=>$this->page->url->out_as_local_url(false), 'sesskey'=>sesskey()));
             }
 
             $editurl = clone($baseurl);
index b263027..1a0df04 100644 (file)
@@ -372,6 +372,9 @@ class core_renderer extends renderer_base {
         // flow player embedding support
         $this->page->requires->js_function_call('M.util.load_flowplayer');
 
+        // Set up help link popups for all links with the helplinkpopup class
+        $this->page->requires->js_init_call('M.util.help_popups.setup');
+
         $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
 
         $focus = $this->page->focuscontrol;
@@ -1495,9 +1498,10 @@ class core_renderer extends renderer_base {
      *
      * @param string $path The page link after doc root and language, no leading slash.
      * @param string $text The text to be displayed for the link
+     * @param boolean $forcepopup Whether to force a popup regardless of the value of $CFG->doctonewwindow
      * @return string
      */
-    public function doc_link($path, $text = '') {
+    public function doc_link($path, $text = '', $forcepopup = false) {
         global $CFG;
 
         $icon = $this->pix_icon('docs', $text, 'moodle', array('class'=>'iconhelp'));
@@ -1505,8 +1509,8 @@ class core_renderer extends renderer_base {
         $url = new moodle_url(get_docs_url($path));
 
         $attributes = array('href'=>$url);
-        if (!empty($CFG->doctonewwindow)) {
-            $attributes['id'] = $this->add_action_handler(new popup_action('click', $url));
+        if (!empty($CFG->doctonewwindow) || $forcepopup) {
+            $attributes['class'] = 'helplinkpopup';
         }
 
         return html_writer::tag('a', $icon.$text, $attributes);
@@ -1739,6 +1743,7 @@ class core_renderer extends renderer_base {
         $output = html_writer::tag('a', $output, $attributes);
 
         $this->page->requires->js_init_call('M.util.help_icon.add', array(array('id'=>$id, 'url'=>$url->out(false))));
+        $this->page->requires->string_for_js('close', 'form');
 
         // and finally span
         return html_writer::tag('span', $output, array('class' => 'helplink'));
@@ -1804,6 +1809,7 @@ class core_renderer extends renderer_base {
         $output = html_writer::tag('a', $output, $attributes);
 
         $this->page->requires->js_init_call('M.util.help_icon.add', array(array('id'=>$id, 'url'=>$url->out(false))));
+        $this->page->requires->string_for_js('close', 'form');
 
         // and finally span
         return html_writer::tag('span', $output, array('class' => 'helplink'));
index 8225f23..8bfd798 100644 (file)
@@ -156,9 +156,11 @@ EOD;
         }
 
         if (!isset($record['username'])) {
-            $record['username'] = textlib::strtolower($record['firstname']).textlib::strtolower($record['lastname']);
+            $record['username'] = 'username'.$i;
+            $j = 2;
             while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
-                $record['username'] = $record['username'].'_'.$i;
+                $record['username'] = 'username'.$i.'_'.$j;
+                $j++;
             }
         }
 
index b580259..f9dc68d 100644 (file)
@@ -544,6 +544,9 @@ class phpunit_util {
     public static function reset_all_data($logchanges = false) {
         global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION, $GROUPLIB_CACHE;
 
+        // Release memory and indirectly call destroy() methods to release resource handles, etc.
+        gc_collect_cycles();
+
         // reset global $DB in case somebody mocked it
         $DB = self::get_global_backup('DB');
 
@@ -618,6 +621,10 @@ class phpunit_util {
         $GROUPLIB_CACHE = null;
         //TODO MDL-25290: add more resets here and probably refactor them to new core function
 
+        // Reset course and module caches.
+        $reset = 'reset';
+        get_fast_modinfo($reset);
+
         // purge dataroot directory
         self::reset_dataroot();
 
index 316ee70..3787b34 100644 (file)
@@ -44,6 +44,11 @@ class core_phpunit_generator_testcase extends advanced_testcase {
         $count = $DB->count_records('user');
         $user = $generator->create_user();
         $this->assertEquals($count+1, $DB->count_records('user'));
+        $this->assertSame($user->username, clean_param($user->username, PARAM_USERNAME));
+        $this->assertSame($user->email, clean_param($user->email, PARAM_EMAIL));
+        $user = $generator->create_user(array('firstname'=>'Žluťoučký', 'lastname'=>'Koníček'));
+        $this->assertSame($user->username, clean_param($user->username, PARAM_USERNAME));
+        $this->assertSame($user->email, clean_param($user->email, PARAM_EMAIL));
 
         $count = $DB->count_records('course_categories');
         $category = $generator->create_category();
index b224d6d..470fa88 100644 (file)
@@ -2514,12 +2514,4 @@ class plugininfo_local extends plugininfo_base {
     public function get_uninstall_url() {
         return new moodle_url('/admin/localplugins.php', array('delete' => $this->name, 'sesskey' => sesskey()));
     }
-
-    public function get_settings_url() {
-        if (file_exists($this->full_path('settings.php'))) {
-            return new moodle_url('/admin/settings.php', array('section' => 'local_' . $this->name));
-        } else {
-            return parent::get_settings_url();
-        }
-    }
 }
index 6de22af..2a08616 100644 (file)
@@ -727,16 +727,19 @@ class flexible_table {
     function col_fullname($row) {
         global $COURSE, $CFG;
 
-        if (!$this->download) {
-            $profileurl = new moodle_url('/user/profile.php', array('id' => $row->{$this->useridfield}));
-            if ($COURSE->id != SITEID) {
-                $profileurl->param('course', $COURSE->id);
-            }
-            return html_writer::link($profileurl, fullname($row));
+        $name = fullname($row);
+        if ($this->download) {
+            return $name;
+        }
 
+        $userid = $row->{$this->useridfield};
+        if ($COURSE->id == SITEID) {
+            $profileurl = new moodle_url('/user/profile.php', array('id' => $userid));
         } else {
-            return fullname($row);
+            $profileurl = new moodle_url('/user/view.php',
+                    array('id' => $userid, 'course' => $COURSE->id));
         }
+        return html_writer::link($profileurl, $name);
     }
 
     /**
index 6beb856..c26bab7 100644 (file)
@@ -49,8 +49,17 @@ class moodlelib_testcase extends advanced_testcase {
             '1.5'     => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.8) Gecko/20051107 Firefox/1.5'),
             '1.5.0.1' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1'),
             '2.0'     => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1',
-                'Ubuntu Linux AMD64' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1) Gecko/20060601 Firefox/2.0 (Ubuntu-edgy)'),
-            '3.0.6' => array('SUSE' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.6) Gecko/2009012700 SUSE/3.0.6-1.4 Firefox/3.0.6'),
+                               'Ubuntu Linux AMD64' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1) Gecko/20060601 Firefox/2.0 (Ubuntu-edgy)'),
+            '3.0.6'   => array('SUSE' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.6) Gecko/2009012700 SUSE/3.0.6-1.4 Firefox/3.0.6'),
+            '3.6'     => array('Linux' => 'Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/3.6'),
+            '11.0'    => array('Windows' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko Firefox/11.0'),
+            '15.0a2'  => array('Windows' => 'Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2'),
+            '18.0'    => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:18.0) Gecko/18.0 Firefox/18.0'),
+        ),
+        'SeaMonkey' => array(
+            '2.0' => array('Windows' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1b3pre) Gecko/20081208 SeaMonkey/2.0'),
+            '2.1' => array('Linux' => 'Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20110609 Firefox/4.0.1 SeaMonkey/2.1'),
+            '2.3' => array('FreeBSD' => 'Mozilla/5.0 (X11; FreeBSD amd64; rv:6.0) Gecko/20110818 Firefox/6.0 SeaMonkey/2.3'),
         ),
         'Safari' => array(
             '312' => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312'),
@@ -248,6 +257,113 @@ class moodlelib_testcase extends advanced_testcase {
         $this->assertTrue(check_browser_version('Firefox'));
         $this->assertTrue(check_browser_version('Firefox', '1.5'));
         $this->assertFalse(check_browser_version('Firefox', '3.0'));
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['1.0.6']['Windows XP'];
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Gecko', '1'));
+        $this->assertFalse(check_browser_version('Gecko', 20030516));
+        $this->assertFalse(check_browser_version('Gecko', 20051106));
+        $this->assertFalse(check_browser_version('Gecko', 2006010100));
+        $this->assertFalse(check_browser_version('Firefox', '1.5'));
+        $this->assertFalse(check_browser_version('Firefox', '3.0'));
+        $this->assertFalse(check_browser_version('Gecko', '2'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP'];
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Firefox', '1.5'));
+        $this->assertTrue(check_browser_version('Gecko', '1'));
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertFalse(check_browser_version('Firefox', '3.0'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['3.6']['Linux'];
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Firefox', '1.5'));
+        $this->assertTrue(check_browser_version('Firefox', '3.0'));
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', '3.6'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertFalse(check_browser_version('Firefox', '4'));
+        $this->assertFalse(check_browser_version('Firefox', '10'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['3.6']['Linux'];
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Firefox', '1.5'));
+        $this->assertTrue(check_browser_version('Firefox', '3.0'));
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', '3.6'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertFalse(check_browser_version('Firefox', '4'));
+        $this->assertFalse(check_browser_version('Firefox', '10'));
+        $this->assertFalse(check_browser_version('Firefox', '18'));
+        $this->assertFalse(check_browser_version('Gecko', '4'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['15.0a2']['Windows'];
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Firefox', '1.5'));
+        $this->assertTrue(check_browser_version('Firefox', '3.0'));
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', '3.6'));
+        $this->assertTrue(check_browser_version('Gecko', '15.0'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertTrue(check_browser_version('Firefox', '4'));
+        $this->assertTrue(check_browser_version('Firefox', '10'));
+        $this->assertTrue(check_browser_version('Firefox', '15'));
+        $this->assertFalse(check_browser_version('Firefox', '18'));
+        $this->assertFalse(check_browser_version('Gecko', '18'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['18.0']['Mac OS X'];
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Firefox', '1.5'));
+        $this->assertTrue(check_browser_version('Firefox', '3.0'));
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', '3.6'));
+        $this->assertTrue(check_browser_version('Gecko', '15.0'));
+        $this->assertTrue(check_browser_version('Gecko', '18.0'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertTrue(check_browser_version('Firefox', '4'));
+        $this->assertTrue(check_browser_version('Firefox', '10'));
+        $this->assertTrue(check_browser_version('Firefox', '15'));
+        $this->assertTrue(check_browser_version('Firefox', '18'));
+        $this->assertFalse(check_browser_version('Firefox', '19'));
+        $this->assertFalse(check_browser_version('Gecko', '19'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['SeaMonkey']['2.0']['Windows'];
+
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertFalse(check_browser_version('Gecko', '3.6'));
+        $this->assertFalse(check_browser_version('Gecko', '4.0'));
+        $this->assertFalse(check_browser_version('Firefox'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['SeaMonkey']['2.1']['Linux'];
+        $this->assertTrue(check_browser_version('Gecko', '2'));
+        $this->assertTrue(check_browser_version('Gecko', '3.6'));
+        $this->assertTrue(check_browser_version('Gecko', '4.0'));
+        $this->assertTrue(check_browser_version('Gecko', 20030516));
+        $this->assertTrue(check_browser_version('Gecko', 20051106));
+        $this->assertTrue(check_browser_version('Gecko', 2006010100));
+        $this->assertTrue(check_browser_version('Firefox'));
+        $this->assertTrue(check_browser_version('Firefox', 4.0));
+        $this->assertFalse(check_browser_version('Firefox', 5));
+        $this->assertFalse(check_browser_version('Gecko', '18.0'));
+
     }
 
     function test_get_browser_version_classes() {
@@ -1993,4 +2109,61 @@ class moodlelib_testcase extends advanced_testcase {
         );
         $this->assertEquals(convert_to_array($obj), $ar);
     }
+
+    /**
+     * Test the function date_format_string().
+     */
+    function test_date_format_string() {
+        // Forcing locale and timezone.
+        $oldlocale = setlocale(LC_TIME, '0');
+        setlocale(LC_TIME, 'en_AU.UTF-8');
+        $systemdefaulttimezone = date_default_timezone_get();
+        date_default_timezone_set('Australia/Perth');
+
+        $tests = array(
+            array(
+                'tz' => 99,
+                'str' => '%A, %d %B %Y, %I:%M %p',
+                'expected' => 'Saturday, 01 January 2011, 06:00 PM'
+            ),
+            array(
+                'tz' => 0,
+                'str' => '%A, %d %B %Y, %I:%M %p',
+                'expected' => 'Saturday, 01 January 2011, 10:00 AM'
+            ),
+            array(
+                'tz' => -12,
+                'str' => '%A, %d %B %Y, %I:%M %p',
+                'expected' => 'Saturday, 01 January 2011, 10:00 AM'
+            ),
+            array(
+                'tz' => 99,
+                'str' => 'Žluťoučký koníček %A',
+                'expected' => 'Žluťoučký koníček Saturday'
+            ),
+            array(
+                'tz' => 99,
+                'str' => '言語設定言語 %A',
+                'expected' => '言語設定言語 Saturday'
+            ),
+            array(
+                'tz' => 99,
+                'str' => '简体中文简体 %A',
+                'expected' => '简体中文简体 Saturday'
+            ),
+        );
+
+        // Note: date_format_string() uses the timezone only to differenciate
+        // the server time from the UTC time. It does not modify the timestamp.
+        // Hence similar results for timezones <= 13.
+        // On different systems case of AM PM changes so compare case insensitive.
+        foreach ($tests as $test) {
+            $str = date_format_string(1293876000, $test['str'], $test['tz']);
+            $this->assertEquals(textlib::strtolower($test['expected']), textlib::strtolower($str));
+        }
+
+        // Restore system default values.
+        date_default_timezone_set($systemdefaulttimezone);
+        setlocale(LC_TIME, $oldlocale);
+    }
 }
index c299ffe..cde67d9 100644 (file)
@@ -964,7 +964,7 @@ function external_update_descriptions($component) {
             $dbfunction->classpath = $function['classpath'];
             $update = true;
         }
-        $functioncapabilities = key_exists('capabilities', $function)?$function['capabilities']:'';
+        $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
         if ($dbfunction->capabilities != $functioncapabilities) {
             $dbfunction->capabilities = $functioncapabilities;
             $update = true;
@@ -980,7 +980,7 @@ function external_update_descriptions($component) {
         $dbfunction->methodname = $function['methodname'];
         $dbfunction->classpath  = empty($function['classpath']) ? null : $function['classpath'];
         $dbfunction->component  = $component;
-        $dbfunction->capabilities = key_exists('capabilities', $function)?$function['capabilities']:'';
+        $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
         $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
     }
     unset($functions);
index d67eaf6..632e4d3 100644 (file)
@@ -62,7 +62,7 @@ echo $OUTPUT->heading($icon . s($course->fullname));
 
 // collapsible course summary
 if (!empty($course->summary)) {
-    unset($options);
+    $options = new stdClass();
     $options->trusted = false;
     $options->para    = false;
     $options->filter  = false;
index 8f73ac1..366d5d8 100644 (file)
@@ -59,6 +59,17 @@ class mod_assign_grade_form extends moodleform {
         }
     }
 
+    /**
+     * This is required so when using "Save and next", each form is not defaulted to the previous form.
+     * Giving each form a unique identitifer is enough to prevent this (include the rownum in the form name).
+     *
+     * @return string - The unique identifier for this form.
+     */
+    protected function get_form_identifier() {
+        $params = $this->_customdata[2];
+        return get_class($this) . '_' . $params['rownum'];
+    }
+
     /**
      * Perform minimal validation on the grade form
      * @param array $data
index 3e6e54b..b24f4fa 100644 (file)
@@ -44,16 +44,17 @@ class mod_assign_grading_options_form extends moodleform {
     function definition() {
         $mform = $this->_form;
         $instance = $this->_customdata;
+        $dirtyclass = array('class'=>'ignoredirty');
 
         $mform->addElement('header', 'general', get_string('gradingoptions', 'assign'));
         // visible elements
         $options = array(-1=>get_string('all'),10=>'10', 20=>'20', 50=>'50', 100=>'100');
-        $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options);
+        $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options, $dirtyclass);
         $options = array('' => get_string('filternone', 'assign'),
                          ASSIGN_FILTER_SUBMITTED => get_string('filtersubmitted', 'assign'),
                          ASSIGN_FILTER_REQUIRE_GRADING => get_string('filterrequiregrading', 'assign'));
         if ($instance['submissionsenabled']) {
-            $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options);
+            $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options, $dirtyclass);
         }
 
         // quickgrading
index 96c1c22..f362421 100644 (file)
@@ -78,6 +78,7 @@ $string['couldnotfindassignmenttoupgrade'] = 'Could not find old assignment inst
 $string['currentgrade'] = 'Current grade in gradebook';
 $string['defaultplugins'] = 'Default assignment settings';
 $string['defaultplugins_help'] = 'These settings define the defaults for all new assignments.';
+$string['deleteallsubmissions'] = 'Delete all submissions';
 $string['deletepluginareyousure'] = 'Delete assignment plugin {$a}: are you sure?';
 $string['deletepluginareyousuremessage'] = 'You are about to completely delete the assignment plugin {$a}. This will completely delete everything in the database associated with this assignment plugin. Are you SURE you want to continue?';
 $string['deletingplugin'] = 'Deleting plugin {$a}.';
index 74cfd3b..92490ec 100644 (file)
@@ -54,6 +54,72 @@ function assign_delete_instance($id) {
     return $assignment->delete_instance();
 }
 
+/**
+ * This function is used by the reset_course_userdata function in moodlelib.
+ * This function will remove all assignment submissions and feedbacks in the database
+ * and clean up any related data.
+ * @param $data the data submitted from the reset course.
+ * @return array status array
+ */
+function assign_reset_userdata($data) {
+    global $CFG, $DB;
+    require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+    $status = array();
+    $params = array('courseid'=>$data->courseid);
+    $sql = "SELECT a.id FROM {assign} a WHERE a.course=:courseid";
+    $course = $DB->get_record('course', array('id'=> $data->courseid), '*', MUST_EXIST);
+    if ($assigns = $DB->get_records_sql($sql,$params)) {
+        foreach ($assigns as $assign) {
+            $cm = get_coursemodule_from_instance('assign', $assign->id, $data->courseid, false, MUST_EXIST);
+            $context = context_module::instance($cm->id);
+            $assignment = new assign($context, $cm, $course);
+            $status = array_merge($status, $assignment->reset_userdata($data));
+        }
+    }
+    return $status;
+}
+
+/**
+ * Removes all grades from gradebook
+ *
+ * @param int $courseid The ID of the course to reset
+ * @param string $type Optional type of assignment to limit the reset to a particular assignment type
+ */
+function assign_reset_gradebook($courseid, $type='') {
+    global $CFG, $DB;
+
+    $params = array('moduletype'=>'assign','courseid'=>$courseid);
+    $sql = 'SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
+            FROM {assign} a, {course_modules} cm, {modules} m
+            WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid';
+
+    if ($assignments = $DB->get_records_sql($sql,$params)) {
+        foreach ($assignments as $assignment) {
+            assign_grade_item_update($assignment, 'reset');
+        }
+    }
+}
+
+/**
+ * Implementation of the function for printing the form elements that control
+ * whether the course reset functionality affects the assignment.
+ * @param $mform form passed by reference
+ */
+function assign_reset_course_form_definition(&$mform) {
+    $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign'));
+    $mform->addElement('advcheckbox', 'reset_assign_submissions', get_string('deleteallsubmissions','assign'));
+}
+
+/**
+ * Course reset form defaults.
+ * @param  object $course
+ * @return array
+ */
+function assign_reset_course_form_defaults($course) {
+    return array('reset_assign_submissions'=>1);
+}
+
 /**
  * Update an assignment instance
  *
index 8ba72d4..7238b70 100644 (file)
@@ -505,6 +505,82 @@ class assign {
         return $result;
     }
 
+    /**
+    * Actual implementation of the reset course functionality, delete all the
+    * assignment submissions for course $data->courseid.
+    *
+    * @param $data the data submitted from the reset course.
+    * @return array status array
+    */
+    public function reset_userdata($data) {
+        global $CFG,$DB;
+
+        $componentstr = get_string('modulenameplural', 'assign');
+        $status = array();
+
+        $fs = get_file_storage();
+        if (!empty($data->reset_assign_submissions)) {
+            // Delete files associated with this assignment.
+            foreach ($this->submissionplugins as $plugin) {
+                $fileareas = array();
+                $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
+                $fileareas = $plugin->get_file_areas();
+                foreach ($fileareas as $filearea) {
+                    $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
+                }
+
+                if (!$plugin->delete_instance()) {
+                    $status[] = array('component'=>$componentstr,
+                                      'item'=>get_string('deleteallsubmissions','assign'),
+                                      'error'=>$plugin->get_error());
+                }
+            }
+
+            foreach ($this->feedbackplugins as $plugin) {
+                $fileareas = array();
+                $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
+                $fileareas = $plugin->get_file_areas();
+                foreach ($fileareas as $filearea) {
+                    $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
+                }
+
+                if (!$plugin->delete_instance()) {
+                    $status[] = array('component'=>$componentstr,
+                                      'item'=>get_string('deleteallsubmissions','assign'),
+                                      'error'=>$plugin->get_error());
+                }
+            }
+
+            $assignssql = "SELECT a.id
+                             FROM {assign} a
+                           WHERE a.course=:course";
+            $params = array ("course" => $data->courseid);
+
+            $DB->delete_records_select('assign_submission', "assignment IN ($assignssql)", $params);
+            $status[] = array('component'=>$componentstr,
+                              'item'=>get_string('deleteallsubmissions','assign'),
+                              'error'=>false);
+
+            if (empty($data->reset_gradebook_grades)) {
+                // Remove all grades from gradebook.
+                require_once($CFG->dirroot.'/mod/assign/lib.php');
+                assign_reset_gradebook($data->courseid);
+            }
+        }
+        // Updating dates - shift may be negative too.
+        if ($data->timeshift) {
+            shift_course_mod_dates('assign',
+                                    array('duedate', 'allowsubmissionsfromdate'),
+                                    $data->timeshift,
+                                    $data->courseid);
+            $status[] = array('component'=>$componentstr,
+                              'item'=>get_string('datechanged'),
+                              'error'=>false);
+        }
+
+        return $status;
+    }
+
     /**
      * Update the settings for a single plugin
      *
index 92d59a0..e9a7f0f 100644 (file)
@@ -162,6 +162,11 @@ class assign_upgrade_manager {
             }
             $completiondone = true;
 
+            // Migrate log entries so we don't lose them.
+            $logparams = array('cmid' => $oldcoursemodule->id, 'course' => $oldcoursemodule->course);
+            $DB->set_field('log', 'module', 'assign', $logparams);
+            $DB->set_field('log', 'cmid', $newcoursemodule->id, $logparams);
+
 
             // copy all the submission data (and get plugins to do their bit)
             $oldsubmissions = $DB->get_records('assignment_submissions', array('assignment'=>$oldassignmentid));
@@ -260,6 +265,10 @@ class assign_upgrade_manager {
                     $DB->update_record('course_completion_criteria', $criteria);
                 }
             }
+            // Roll back the log changes
+            $logparams = array('cmid' => $newcoursemodule->id, 'course' => $newcoursemodule->course);
+            $DB->set_field('log', 'module', 'assignment', $logparams);
+            $DB->set_field('log', 'cmid', $oldcoursemodule->id, $logparams);
             // roll back the advanced grading update
             if ($gradingarea) {
                 foreach ($gradeidmap as $newgradeid => $oldsubmissionid) {
index 91c5f5f..8ca17bd 100644 (file)
@@ -1164,9 +1164,9 @@ class assignment_upload extends assignment_base {
         }
         $filename = str_replace(' ', '_', clean_filename($this->course->shortname.'-'.$this->assignment->name.'-'.$groupname.$this->assignment->id.".zip")); //name of new zip file.
         foreach ($submissions as $submission) {
-            // If assignment is open and submission is not finalized then don't add it to zip.
+            // If assignment is open and submission is not finalized and marking button enabled then don't add it to zip.
             $submissionstatus = $this->is_finalized($submission);
-            if ($this->isopen() && empty($submissionstatus)) {
+            if ($this->isopen() && empty($submissionstatus) && !empty($this->assignment->var4)) {
                 continue;
             }
             $a_userid = $submission->userid; //get userid
index 3fb1085..c4709f0 100644 (file)
@@ -40,6 +40,152 @@ function xmldb_book_upgrade($oldversion) {
     // Moodle v2.3.0 release upgrade line
     // Put any upgrade step following this
 
+    // Note: The next steps (up to 2012061710 included, are a "replay" of old upgrade steps,
+    // because some sites updated to Moodle 2.3 didn't have the latest contrib mod_book
+    // installed, so some required changes were missing.
+    //
+    // All the steps are run conditionally so sites upgraded from latest contrib mod_book or
+    // new (2.3 and upwards) sites won't get affected.
+    //
+    // See MDL-35297 and commit msg for more information.
+
+    if ($oldversion < 2012061703) {
+        // Rename field summary on table book to intro
+        $table = new xmldb_table('book');
+        $field = new xmldb_field('summary', XMLDB_TYPE_TEXT, null, null, null, null, null, 'name');
+
+        // Launch rename field summary
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->rename_field($table, $field, 'intro');
+        }
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061703, 'book');
+    }
+
+    if ($oldversion < 2012061704) {
+        // Define field introformat to be added to book
+        $table = new xmldb_table('book');
+        $field = new xmldb_field('introformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'intro');
+
+        // Launch add field introformat
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+            // Conditionally migrate to html format in intro
+            // Si está activo el htmleditor!!!!!
+            if ($CFG->texteditors !== 'textarea') {
+                $rs = $DB->get_recordset('book', array('introformat'=>FORMAT_MOODLE), '', 'id,intro,introformat');
+                foreach ($rs as $b) {
+                    $b->intro       = text_to_html($b->intro, false, false, true);
+                    $b->introformat = FORMAT_HTML;
+                    $DB->update_record('book', $b);
+                    upgrade_set_timeout();
+                }
+                unset($b);
+                $rs->close();
+            }
+        }
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061704, 'book');
+    }
+
+    if ($oldversion < 2012061705) {
+        // Define field introformat to be added to book
+        $table = new xmldb_table('book_chapters');
+        $field = new xmldb_field('contentformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'content');
+
+        // Launch add field introformat
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+
+            $DB->set_field('book_chapters', 'contentformat', FORMAT_HTML, array());
+        }
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061705, 'book');
+    }
+
+    if ($oldversion < 2012061706) {
+        require_once("$CFG->dirroot/mod/book/db/upgradelib.php");
+
+        $sqlfrom = "FROM {book} b
+                    JOIN {modules} m ON m.name = 'book'
+                    JOIN {course_modules} cm ON (cm.module = m.id AND cm.instance = b.id)";
+
+        $count = $DB->count_records_sql("SELECT COUNT('x') $sqlfrom");
+
+        if ($rs = $DB->get_recordset_sql("SELECT b.id, b.course, cm.id AS cmid $sqlfrom ORDER BY b.course, b.id")) {
+
+            $pbar = new progress_bar('migratebookfiles', 500, true);
+
+            $i = 0;
+            foreach ($rs as $book) {
+                $i++;
+                upgrade_set_timeout(360); // set up timeout, may also abort execution
+                $pbar->update($i, $count, "Migrating book files - $i/$count.");
+
+                $context = context_module::instance($book->cmid);
+
+                mod_book_migrate_moddata_dir_to_legacy($book, $context, '/');
+
+                // remove dirs if empty
+                @rmdir("$CFG->dataroot/$book->course/$CFG->moddata/book/$book->id/");
+                @rmdir("$CFG->dataroot/$book->course/$CFG->moddata/book/");
+                @rmdir("$CFG->dataroot/$book->course/$CFG->moddata/");
+                @rmdir("$CFG->dataroot/$book->course/");
+            }
+            $rs->close();
+        }
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061706, 'book');
+    }
+
+    if ($oldversion < 2012061707) {
+        // Define field disableprinting to be dropped from book
+        $table = new xmldb_table('book');
+        $field = new xmldb_field('disableprinting');
+
+        // Conditionally launch drop field disableprinting
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061707, 'book');
+    }
+
+    if ($oldversion < 2012061708) {
+        unset_config('book_tocwidth');
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061708, 'book');
+    }
+
+    if ($oldversion < 2012061709) {
+        require_once("$CFG->dirroot/mod/book/db/upgradelib.php");
+
+        mod_book_migrate_all_areas();
+
+        upgrade_mod_savepoint(true, 2012061709, 'book');
+    }
+
+    if ($oldversion < 2012061710) {
+
+        // Define field revision to be added to book
+        $table = new xmldb_table('book');
+        $field = new xmldb_field('revision', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'customtitles');
+
+        // Conditionally launch add field revision
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // book savepoint reached
+        upgrade_mod_savepoint(true, 2012061710, 'book');
+    }
+    // End of MDL-35297 "replayed" steps.
 
     return true;
 }
diff --git a/mod/book/db/upgradelib.php b/mod/book/db/upgradelib.php
new file mode 100644 (file)
index 0000000..5411930
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+// This file is part of Book module for 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/>.
+
+/**
+ * Book module upgrade related helper functions
+ *
+ * @package    mod_book
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Migrate book files stored in moddata folders.
+ *
+ * Please note it was a big mistake to store the files there in the first place!
+ *
+ * @param stdClass $book
+ * @param stdClass $context
+ * @param string $path
+ * @return void
+ */
+function mod_book_migrate_moddata_dir_to_legacy($book, $context, $path) {
+    global $OUTPUT, $CFG;
+
+    $base = "$CFG->dataroot/$book->course/$CFG->moddata/book/$book->id";
+    $fulldir = $base.$path;
+
+    if (!is_dir($fulldir)) {
+        // does not exist
+        return;
+    }
+
+    $fs      = get_file_storage();
+    $items   = new DirectoryIterator($fulldir);
+
+    foreach ($items as $item) {
+        if ($item->isDot()) {
+            unset($item); // release file handle
+            continue;
+        }
+
+        if ($item->isLink()) {
+            // do not follow symlinks - they were never supported in moddata, sorry
+            unset($item); // release file handle
+            continue;
+        }
+
+        if ($item->isFile()) {
+            if (!$item->isReadable()) {
+                echo $OUTPUT->notification(" File not readable, skipping: ".$fulldir.$item->getFilename());
+                unset($item); // release file handle
+                continue;
+            }
+
+            $filepath = clean_param("/$CFG->moddata/book/$book->id".$path, PARAM_PATH);
+            $filename = clean_param($item->getFilename(), PARAM_FILE);
+
+            if ($filename === '') {
+                // unsupported chars, sorry
+                unset($item); // release file handle
+                continue;
+            }
+
+            if (textlib::strlen($filepath) > 255) {
+                echo $OUTPUT->notification(" File path longer than 255 chars, skipping: ".$fulldir.$item->getFilename());
+                unset($item); // release file handle
+                continue;
+            }
+
+            if (!$fs->file_exists($context->id, 'course', 'legacy', '0', $filepath, $filename)) {
+                $file_record = array('contextid'=>$context->id, 'component'=>'course', 'filearea'=>'legacy', 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename,
+                                     'timecreated'=>$item->getCTime(), 'timemodified'=>$item->getMTime());
+                $fs->create_file_from_pathname($file_record, $fulldir.$item->getFilename());
+            }
+            $oldpathname = $fulldir.$item->getFilename();
+            unset($item); // release file handle
+            @unlink($oldpathname);
+
+        } else {
+            // migrate recursively all subdirectories
+            $oldpathname = $base.$item->getFilename().'/';
+            $subpath     = $path.$item->getFilename().'/';
+            unset($item);  // release file handle
+            mod_book_migrate_moddata_dir_to_legacy($book, $context, $subpath);
+            @rmdir($oldpathname); // deletes dir if empty
+        }
+    }
+    unset($items); // release file handles
+}
+
+/**
+ * Migrate legacy files in intro and chapters
+ * @return void
+ */
+function mod_book_migrate_all_areas() {
+    global $DB;
+
+    $rsbooks = $DB->get_recordset('book');
+    foreach($rsbooks as $book) {
+        upgrade_set_timeout(360); // set up timeout, may also abort execution
+        $cm = get_coursemodule_from_instance('book', $book->id);
+        $context = context_module::instance($cm->id);
+        mod_book_migrate_area($book, 'intro', 'book', $book->course, $context, 'mod_book', 'intro', 0);
+
+        $rschapters = $DB->get_recordset('book_chapters', array('bookid'=>$book->id));
+        foreach ($rschapters as $chapter) {
+            mod_book_migrate_area($chapter, 'content', 'book_chapters', $book->course, $context, 'mod_book', 'chapter', $chapter->id);
+        }
+        $rschapters->close();
+    }
+    $rsbooks->close();
+}
+
+/**
+ * Migrate one area, this should be probably part of moodle core...
+ *
+ * @param stdClass $record object to migrate files (book, chapter)
+ * @param string $field field in the record we are going to migrate
+ * @param string $table DB table containing the information to migrate
+ * @param int $courseid id of the course the book module belongs to
+ * @param context_module $context context of the book module
+ * @param string $component component to be used for the migrated files
+ * @param string $filearea filearea to be used for the migrated files
+ * @param int $itemid id to be used for the migrated files
+ * @return void
+ */
+function mod_book_migrate_area($record, $field, $table, $courseid, $context, $component, $filearea, $itemid) {
+    global $CFG, $DB;
+
+    $fs = get_file_storage();
+
+    foreach(array(get_site()->id, $courseid) as $cid) {
+        $matches = null;
+        $ooldcontext = context_course::instance($cid);
+        if (preg_match_all("|$CFG->wwwroot/file.php(\?file=)?/$cid(/[^\s'\"&\?#]+)|", $record->$field, $matches)) {
+            $file_record = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid);
+            foreach ($matches[2] as $i=>$filepath) {
+                if (!$file = $fs->get_file_by_hash(sha1("/$ooldcontext->id/course/legacy/0".$filepath))) {
+                    continue;
+                }
+                try {
+                    if (!$newfile = $fs->get_file_by_hash(sha1("/$context->id/$component/$filearea/$itemid".$filepath))) {
+                        $fs->create_file_from_storedfile($file_record, $file);
+                    }
+                    $record->$field = str_replace($matches[0][$i], '@@PLUGINFILE@@'.$filepath, $record->$field);
+                } catch (Exception $ex) {
+                    // ignore problems
+                }
+                $DB->set_field($table, $field, $record->$field, array('id'=>$record->id));
+            }
+        }
+    }
+}
\ No newline at end of file
index 470e18e..e15de10 100644 (file)
@@ -47,6 +47,7 @@ $string['editingchapter'] = 'Editing chapter';
 $string['chaptertitle'] = 'Chapter title';
 $string['content'] = 'Content';
 $string['subchapter'] = 'Subchapter';
+$string['nocontent'] = 'No content has been added to this book yet.';
 $string['numbering'] = 'Chapter formatting';
 $string['numbering_help'] = '* None - Chapter and subchapter titles have no formatting
 * Numbers - Chapters and subchapter titles are numbered 1, 1.1, 1.2, 2, ...
index 26d0e40..9502805 100644 (file)
@@ -448,18 +448,84 @@ class book_file_info extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
+        return $this->get_filtered_children('*', false, true);
+    }
+
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
         global $DB;
+        $params = array('contextid' => $this->context->id,
+            'component' => 'mod_book',
+            'filearea' => $this->filearea,
+            'bookid' => $this->cm->instance);
+        $sql = 'SELECT DISTINCT bc.id, bc.pagenum
+                    FROM {files} f, {book_chapters} bc
+                    WHERE f.contextid = :contextid
+                    AND f.component = :component
+                    AND f.filearea = :filearea
+                    AND bc.bookid = :bookid
+                    AND bc.id = f.itemid';
+        if (!$returnemptyfolders) {
+            $sql .= ' AND filename <> :emptyfilename';
+            $params['emptyfilename'] = '.';
+        }
+        list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f');
+        $sql .= ' '.$sql2;
+        $params = array_merge($params, $params2);
+        if ($countonly === false) {
+            $sql .= ' ORDER BY bc.pagenum';
+        }
 
+        $rs = $DB->get_recordset_sql($sql, $params);
         $children = array();
-        $chapters = $DB->get_records('book_chapters', array('bookid'=>$this->cm->instance), 'pagenum', 'id, pagenum');
-        foreach ($chapters as $itemid => $unused) {
-            if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $itemid)) {
-                $children[] = $child;
+        foreach ($rs as $record) {
+            if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $record->id)) {
+                if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
+                    $children[] = $child;
+                }
+            }
+            if ($countonly !== false && count($children) >= $countonly) {
+                break;
             }
         }
+        $rs->close();
+        if ($countonly !== false) {
+            return count($children);
+        }
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        return $this->get_filtered_children($extensions, false);
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      * @return file_info or null for root
index eaae261..25ee6f7 100644 (file)
@@ -60,9 +60,9 @@ $mform = new booktool_importhtml_form(null, array('id'=>$id, 'chapterid'=>$chapt
 // If data submitted, then process and store.
 if ($mform->is_cancelled()) {
     if (empty($chapter->id)) {
-        redirect("/mod/book/view.php?id=$cm->id");
+        redirect($CFG->wwwroot."/mod/book/view.php?id=$cm->id");
     } else {
-        redirect("/mod/book/view.php?id=$cm->id&chapterid=$chapter->id");
+        redirect($CFG->wwwroot."/mod/book/view.php?id=$cm->id&chapterid=$chapter->id");
     }
 
 } else if ($data = $mform->get_data()) {
index e187cb3..f37c00d 100644 (file)
@@ -25,6 +25,6 @@
 defined('MOODLE_INTERNAL') || die;
 
 $module->component = 'mod_book'; // Full name of the plugin (used for diagnostics)
-$module->version   = 2012061702; // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2012061710; // The current module version (Date: YYYYMMDDXX)
 $module->requires  = 2012061700; // Requires this Moodle version
 $module->cron      = 0;          // Period for cron to check this module (secs)
index 91b83b8..6b9365d 100644 (file)
@@ -87,13 +87,16 @@ if ($chapterid == '0') { // Go to first chapter if no given.
     }
 }
 
-if (!$chapterid or !$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id))) {
-    print_error('errorchapter', 'mod_book', new moodle_url('/course/view.php', array('id'=>$course->id)));
-}
+$courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
 
-// chapter is hidden for students
-if ($chapter->hidden and !$viewhidden) {
-    print_error('errorchapter', 'mod_book', new moodle_url('/course/view.php', array('id'=>$course->id)));
+// No content in the book.
+if (!$chapterid) {
+    $PAGE->set_url('/mod/book/view.php', array('id' => $id));
+    notice(get_string('nocontent', 'mod_book'), $courseurl->out(false));
+}
+// Chapter doesnt exist or it is hidden for students
+if ((!$chapter = $DB->get_record('book_chapters', array('id' => $chapterid, 'bookid' => $book->id))) or ($chapter->hidden and !$viewhidden)) {
+    print_error('errorchapter', 'mod_book', $courseurl);
 }
 
 $PAGE->set_url('/mod/book/view.php', array('id'=>$id, 'chapterid'=>$chapterid));
index 313058c..2fa245d 100644 (file)
@@ -55,12 +55,13 @@ class data_field_date extends data_field_base {
 
     //Enable the following three functions once core API issues have been addressed.
     function display_search_field($value=0) {
-        $selectors = html_writer::select_time('days', 'f_'.$this->field->id.'_d', $value)
-           . html_writer::select_time('months', 'f_'.$this->field->id.'_m', $value)
-           . html_writer::select_time('years', 'f_'.$this->field->id.'_y', $value);
-       return $selectors;
+        $selectors = html_writer::select_time('days', 'f_'.$this->field->id.'_d', $value['timestamp'])
+           . html_writer::select_time('months', 'f_'.$this->field->id.'_m', $value['timestamp'])
+           . html_writer::select_time('years', 'f_'.$this->field->id.'_y', $value['timestamp']);
+        $datecheck = html_writer::checkbox('f_'.$this->field->id.'_z', 1, $value['usedate']);
+        $str = $selectors . ' ' . $datecheck . ' ' . get_string('usedate', 'data');
 
-        //return print_date_selector('f_'.$this->field->id.'_d', 'f_'.$this->field->id.'_m', 'f_'.$this->field->id.'_y', $value, true);
+        return $str;
     }
 
     function generate_sql($tablealias, $value) {
@@ -70,21 +71,22 @@ class data_field_date extends data_field_base {
         $i++;
         $name = "df_date_$i";
         $varcharcontent = $DB->sql_compare_text("{$tablealias}.content");
-        return array(" ({$tablealias}.fieldid = {$this->field->id} AND $varcharcontent = :$name) ", array($name=>$value));
+        return array(" ({$tablealias}.fieldid = {$this->field->id} AND $varcharcontent = :$name) ", array($name => $value['timestamp']));
     }
 
     function parse_search_field() {
-
         $day   = optional_param('f_'.$this->field->id.'_d', 0, PARAM_INT);
         $month = optional_param('f_'.$this->field->id.'_m', 0, PARAM_INT);
         $year  = optional_param('f_'.$this->field->id.'_y', 0, PARAM_INT);
-        if (!empty($day) && !empty($month) && !empty($year)) {
-            return make_timestamp($year, $month, $day, 12, 0, 0, 0, false);
-        }
-        else {
+        $usedate = optional_param('f_'.$this->field->id.'_z', 0, PARAM_INT);
+        $data = array();
+        if (!empty($day) && !empty($month) && !empty($year) && $usedate == 1) {
+            $data['timestamp'] = make_timestamp($year, $month, $day, 12, 0, 0, 0, false);
+            $data['usedate'] = 1;
+            return $data;
+        } else {
             return 0;
         }
-
     }
 
     function update_content($recordid, $value, $name='') {
index ed005e6..53887ec 100644 (file)
@@ -338,6 +338,7 @@ $string['uploadrecords_help'] = 'Entries may be uploaded via text file. The form
 The field enclosure is a character that surrounds each field in each record. It can normally be left unset.';
 $string['uploadrecords_link'] = 'mod/data/import';
 $string['url'] = 'Url';
+$string['usedate'] = 'Include in search.';
 $string['usestandard'] = 'Use a preset';
 $string['usestandard_help'] = 'To use a preset available to the whole site, select it from the list. (If you have added a preset to the list using the save as preset feature then you have the option of deleting it.)';
 $string['viewfromdate'] = 'Read only from';
index fc26f78..583ea73 100644 (file)
@@ -3408,21 +3408,28 @@ function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
 /**
  * Get all of the record ids from a database activity.
  *
- * @param int $dataid      The dataid of the database module.
- * @return array $idarray  An array of record ids
+ * @param int    $dataid      The dataid of the database module.
+ * @param object $selectdata  Contains an additional sql statement for the
+ *                            where clause for group and approval fields.
+ * @param array  $params      Parameters that coincide with the sql statement.
+ * @return array $idarray     An array of record ids
  */
-function data_get_all_recordids($dataid) {
+function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
     global $DB;
-    $initsql = 'SELECT c.recordid
-                  FROM {data_fields} f,
-                       {data_content} c
-                 WHERE f.dataid = :dataid
-                   AND f.id = c.fieldid
-              GROUP BY c.recordid';
-    $initrecord = $DB->get_recordset_sql($initsql, array('dataid' => $dataid));
+    $initsql = 'SELECT r.id
+                  FROM {data_records} r
+                 WHERE r.dataid = :dataid';
+    if ($selectdata != '') {
+        $initsql .= $selectdata;
+        $params = array_merge(array('dataid' => $dataid), $params);
+    } else {
+        $params = array('dataid' => $dataid);
+    }
+    $initsql .= ' GROUP BY r.id';
+    $initrecord = $DB->get_recordset_sql($initsql, $params);
     $idarray = array();
     foreach ($initrecord as $data) {
-        $idarray[] = $data->recordid;
+        $idarray[] = $data->id;
     }
     // Close the record set and free up resources.
     $initrecord->close();
@@ -3567,8 +3574,7 @@ function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $so
     } else {
         list($insql, $inparam) = $DB->get_in_or_equal(array('-1'), SQL_PARAMS_NAMED);
     }
-    $nestfromsql .= ' AND c.recordid ' . $insql . $groupsql;
-    $nestfromsql = "$nestfromsql $selectdata";
+    $nestfromsql .= ' AND c.recordid ' . $insql . $selectdata . $groupsql;
     $sqlselect['sql'] = "$nestselectsql $nestfromsql $sortorder";
     $sqlselect['params'] = $inparam;
     return $sqlselect;
index e06430a..55a2070 100644 (file)
@@ -493,20 +493,79 @@ class data_file_info_container extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
+        return $this->get_filtered_children('*', false, true);
+    }
+
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
         global $DB;
+        $params = array('contextid' => $this->context->id,
+            'component' => $this->component,
+            'filearea' => $this->filearea);
+        $sql = 'SELECT DISTINCT itemid
+                    FROM {files}
+                    WHERE contextid = :contextid
+                    AND component = :component
+                    AND filearea = :filearea';
+        if (!$returnemptyfolders) {
+            $sql .= ' AND filename <> :emptyfilename';
+            $params['emptyfilename'] = '.';
+        }
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $sql .= ' '.$sql2;
+        $params = array_merge($params, $params2);
+        if ($countonly === false) {
+            $sql .= ' ORDER BY itemid DESC';
+        }
 
+        $rs = $DB->get_recordset_sql($sql, $params);
         $children = array();
-        $itemids = $DB->get_records('files', array('contextid' => $this->context->id, 'component' => $this->component,
-            'filearea' => $this->filearea), 'itemid DESC', "DISTINCT itemid");
-        foreach ($itemids as $itemid => $unused) {
-            if ($child = $this->browser->get_file_info($this->context, 'mod_data', $this->filearea, $itemid)) {
+        foreach ($rs as $record) {
+            if ($child = $this->browser->get_file_info($this->context, 'mod_data', $this->filearea, $record->itemid)) {
                 $children[] = $child;
             }
+            if ($countonly !== false && count($children) >= $countonly) {
+                break;
+            }
+        }
+        $rs->close();
+        if ($countonly !== false) {
+            return count($children);
         }
-
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        return $this->get_filtered_children($extensions, false);
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      *
index 5ffa5bf..f740429 100644 (file)
 id,userid,groupid,dataid,timecreated,timemodified,approved
 1,1,0,1,1234567891,1234567892,1
-2,2,0,1,1234567891,1234567892,1
-3,3,0,1,1234567891,1234567892,1
-4,4,0,1,1234567891,1234567892,1
-5,5,0,1,1234567891,1234567892,1
-6,6,0,1,1234567891,1234567892,1
+2,2,1,1,1234567891,1234567892,1
+3,3,2,1,1234567891,1234567892,1
+4,4,1,1,1234567891,1234567892,1
+5,5,1,1,1234567891,1234567892,1
+6,6,2,1,1234567891,1234567892,1
 7,7,0,1,1234567891,1234567892,1
-8,8,0,1,1234567891,1234567892,1
-9,9,0,1,1234567891,1234567892,1
-10,10,0,1,1234567891,1234567892,1
-11,11,0,1,1234567891,1234567892,1
-12,12,0,1,1234567891,1234567892,1
-13,13,0,1,1234567891,1234567892,1
+8,8,2,1,1234567891,1234567892,1
+9,9,1,1,1234567891,1234567892,1
+10,10,1,1,1234567891,1234567892,1
+11,11,1,1,1234567891,1234567892,1
+12,12,2,1,1234567891,1234567892,1
+13,13,2,1,1234567891,1234567892,1
 14,14,0,1,1234567891,1234567892,1
-15,15,0,1,1234567891,1234567892,1
+15,15,2,1,1234567891,1234567892,1
 16,16,0,1,1234567891,1234567892,1
-17,17,0,1,1234567891,1234567892,1
-18,18,0,1,1234567891,1234567892,1
+17,17,1,1,1234567891,1234567892,1
+18,18,2,1,1234567891,1234567892,1
 19,19,0,1,1234567891,1234567892,1
-20,20,0,1,1234567891,1234567892,1
+20,20,1,1,1234567891,1234567892,1
 21,21,0,1,1234567891,1234567892,1
-22,22,0,1,1234567891,1234567892,1
+22,22,2,1,1234567891,1234567892,1
 23,23,0,1,1234567891,1234567892,1
-24,24,0,1,1234567891,1234567892,1
-25,25,0,1,1234567891,1234567892,1
+24,24,2,1,1234567891,1234567892,1
+25,25,2,1,1234567891,1234567892,1
 26,26,0,1,1234567891,1234567892,1
-27,27,0,1,1234567891,1234567892,1
-28,28,0,1,1234567891,1234567892,1
+27,27,1,1,1234567891,1234567892,1
+28,28,2,1,1234567891,1234567892,1
 29,29,0,1,1234567891,1234567892,1
-30,30,0,1,1234567891,1234567892,1
+30,30,2,1,1234567891,1234567892,1
 31,31,0,1,1234567891,1234567892,1
-32,32,0,1,1234567891,1234567892,1
-33,33,0,1,1234567891,1234567892,1
-34,34,0,1,1234567891,1234567892,1
-35,35,0,1,1234567891,1234567892,1
+32,32,1,1,1234567891,1234567892,1
+33,33,1,1,1234567891,1234567892,1
+34,34,2,1,1234567891,1234567892,1
+35,35,1,1,1234567891,1234567892,1
 36,36,0,1,1234567891,1234567892,1
 37,37,0,1,1234567891,1234567892,1
-38,38,0,1,1234567891,1234567892,1
-39,39,0,1,1234567891,1234567892,1
-40,40,0,1,1234567891,1234567892,1
+38,38,2,1,1234567891,1234567892,1
+39,39,1,1,1234567891,1234567892,1
+40,40,1,1,1234567891,1234567892,1
 41,41,0,1,1234567891,1234567892,1
-42,42,0,1,1234567891,1234567892,1
+42,42,1,1,1234567891,1234567892,1
 43,43,0,1,1234567891,1234567892,1
-44,44,0,1,1234567891,1234567892,1
+44,44,1,1,1234567891,1234567892,1
 45,45,0,1,1234567891,1234567892,1
 46,46,0,1,1234567891,1234567892,1
-47,47,0,1,1234567891,1234567892,1
+47,47,1,1,1234567891,1234567892,1
 48,48,0,1,1234567891,1234567892,1
 49,49,0,1,1234567891,1234567892,1
-50,50,0,1,1234567891,1234567892,1
+50,50,1,1,1234567891,1234567892,1
 51,51,0,1,1234567891,1234567892,1
 52,52,0,1,1234567891,1234567892,1
-53,53,0,1,1234567891,1234567892,1
-54,54,0,1,1234567891,1234567892,1
+53,53,1,1,1234567891,1234567892,1
+54,54,1,1,1234567891,1234567892,1
 55,55,0,1,1234567891,1234567892,1
-56,56,0,1,1234567891,1234567892,1
-57,57,0,1,1234567891,1234567892,1
-58,58,0,1,1234567891,1234567892,1
-59,59,0,1,1234567891,1234567892,1
-60,60,0,1,1234567891,1234567892,1
+56,56,2,1,1234567891,1234567892,1
+57,57,2,1,1234567891,1234567892,1
+58,58,2,1,1234567891,1234567892,1
+59,59,1,1,1234567891,1234567892,1
+60,60,1,1,1234567891,1234567892,1
 61,61,0,1,1234567891,1234567892,1
-62,62,0,1,1234567891,1234567892,1
+62,62,2,1,1234567891,1234567892,1
 63,63,0,1,1234567891,1234567892,1
 64,64,0,1,1234567891,1234567892,1
-65,65,0,1,1234567891,1234567892,1
-66,66,0,1,1234567891,1234567892,1
+65,65,1,1,1234567891,1234567892,1
+66,66,1,1,1234567891,1234567892,1
 67,67,0,1,1234567891,1234567892,1
 68,68,0,1,1234567891,1234567892,1
-69,69,0,1,1234567891,1234567892,1
-70,70,0,1,1234567891,1234567892,1
+69,69,2,1,1234567891,1234567892,1
+70,70,2,1,1234567891,1234567892,1
 71,71,0,1,1234567891,1234567892,1
-72,72,0,1,1234567891,1234567892,1
-73,73,0,1,1234567891,1234567892,1
+72,72,1,1,1234567891,1234567892,1
+73,73,1,1,1234567891,1234567892,1
 74,74,0,1,1234567891,1234567892,1
 75,75,0,1,1234567891,1234567892,1
-76,76,0,1,1234567891,1234567892,1
-77,77,0,1,1234567891,1234567892,1
+76,76,2,1,1234567891,1234567892,1
+77,77,2,1,1234567891,1234567892,1
 78,78,0,1,1234567891,1234567892,1
-79,79,0,1,1234567891,1234567892,1
-80,80,0,1,1234567891,1234567892,1
+79,79,1,1,1234567891,1234567892,1
+80,80,1,1,1234567891,1234567892,1
 81,81,0,1,1234567891,1234567892,1
-82,82,0,1,1234567891,1234567892,1
-83,83,0,1,1234567891,1234567892,1
-84,84,0,1,1234567891,1234567892,1
-85,85,0,1,1234567891,1234567892,1
+82,82,1,1,1234567891,1234567892,1
+83,83,1,1,1234567891,1234567892,1
+84,84,1,1,1234567891,1234567892,1
+85,85,1,1,1234567891,1234567892,1
 86,86,0,1,1234567891,1234567892,1
 87,87,0,1,1234567891,1234567892,1
 88,88,0,1,1234567891,1234567892,1
-89,89,0,1,1234567891,1234567892,1
-90,90,0,1,1234567891,1234567892,1
-91,91,0,1,1234567891,1234567892,1
-92,92,0,1,1234567891,1234567892,1
-93,93,0,1,1234567891,1234567892,1
-94,94,0,1,1234567891,1234567892,1
-95,95,0,1,1234567891,1234567892,1
-96,96,0,1,1234567891,1234567892,1
-97,97,0,1,1234567891,1234567892,1
-98,98,0,1,1234567891,1234567892,1
-99,99,0,1,1234567891,1234567892,1
-100,100,0,1,1234567891,1234567892,1
+89,89,1,1,1234567891,1234567892,1
+90,90,1,1,1234567891,1234567892,0
+91,91,2,1,1234567891,1234567892,0
+92,92,0,1,1234567891,1234567892,0
+93,93,2,1,1234567891,1234567892,0
+94,94,1,1,1234567891,1234567892,0
+95,95,1,1,1234567891,1234567892,0
+96,96,1,1,1234567891,1234567892,0
+97,97,0,1,1234567891,1234567892,0
+98,98,1,1,1234567891,1234567892,0
+99,99,2,1,1234567891,1234567892,0
+100,100,0,1,1234567891,1234567892,0
index 150bc5d..2980643 100644 (file)
@@ -70,6 +70,11 @@ class data_advanced_search_sql_test extends advanced_testcase {
      */
     public $datarecordcount = 100;
 
+    /**
+     * @var int $groupdatarecordcount  The number of records in the database in groups 0 and 1.
+     */
+    public $groupdatarecordcount = 75;
+
     /**
      * @var array $datarecordset   Expected record IDs.
      */
@@ -80,6 +85,11 @@ class data_advanced_search_sql_test extends advanced_testcase {
      */
     public $finalrecord = array();
 
+    /**
+     * @var int $approvedatarecordcount  The number of approved records in the database.
+     */
+    public $approvedatarecordcount = 89;
+
     /**
      * Set up function. In this instance we are setting up database
      * records to be used in the unit tests.
@@ -169,6 +179,13 @@ class data_advanced_search_sql_test extends advanced_testcase {
      * Test 4: data_get_advanced_search_sql provides an array which contains an sql string to be used for displaying records
      * to the user when they use the advanced search criteria and the parameters that go with the sql statement. This test
      * takes that information and does a search on the database, returning a record.
+     *
+     * Test 5: Returning to data_get_all_recordids(). Here we are ensuring that the total amount of record ids is reduced to
+     * match the group conditions that are provided. There are 25 entries which relate to group 2. They are removed
+     * from the total so we should only have 75 records total.
+     *
+     * Test 6: data_get_all_recordids() again. This time we are testing approved database records. We only want to
+     * display the records that have been approved. In this record set we have 89 approved records.
      */
     function test_advanced_search_sql_section() {
         global $DB;
@@ -193,5 +210,16 @@ class data_advanced_search_sql_test extends advanced_testcase {
         $allparams = array_merge($html['params'], array('dataid' => $this->recorddata->id));
         $records = $DB->get_records_sql($html['sql'], $allparams);
         $this->assertEquals($records, $this->finalrecord);
+
+        // Test 5
+        $groupsql = " AND (r.groupid = :currentgroup OR r.groupid = 0)";
+        $params = array('currentgroup' => 1);
+        $recordids = data_get_all_recordids($this->recorddata->id, $groupsql, $params);
+        $this->assertEquals($this->groupdatarecordcount, count($recordids));
+
+        // Test 6
+        $approvesql = ' AND r.approved=1 ';
+        $recordids = data_get_all_recordids($this->recorddata->id, $approvesql, $params);
+        $this->assertEquals($this->approvedatarecordcount, count($recordids));
     }
 }
index 3e9146e..1b2d85b 100644 (file)
     groups_print_activity_menu($cm, $returnurl);
     $currentgroup = groups_get_activity_group($cm);
     $groupmode = groups_get_activity_groupmode($cm);
+    // If a student is not part of a group and seperate groups is enabled, we don't
+    // want them seeing all records.
+    if ($currentgroup == 0 && $groupmode == 1 && !has_capability('mod/data:manageentries', $context)) {
+        $canviewallrecords = false;
+    } else {
+        $canviewallrecords = true;
+    }
 
     // detect entries not approved yet and show hint instead of not found error
     if ($record and $data->approval and !$record->approved and $record->userid != $USER->id and !has_capability('mod/data:manageentries', $context)) {
@@ -465,7 +472,13 @@ if ($showactivity) {
             $groupselect = " AND (r.groupid = :currentgroup OR r.groupid = 0)";
             $params['currentgroup'] = $currentgroup;
         } else {
-            $groupselect = ' ';
+            if ($canviewallrecords) {
+                $groupselect = ' ';
+            } else {
+                // If separate groups are enabled and the user isn't in a group or
+                // a teacher, manager, admin etc, then just show them entries for 'All participants'.
+                $groupselect = " AND r.groupid = 0";
+            }
         }
 
         // Init some variables to be used by advanced search
@@ -590,10 +603,19 @@ if ($showactivity) {
         $sqlmax     = "SELECT $count FROM $tables $where $groupselect $approveselect"; // number of all recoirds user may see
         $allparams  = array_merge($params, $advparams);
 
-        $recordids = data_get_all_recordids($data->id);
+        // Provide initial sql statements and parameters to reduce the number of total records.
+        $selectdata = $groupselect . $approveselect;
+        $initialparams = array();
+        if ($currentgroup) {
+            $initialparams['currentgroup'] = $params['currentgroup'];
+        }
+        if (!$approvecap && $data->approval && isloggedin()) {
+            $initialparams['myid1'] = $params['myid1'];
+        }
+
+        $recordids = data_get_all_recordids($data->id, $selectdata, $initialparams);
         $newrecordids = data_get_advance_search_ids($recordids, $search_array, $data->id);
         $totalcount = count($newrecordids);
-        $selectdata = $groupselect . $approveselect;
 
         if (!empty($advanced)) {
             $advancedsearchsql = data_get_advanced_search_sql($sort, $data, $newrecordids, $selectdata, $sortorder);
index dd1b3af..406352a 100644 (file)
@@ -484,20 +484,79 @@ class forum_file_info_container extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
+        return $this->get_filtered_children('*', false, true);
+    }
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
         global $DB;
+        $params = array('contextid' => $this->context->id,
+            'component' => $this->component,
+            'filearea' => $this->filearea);
+        $sql = 'SELECT DISTINCT itemid
+                    FROM {files}
+                    WHERE contextid = :contextid
+                    AND component = :component
+                    AND filearea = :filearea';
+        if (!$returnemptyfolders) {
+            $sql .= ' AND filename <> :emptyfilename';
+            $params['emptyfilename'] = '.';
+        }
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $sql .= ' '.$sql2;
+        $params = array_merge($params, $params2);
+        if ($countonly !== false) {
+            $sql .= ' ORDER BY itemid DESC';
+        }
 
+        $rs = $DB->get_recordset_sql($sql, $params);
         $children = array();
-        $itemids = $DB->get_records('files', array('contextid' => $this->context->id, 'component' => $this->component,
-            'filearea' => $this->filearea), 'itemid DESC', "DISTINCT itemid");
-        foreach ($itemids as $itemid => $unused) {
-            if ($child = $this->browser->get_file_info($this->context, 'mod_forum', $this->filearea, $itemid)) {
+        foreach ($rs as $record) {
+            if (($child = $this->browser->get_file_info($this->context, 'mod_forum', $this->filearea, $record->itemid))
+                    && ($returnemptyfolders || $child->count_non_empty_children($extensions))) {
                 $children[] = $child;
             }
+            if ($countonly !== false && count($children) >= $countonly) {
+                break;
+            }
+        }
+        $rs->close();
+        if ($countonly !== false) {
+            return count($children);
         }
-
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        return $this->get_filtered_children($extensions, false);
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      *
index fe824c4..0e544df 100644 (file)
 
 /** Unknown Styles ??? */
 #email .unsubscribelink {margin-top:20px;}
+
+
+/* Forumpost unread
+-------------------------*/
+#page-mod-forum-view .unread,
+.forumpost.unread .row.header,
+.path-course-view .unread,
+span.unread {
+    background-color: #FFD;
+}
+.forumpost.unread .row.header {
+    border-bottom: 1px solid #DDD;
+}
\ No newline at end of file
index c569b18..5b04e57 100644 (file)
@@ -546,30 +546,86 @@ class glossary_file_info_container extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
-        global $DB;
+        return $this->get_filtered_children('*', false, true);
+    }
 
-        $sql = "SELECT DISTINCT f.itemid, ge.concept
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
+        global $DB;
+        $sql = 'SELECT DISTINCT f.itemid, ge.concept
                   FROM {files} f
-                  JOIN {modules} m ON (m.name = 'glossary' AND m.visible = 1)
-                  JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = ?)
+                  JOIN {modules} m ON (m.name = :modulename AND m.visible = 1)
+                  JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = :instanceid)
                   JOIN {glossary} g ON g.id = cm.instance
                   JOIN {glossary_entries} ge ON (ge.glossaryid = g.id AND ge.id = f.itemid)
-                 WHERE f.contextid = ? AND f.component = ? AND f.filearea = ?
-              ORDER BY ge.concept, f.itemid";
-        $params = array($this->context->instanceid, $this->context->id, $this->component, $this->filearea);
+                 WHERE f.contextid = :contextid
+                  AND f.component = :component
+                  AND f.filearea = :filearea';
+        $params = array(
+            'modulename' => 'glossary',
+            'instanceid' => $this->context->instanceid,
+            'contextid' => $this->context->id,
+            'component' => $this->component,
+            'filearea' => $this->filearea);
+        if (!$returnemptyfolders) {
+            $sql .= ' AND f.filename <> :emptyfilename';
+            $params['emptyfilename'] = '.';
+        }
+        list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f');
+        $sql .= ' '.$sql2;
+        $params = array_merge($params, $params2);
+        if ($countonly !== false) {
+            $sql .= ' ORDER BY ge.concept, f.itemid';
+        }
 
         $rs = $DB->get_recordset_sql($sql, $params);
         $children = array();
-        foreach ($rs as $file) {
-            if ($child = $this->browser->get_file_info($this->context, 'mod_glossary', $this->filearea, $file->itemid)) {
+        foreach ($rs as $record) {
+            if ($child = $this->browser->get_file_info($this->context, 'mod_glossary', $this->filearea, $record->itemid)) {
                 $children[] = $child;
             }
+            if ($countonly !== false && count($children) >= $countonly) {
+                break;
+            }
         }
         $rs->close();
-
+        if ($countonly !== false) {
+            return count($children);
+        }
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        return $this->get_filtered_children($extensions, false);
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      *
index 6eb6ebb..04b16fb 100644 (file)
@@ -273,18 +273,79 @@ class imscp_file_info extends file_info {
      * @return array of file_info instances
      */
     public function get_children() {
+        return $this->get_filtered_children('*', false, true);
+    }
+
+    /**
+     * Help function to return files matching extensions or their count
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @param bool|int $countonly if false returns the children, if an int returns just the
+     *    count of children but stops counting when $countonly number of children is reached
+     * @param bool $returnemptyfolders if true returns items that don't have matching files inside
+     * @return array|int array of file_info instances or the count
+     */
+    private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
         global $DB;
+        $params = array('contextid' => $this->context->id,
+            'component' => 'mod_imscp',
+            'filearea' => $this->filearea);
+        $sql = 'SELECT DISTINCT itemid
+                    FROM {files}
+                    WHERE contextid = :contextid
+                    AND component = :component
+                    AND filearea = :filearea';
+        if (!$returnemptyfolders) {
+            $sql .= ' AND filename <> :emptyfilename';
+            $params['emptyfilename'] = '.';
+        }
+        list($sql2, $params2) = $this->build_search_files_sql($extensions);
+        $sql .= ' '.$sql2;
+        $params = array_merge($params, $params2);
+        if ($countonly !== false) {
+            $sql .= ' ORDER BY itemid';
+        }
 
+        $rs = $DB->get_recordset_sql($sql, $params);
         $children = array();
-        $itemids = $DB->get_records('files', array('contextid'=>$this->context->id, 'component'=>'mod_imscp', 'filearea'=>$this->filearea), 'itemid', "DISTINCT itemid");
-        foreach ($itemids as $itemid=>$unused) {
-            if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $itemid)) {
+        foreach ($rs as $record) {
+            if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $record->itemid)) {
                 $children[] = $child;
+                if ($countonly !== false && count($children) >= $countonly) {
+                    break;
+                }
             }
         }
+        $rs->close();
+        if ($countonly !== false) {
+            return count($children);
+        }
         return $children;
     }
 
+    /**
+     * Returns list of children which are either files matching the specified extensions
+     * or folders that contain at least one such file.
+     *
+     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
+     * @return array of file_info instances
+     */
+    public function get_non_empty_children($extensions = '*') {
+        return $this->get_filtered_children($extensions, false);
+    }
+
+    /**
+     * Returns the number of children which are either files matching the specified extensions
+     * or folders containing at least one such file.
+     *
+     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
+     * @param int $limit stop counting after at least $limit non-empty children are found
+     * @return int
+     */
+    public function count_non_empty_children($extensions = '*', $limit = 1) {
+        return $this->get_filtered_children($extensions, $limit);
+    }
+
     /**
      * Returns parent file_info instance
      * @return file_info or null for root
diff --git a/mod/lesson/importppt.php b/mod/lesson/importppt.php
deleted file mode 100644 (file)
index 84bd61b..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-<?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/>.
-
-/**
- * This is a very rough importer for powerpoint slides
- * Export a powerpoint presentation with powerpoint as html pages
- * Do it with office 2002 (I think?) and no special settings
- * Then zip the directory with all of the html pages
- * and the zip file is what you want to upload
- *
- * The script supports book and lesson.
- *
- * @package    mod
- * @subpackage lesson
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- **/
-
-/** include required files */
-require_once("../../config.php");
-require_once($CFG->dirroot.'/mod/lesson/locallib.php');
-require_once($CFG->dirroot.'/mod/lesson/importpptlib.php');
-
-$id     = required_param('id', PARAM_INT);         // Course Module ID
-$pageid = optional_param('pageid', '', PARAM_INT); // Page ID
-
-$url = new moodle_url('/mod/lesson/importppt.php', array('id'=>$id));
-if ($pageid !== '') {
-    $url->param('pageid', $pageid);
-}
-$PAGE->set_url($url);
-
-$cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);;
-$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
-$lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
-
-$modname = 'lesson';
-$mod = $cm;
-require_login($course, false, $cm);
-
-require_login($course, false, $cm);
-$context = get_context_instance(CONTEXT_MODULE, $cm->id);
-require_capability('mod/lesson:edit', $context);
-
-$strimportppt = get_string("importppt", "lesson");
-$strlessons = get_string("modulenameplural", "lesson");
-
-$data = new stdClass;
-$data->id = $cm->id;
-$data->pageid = $pageid;
-$mform = new lesson_importppt_form();
-$mform->set_data($data);
-
-if ($data = $mform->get_data()) {
-    $manager = lesson_page_type_manager::get($lesson);
-    if (!$filename = $mform->get_new_filename('pptzip')) {
-        print_error('invalidfile', 'lesson');
-    }
-    if (!$package = $mform->save_stored_file('pptzip', $context->id, 'mod_lesson', 'ppt_imports', $lesson->id, '/', $filename, true)) {
-        print_error('unabletosavefile', 'lesson');
-    }
-    // extract package content
-    $packer = get_file_packer('application/zip');
-    $package->extract_to_storage($packer, $context->id, 'mod_lesson', 'imported_files', $lesson->id, '/');
-
-    $fs = get_file_storage();
-    if ($files = $fs->get_area_files($context->id, 'mod_lesson', 'imported_files', $lesson->id)) {
-
-        $pages = array();
-        foreach ($files as $key=>$file) {
-            if ($file->get_mimetype() != 'text/html') {
-                continue;
-            }
-            $filenameinfo = pathinfo($file->get_filepath().$file->get_filename());
-
-            $page = new stdClass;
-            $page->title = '';
-            $page->contents = array();
-            $page->images = array();
-            $page->source = $filenameinfo['basename'];
-
-            $string = strip_tags($file->get_content(),'<div><img>');
-            $imgs = array();
-            preg_match_all("/<img[^>]*(src\=\"(".$filenameinfo['filename']."\_image[^>^\"]*)\"[^>]*)>/i", $string, $imgs);
-            foreach ($imgs[2] as $img) {
-                $imagename = basename($img);
-                foreach ($files as $file) {
-                    if ($imagename === $file->get_filename()) {
-                        $page->images[] = clone($file);
-                    }
-                }
-            }
-
-            $matches = array();
-            // this will look for a non nested tag that is closed
-            // want to allow <b><i>(maybe more) tags but when we do that
-            // the preg_match messes up.
-            preg_match_all("/(<([\w]+)[^>]*>)([^<\\2>]*)(<\/\\2>)/", $string, $matches);
-            $countmatches = count($matches[1]);
-            for($i = 0; $i < $countmatches; $i++) { // go through all of our div matches
-
-                $class = lesson_importppt_isolate_class($matches[1][$i]); // first step in isolating the class
-
-                // check for any static classes
-                switch ($class) {
-                    case 'T':  // class T is used for Titles
-                        $page->title = $matches[3][$i];
-                        break;
-                    case 'B':  // I would guess that all bullet lists would start with B then go to B1, B2, etc
-                    case 'B1': // B1-B4 are just insurance, should just hit B and all be taken care of
-                    case 'B2':
-                    case 'B3':
-                    case 'B4':
-                        $page->contents[] = lesson_importppt_build_list($matches, '<ul>', $i, 0);  // this is a recursive function that will grab all the bullets and rebuild the list in html
-                        break;
-                    default:
-                        if ($matches[3][$i] != '&#13;') {  // odd crap generated... sigh
-                            if (substr($matches[3][$i], 0, 1) == ':') {  // check for leading :    ... hate MS ...
-                                $page->contents[] = substr($matches[3][$i], 1);  // get rid of :
-                            } else {
-                                $page->contents[] = $matches[3][$i];
-                            }
-                        }
-                        break;
-                }
-            }
-            $pages[] = $page;
-        }
-
-        $branchtables = lesson_create_objects($pages, $lesson->id);
-
-        // first set up the prevpageid and nextpageid
-        if (empty($pageid)) { // adding it to the top of the lesson
-            $prevpageid = 0;
-            // get the id of the first page.  If not found, then no pages in the lesson
-            if (!$nextpageid = $DB->get_field('lesson_pages', 'id', array('prevpageid' => 0, 'lessonid' => $lesson->id))) {
-                $nextpageid = 0;
-            }
-        } else {
-            // going after an actual page
-            $prevpageid = $pageid;
-            $nextpageid = $DB->get_field('lesson_pages', 'nextpageid', array('id' => $pageid));
-        }
-
-        foreach ($branchtables as $branchtable) {
-
-            // set the doubly linked list
-            $branchtable->page->nextpageid = $nextpageid;
-            $branchtable->page->prevpageid = $prevpageid;
-
-            // insert the page
-            $id = $DB->insert_record('lesson_pages', $branchtable->page);
-
-            if (!empty($branchtable->page->images)) {
-                $changes = array('contextid'=>$context->id, 'component'=>'mod_lesson', 'filearea'=>'page_contents', 'itemid'=>$id, 'timemodified'=>time());
-                foreach ($branchtable->page->images as $image) {
-                    $fs->create_file_from_storedfile($changes, $image);
-                }
-            }
-
-            // update the link of the page previous to the one we just updated
-            if ($prevpageid != 0) {  // if not the first page
-                $DB->set_field("lesson_pages", "nextpageid", $id, array("id" => $prevpageid));
-            }
-
-            // insert the answers
-            foreach ($branchtable->answers as $answer) {
-                $answer->pageid = $id;
-                $DB->insert_record('lesson_answers', $answer);
-            }
-
-            $prevpageid = $id;
-        }
-
-        // all done with inserts.  Now check to update our last page (this is when we import between two lesson pages)
-        if ($nextpageid != 0) {  // if the next page is not the end of lesson
-            $DB->set_field("lesson_pages", "prevpageid", $id, array("id" => $nextpageid));
-        }
-    }
-
-    // Remove all unzipped files!
-    $fs->delete_area_files($context->id, 'mod_lesson', 'imported_files', $lesson->id);
-
-    redirect("$CFG->wwwroot/mod/$modname/view.php?id=$cm->id", get_string('pptsuccessfullimport', 'lesson'), 5);
-}
-
-$PAGE->navbar->add($strimportppt);
-$PAGE->set_title($strimportppt);
-$PAGE->set_heading($strimportppt);
-echo $OUTPUT->header();
-
-/// Print upload form
-echo $OUTPUT->heading_with_help($strimportppt, 'importppt', 'lesson');
-echo $OUTPUT->box_start('generalbox boxaligncenter');
-$mform->display();
-echo $OUTPUT->box_end();
-echo $OUTPUT->footer();
diff --git a/mod/lesson/importpptlib.php b/mod/lesson/importpptlib.php
deleted file mode 100644 (file)
index 8550e61..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-<?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/>.
-
-/**
- * Contains functions used by importppt.php that naturally pertain to importing
- * powerpoint presentations into the lesson module
- *
- * @package    mod
- * @subpackage lesson
- * @copyright  2009 Sam Hemelryk
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- **/
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * A recursive function to build a html list
- *
- * @param array $matches
- * @param string $list
- * @param int $i
- * @param int $depth
- * @return string
- */
-function lesson_importppt_build_list(array &$matches, $list, &$i, $depth) {
-    while($i < count($matches[1])) {
-
-        $class = lesson_importppt_isolate_class($matches[1][$i]);
-
-        if (strstr($class, 'B')) {  // make sure we are still working with bullet classes
-            if ($class == 'B') {
-                $this_depth = 0;  // calling class B depth 0
-            } else {
-                // set the depth number.  So B1 is depth 1 and B2 is depth 2 and so on
-                $this_depth = substr($class, 1);
-                if (!is_numeric($this_depth)) {
-                    print_error('invalidnum');
-                }
-            }
-            if ($this_depth < $depth) {
-                // we are moving back a level in the nesting
-                break;
-            }
-            if ($this_depth > $depth) {
-                // we are moving in a lvl in nesting
-                $list .= '<ul>';
-                $list = lesson_importppt_build_list($matches, $list, $i, $this_depth);
-                // once we return back, should go to the start of the while
-                continue;
-            }
-            // no depth changes, so add the match to our list
-            if ($cleanstring = lesson_importppt_clean_text($matches[3][$i])) {
-                $list .= '<li>'.lesson_importppt_clean_text($matches[3][$i]).'</li>';
-            }
-            $i++;
-        } else {
-            // not a B class, so get out of here...
-            break;
-        }
-    }
-    // end the list and return it
-    $list .= '</ul>';
-    return $list;
-
-}
-
-/**
- * Given an html tag, this function will
- *
- * @param string $string
- * @return string
- */
-function lesson_importppt_isolate_class($string) {
-    if($class = strstr($string, 'class=')) { // first step in isolating the class
-        $class = substr($class, strpos($class, '=')+1);  // this gets rid of <div blawblaw class=  there are no "" or '' around the class name   ...sigh...
-        if (strstr($class, ' ')) {
-            // spaces found, so cut off everything off after the first space
-            return substr($class, 0, strpos($class, ' '));
-        } else {
-            // no spaces so nothing else in the div tag, cut off the >
-            return substr($class, 0, strpos($class, '>'));
-        }
-    } else {
-        // no class defined in the tag
-        return '';
-    }
-}
-
-/**
- * This function strips off the random chars that ppt puts infront of bullet lists
- *
- * @param string $string
- * @return string
- */
-function lesson_importppt_clean_text($string) {
-    $chop = 1; // default: just a single char infront of the content
-
-    // look for any other crazy things that may be infront of the content
-    if (strstr($string, '&lt;') and strpos($string, '&lt;') == 0) {  // look for the &lt; in the sting and make sure it is in the front
-        $chop = 4;  // increase the $chop
-    }
-    // may need to add more later....
-
-    $string = substr($string, $chop);
-
-    if ($string != '&#13;') {
-        return $string;
-    } else {
-        return false;
-    }
-}
-
-/**
- *  Creates objects an object with the page and answers that are to be inserted into the database
- *
- * @param array $pageobjects
- * @param int $lessonid
- * @return array
- */
-function lesson_create_objects($pageobjects, $lessonid) {
-
-    $branchtables = array();
-    $branchtable = new stdClass;
-
-    // all pages have this info
-    $page = new stdClass();
-    $page->lessonid = $lessonid;
-    $page->prevpageid = 0;
-    $page->nextpageid = 0;
-    $page->qtype = LESSON_PAGE_BRANCHTABLE;
-    $page->qoption = 0;
-    $page->layout = 1;
-    $page->display = 1;
-    $page->timecreated = time();
-    $page->timemodified = 0;
-
-    // all answers are the same
-    $answer = new stdClass();
-    $answer->lessonid = $lessonid;
-    $answer->jumpto = LESSON_NEXTPAGE;
-    $answer->grade = 0;
-    $answer->score = 0;
-    $answer->flags = 0;
-    $answer->timecreated = time();
-    $answer->timemodified = 0;
-    $answer->answer = "Next";
-    $answer->response = "";
-
-    $answers[] = clone($answer);
-
-    $answer->jumpto = LESSON_PREVIOUSPAGE;
-    $answer->answer = "Previous";
-
-    $answers[] = clone($answer);
-
-    $branchtable->answers = $answers;
-
-    $i = 1;
-
-    foreach ($pageobjects as $pageobject) {
-        if ($pageobject->title == '') {
-            $page->title = "Page $i";  // no title set so make a generic one
-        } else {
-            $page->title = $pageobject->title;
-        }
-        $page->contents = '';
-
-        // nab all the images first
-        $page->images = $pageobject->images;
-        foreach ($page->images as $image) {
-            $imagetag = '<img src="@@PLUGINFILE@@'.$image->get_filepath().$image->get_filename().'" title="'.$image->get_filename().'" />';
-            $imagetag = str_replace("\n", '', $imagetag);
-            $imagetag = str_replace("\r", '', $imagetag);
-            $imagetag = str_replace("'", '"', $imagetag);  // imgstyle
-            $page->contents .= $imagetag;
-        }
-        // go through the contents array and put <p> tags around each element and strip out \n which I have found to be unneccessary
-        foreach ($pageobject->contents as $content) {
-            $content = str_replace("\n", '', $content);
-            $content = str_replace("\r", '', $content);
-            $content = str_replace('&#13;', '', $content);  // puts in returns?
-            $content = '<p>'.$content.'</p>';
-            $page->contents .= $content;
-        }
-
-        $branchtable->page = clone($page);  // add the page
-        $branchtables[] = clone($branchtable);  // add it all to our array
-        $i++;
-    }
-
-    return $branchtables;
-}
-
-/**
- * Form displayed to the user asking them to select a file to upload
- *
- * @copyright  2009 Sam Hemelryk
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class lesson_importppt_form extends moodleform {
-
-    public function definition() {
-        global $COURSE;
-
-        $mform = $this->_form;
-
-        $mform->addElement('hidden', 'id');
-        $mform->setType('id', PARAM_INT);
-
-        $mform->addElement('hidden', 'pageid');
-        $mform->setType('pageid', PARAM_INT);
-
-        $filepickeroptions = array();
-        $filepickeroptions['filetypes'] = array('*.zip');
-        $filepickeroptions['maxbytes'] = $COURSE->maxbytes;
-        $mform->addElement('filepicker', 'pptzip', get_string('upload'), null, $filepickeroptions);
-        $mform->addRule('pptzip', null, 'required', null, 'client');
-
-        $this->add_action_buttons(null, get_string("uploadthisfile"));
-    }
-
-}
\ No newline at end of file
index 765b7ca..a37f40b 100644 (file)
@@ -364,9 +364,6 @@ class mod_lesson_renderer extends plugin_renderer_base {
         $importquestionsurl = new moodle_url('/mod/lesson/import.php',array('id'=>$this->page->cm->id, 'pageid'=>$prevpageid));
         $links[] = html_writer::link($importquestionsurl, get_string('importquestions', 'lesson'));
 
-        $importppturl = new moodle_url('/mod/lesson/importppt.php',array('id'=>$this->page->cm->id, 'pageid'=>$prevpageid));
-        $links[] = html_writer::link($importppturl, get_string('importppt', 'lesson'));
-
         $manager = lesson_page_type_manager::get($lesson);
         foreach ($manager->get_add_page_type_links($prevpageid) as $link) {
             $link['addurl']->param('firstpage', 1);
index e663af6..d75f2aa 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2012061700;       // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2012061701;       // The current module version (Date: YYYYMMDDXX)
 $module->requires  = 2012061700;    // Requires this Moodle version
 $module->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics)
 $module->cron      = 0;
index 3ab0f5a..23fe7d5 100644 (file)
@@ -116,6 +116,7 @@ table.quizattemptsummary .noreviewmessage {color: gray;}
 #page-mod-quiz-view .generalbox#feedback {width:70%;margin-left:auto;margin-right:auto;padding-bottom:15px;}
 #page-mod-quiz-view .generalbox#feedback h2 {margin: 0;}
 #page-mod-quiz-view .generalbox#feedback h3 {text-align: left;}
+#page-mod-quiz-view.dir-rtl .generalbox#feedback h3 {text-align: center;}
 #page-mod-quiz-view .generalbox#feedback .overriddennotice {text-align: center;font-size: 0.7em;}
 .quizstartbuttondiv.quizsecuremoderequired input { display: none; }
 .jsenabled .quizstartbuttondiv.quizsecuremoderequired input { display: inline; }
@@ -251,6 +252,7 @@ table#categoryquestions {width: 100%;overflow: hidden;table-layout: fixed;}
 #page-mod-quiz-edit div.question div.content .singlequestion .questiontext{display:inline-block;}
 #page-mod-quiz-edit div.question div.content .singlequestion .questionpreview{background-color:#eee;}
 #page-mod-quiz-edit div.question div.content .questiontype{display:block;clear:left;float:left;}
+#page-mod-quiz-edit.dir-rtl div.question div.content .questiontype {clear: right;float: right;}
 #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;}
@@ -412,4 +414,7 @@ bank window's title is prominent enough*/
 #page-mod-quiz-edit.dir-rtl .quizpagedelete {left: 0.2em;right:auto;}
 #page-mod-quiz-edit.dir-rtl div.quizcontents {clear: right;float: right;}
 #page-mod-quiz-edit.dir-rtl .questionbankwindow.block {float: left;}
-#page-question-edit.dir-rtl td.creatorname, #page-question-edit.dir-rtl td.modifiername {text-align: center;}
\ No newline at end of file
+#page-question-edit.dir-rtl td.creatorname, #page-question-edit.dir-rtl td.modifiername {text-align: center;}
+.path-question.dir-rtl input[name="maxmark"],
+.path-question-type.dir-rtl input[name="defaultmark"],
+#page-mod-quiz-edit.dir-rtl div.points input {direction: ltr;text-align: left;}
index 27b4722..c3de373 100644 (file)
@@ -1328,22 +1328,25 @@ function scorm_dndupload_handle($uploadinfo) {
  * @param array $grades grades array of users with grades - used when $userid = 0
  */
 function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COMPLETE, $grades = array()) {
-    if (!completion_info::is_enabled()) {
-        return;
-    }
-
     $course = new stdClass();
     $course->id = $scorm->course;
+    $completion = new completion_info($course);
+
+    // Check if completion is enabled site-wide, or for the course
+    if (!$completion->is_enabled()) {
+        return;
+    }
 
     $cm = get_coursemodule_from_instance('scorm', $scorm->id, $scorm->course);
-    if (!empty($cm)) {
-        $completion = new completion_info($course);
-        if (empty($userid)) { //we need to get all the relevant users from $grades param.
-            foreach ($grades as $grade) {
-                $completion->update_state($cm, $completionstate, $grade->userid);
-            }
-        } else {
-            $completion->update_state($cm, $completionstate, $userid);
+    if (empty($cm) || !$completion->is_enabled($cm)) {
+            return;
+    }
+
+    if (empty($userid)) { //we need to get all the relevant users from $grades param.
+        foreach ($grades as $grade) {
+            $completion->update_state($cm, $completionstate, $grade->userid);
         }
+    } else {
+        $completion->update_state($cm, $completionstate, $userid);
     }
 }
index 72411c8..6eac247 100644 (file)
@@ -21,6 +21,9 @@ optional - no changes needed:
   xxx_dndupload_register() and xxx_dndupload_handle($uploadinfo) see:
   http://docs.moodle.org/dev/Implementing_Course_drag_and_drop_upload_support_in_a_module
 
+* mod/data/lib.php data_get_all_recordids() now has two new optional variables:  $selectdata and $params.
+
+
 === 2.2 ===
 
 required changes in code:
@@ -29,12 +32,20 @@ required changes in code:
 * textlib->asort() replaced by specialized collatorlib::asort()
 * use new make_temp_directory() and make_cache_directory()
 
+optional - no changes needed:
+
+* mod/data/lib.php data_get_all_recordids() now has two new optional variables:  $selectdata and $params.
+
 
 === 2.1 ===
 
 required changes in code:
 * add new support for basic restore from 1.9
 
+optional - no changes needed:
+
+* mod/data/lib.php data_get_all_recordids() now has two new optional variables:  $selectdata and $params.
+
 
 === 2.0 ===
 
index 80fbdf4..d332563 100644 (file)
@@ -22,8 +22,10 @@ $string['backoldversion'] = 'Back to old version';
 $string['backpage'] = 'Back to page';
 $string['backtomapmenu'] = 'Back to map menu';
 $string['changerate'] = 'Do you wish to change it?';
+$string['cannoteditpage'] = 'You can not edit this page.';
 $string['cannotmanagefiles'] = 'You don\'t have permission to manage the wiki files.';
 $string['cannotviewfiles'] = 'You don\'t have permission to view the wiki files.';
+$string['cannotviewpage'] = 'You can not view this page.';
 $string['comparesel'] = 'Compare selected';
 $string['comments'] = 'Comments';
 $string['commentscount'] = 'Comments ({$a})';