Merge branch 'MDL-48894_registration' of git://github.com/andyjdavis/moodle
authorDavid Monllao <davidm@moodle.com>
Wed, 11 Feb 2015 01:11:06 +0000 (09:11 +0800)
committerDavid Monllao <davidm@moodle.com>
Wed, 11 Feb 2015 01:11:06 +0000 (09:11 +0800)
131 files changed:
admin/settings/plugins.php
admin/tool/timezoneimport/index.php
admin/webservice/forms.php
availability/tests/behat/display_availability.feature
backup/util/ui/tests/behat/duplicate_activities.feature
backup/util/ui/tests/behat/restore_moodle2_courses.feature
badges/badge.php
badges/mybadges.php
blocks/moodleblock.class.php
blocks/navigation/renderer.php
blocks/navigation/styles.css
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js
blocks/navigation/yui/src/navigation/js/navigation.js
blocks/tests/behat/hide_blocks.feature [new file with mode: 0644]
cache/classes/helper.php
cache/classes/loaders.php
cache/upgrade.txt
calendar/lib.php
calendar/renderer.php
calendar/view.php
course/changenumsections.php
course/editsection.php
course/externallib.php
course/format/lib.php
course/format/renderer.php
course/format/topics/lang/en/format_topics.php
course/format/topics/lib.php
course/format/topics/renderer.php
course/format/topics/tests/behat/edit_delete_sections.feature [new file with mode: 0644]
course/format/topics/tests/format_topics_test.php [new file with mode: 0644]
course/format/upgrade.txt
course/format/weeks/lang/en/format_weeks.php
course/format/weeks/lib.php
course/format/weeks/tests/behat/edit_delete_sections.feature [new file with mode: 0644]
course/format/weeks/tests/format_weeks_test.php [new file with mode: 0644]
course/lib.php
course/tests/behat/course_controls.feature
course/tests/behat/move_activities.feature
course/tests/behat/move_sections.feature
course/tests/behat/paged_course_navigation.feature
course/tests/courselib_test.php
course/tests/externallib_test.php
enrol/externallib.php
enrol/manual/db/services.php
enrol/manual/externallib.php
files/externallib.php
files/tests/externallib_test.php
grade/export/grade_export_form.php
grade/export/lib.php
grade/export/ods/dump.php
grade/export/ods/export.php
grade/export/txt/dump.php
grade/export/txt/export.php
grade/export/xls/dump.php
grade/export/xls/export.php
grade/export/xml/dump.php
grade/export/xml/export.php
grade/externallib.php
grade/report/grader/lang/en/gradereport_grader.php
grade/report/grader/lib.php
grade/report/upgrade.txt
grade/report/user/db/services.php [new file with mode: 0644]
grade/report/user/externallib.php [new file with mode: 0644]
grade/report/user/tests/externallib_test.php [new file with mode: 0644]
grade/report/user/version.php
group/externallib.php
install.php
lang/en/error.php
lang/en/grades.php
lang/en/moodle.php
lib/adminlib.php
lib/badgeslib.php
lib/behat/classes/behat_selectors.php
lib/blocklib.php
lib/classes/message/manager.php
lib/classes/message/message.php [new file with mode: 0644]
lib/db/services.php
lib/externallib.php
lib/gdlib.php
lib/messagelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/testing/lib.php
lib/tests/html_writer_test.php
lib/tests/message_test.php [new file with mode: 0644]
lib/timezone.txt
lib/upgrade.txt
login/signup.php
message/externallib.php
message/tests/behat/send_message.feature
mod/book/settings.php
mod/book/tests/behat/create_chapters.feature
mod/folder/settings.php
mod/forum/lib.php
mod/imscp/settings.php
mod/lesson/essay.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/pagetypes/matching.php
mod/lesson/report.php
mod/lesson/settings.php
mod/lesson/tests/behat/teacher_grade_essays.feature
mod/lesson/upgrade.txt
mod/page/settings.php
mod/resource/settings.php
mod/resource/view.php
mod/url/settings.php
notes/externallib.php
report/log/graph.php
repository/s3/lib.php
tag/lib.php
tag/tests/taglib_test.php
theme/base/style/core.css
theme/bootstrapbase/less/moodle/admin.less
theme/bootstrapbase/less/moodle/debug.less
theme/bootstrapbase/less/moodle/expendable.less
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/yui/build/moodle-theme_bootstrapbase-bootstrap/moodle-theme_bootstrapbase-bootstrap-debug.js
theme/bootstrapbase/yui/build/moodle-theme_bootstrapbase-bootstrap/moodle-theme_bootstrapbase-bootstrap-min.js
theme/bootstrapbase/yui/build/moodle-theme_bootstrapbase-bootstrap/moodle-theme_bootstrapbase-bootstrap.js
theme/bootstrapbase/yui/src/bootstrap/js/bootstrap.js
user/externallib.php
user/profile/lib.php
version.php
webservice/externallib.php
webservice/renderer.php
webservice/upgrade.txt

index 7a1551a..f0455b7 100644 (file)
@@ -282,9 +282,6 @@ if ($hassiteconfig) {
     $ADMIN->add('webservicesettings', new admin_externalpage('webservicedocumentation', new lang_string('wsdocapi', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/documentation.php", 'moodle/site:config', false));
     /// manage service
     $temp = new admin_settingpage('externalservices', new lang_string('externalservices', 'webservice'));
-    $temp->add(new admin_setting_enablemobileservice('enablemobilewebservice',
-            new lang_string('enablemobilewebservice', 'admin'),
-            new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), 0));
     $temp->add(new admin_setting_heading('manageserviceshelpexplaination', new lang_string('information', 'webservice'), new lang_string('servicehelpexplanation', 'webservice')));
     $temp->add(new admin_setting_manageexternalservices());
     $ADMIN->add('webservicesettings', $temp);
index 4277fd0..d6b6571 100644 (file)
         $a = new stdClass();
         $a->count = count($timezones);
         $a->source  = $importdone;
-        echo $OUTPUT->heading(get_string('importtimezonescount', 'tool_timezoneimport', $a), 3);
-
+        echo $OUTPUT->notification(get_string('importtimezonescount', 'tool_timezoneimport', $a), 'notifysuccess');
         echo $OUTPUT->continue_button(new moodle_url('/admin/index.php'));
 
         $timezonelist = array();
         }
         ksort($timezonelist);
 
-        echo "<br />";
-        echo $OUTPUT->box_start();
+        $timezonetable = new html_table();
+        $timezonetable->head = array(
+            get_string('timezone', 'moodle'),
+            get_string('entries', 'moodle')
+        );
+        $rows = array();
         foreach ($timezonelist as $name => $count) {
-            echo "$name ($count)<br />";
+            $row = new html_table_row(
+                array(
+                    new html_table_cell($name),
+                    new html_table_cell($count)
+                )
+            );
+            $rows[] = $row;
         }
-        echo $OUTPUT->box_end();
+        $timezonetable->data = $rows;
+        echo html_writer::table($timezonetable);
 
     } else {
-        echo $OUTPUT->heading(get_string('importtimezonesfailed', 'tool_timezoneimport'), 3);
+        echo $OUTPUT->notification(get_string('importtimezonesfailed', 'tool_timezoneimport'));
         echo $OUTPUT->continue_button(new moodle_url('/admin/index.php'));
     }
 
index 41c15c2..9465bf4 100644 (file)
@@ -190,7 +190,12 @@ class external_service_functions_form extends moodleform {
         foreach ($functions as $functionid => $functionname) {
             //retrieve full function information (including the description)
             $function = external_function_info($functionname);
-            $functions[$functionid] = $function->name . ':' . $function->description;
+            if (empty($function->deprecated)) {
+                $functions[$functionid] = $function->name . ':' . $function->description;
+            } else {
+                // Exclude the deprecated ones.
+                unset($functions[$functionid]);
+            }
         }
 
         $mform->addElement('searchableselector', 'fids', get_string('name'),
index 1efef1d..9043836 100644 (file)
@@ -109,17 +109,17 @@ Feature: display_availability
 
     # Page 1 display still there but should be dimmed and not a link.
     Then I should see "Page 1" in the "#section-1 .dimmed_text" "css_element"
-    And ".activityinstance a" "css_element" should not exist in the "#section-1" "css_element"
+    And ".activityinstance a" "css_element" should not exist in the "Topic 1" "section"
 
     # Date display should be present.
-    And I should see "Available until" in the "#section-1" "css_element"
+    And I should see "Available until" in the "Topic 1" "section"
 
     # Page 2 display not there at all
     And I should not see "Page 2" in the "region-main" "region"
 
     # Page 3 display and link
     And I should see "Page 3" in the "region-main" "region"
-    And ".activityinstance a" "css_element" should exist in the "#section-3" "css_element"
+    And ".activityinstance a" "css_element" should exist in the "Topic 3" "section"
 
   @javascript
   Scenario: Section availability display
index d839beb..37537ec 100644 (file)
@@ -34,6 +34,6 @@ Feature: Duplicate activities
       | Name | Duplicated database name |
       | Description | Duplicated database description |
     And I press "Save and return to course"
-    Then I should see "Original database name" in the "#section-1" "css_element"
-    And I should see "Duplicated database name" in the "#section-1" "css_element"
+    Then I should see "Original database name" in the "Topic 1" "section"
+    And I should see "Duplicated database name" in the "Topic 1" "section"
     And "Original database name" "link" should appear before "Duplicated database name" "link"
index 934e011..ec07143 100644 (file)
@@ -126,5 +126,5 @@ Feature: Restore Moodle 2 course backups
     And section "3" should be hidden
     And section "7" should be hidden
     And section "15" should be visible
-    And I should see "Test URL name" in the "#section-3" "css_element"
-    And I should see "Test forum name" in the "#section-1" "css_element"
+    And I should see "Test URL name" in the "Topic 3" "section"
+    And I should see "Test forum name" in the "Topic 1" "section"
index 29f8cb4..68cf109 100644 (file)
@@ -26,6 +26,7 @@
 
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->libdir . '/filelib.php');
 
 $id = required_param('hash', PARAM_ALPHANUM);
 $bake = optional_param('bake', 0, PARAM_BOOL);
@@ -37,12 +38,10 @@ $badge = new issued_badge($id);
 
 if ($bake && ($badge->recipient->id == $USER->id)) {
     $name = str_replace(' ', '_', $badge->badgeclass['name']) . '.png';
-    ob_start();
-    $file = badges_bake($id, $badge->badgeid);
-    header('Content-Type: image/png');
-    header('Content-Disposition: attachment; filename="'. $name .'"');
-    readfile($file);
-    ob_flush();
+    $filehash = badges_bake($id, $badge->badgeid, $USER->id, true);
+    $fs = get_file_storage();
+    $file = $fs->get_file_by_hash($filehash);
+    send_stored_file($file, 0, 0, true, array('filename' => $name));
 }
 
 $PAGE->set_url('/badges/badge.php', array('hash' => $id));
index 3f15a93..a1454da 100644 (file)
@@ -26,6 +26,7 @@
 
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->libdir . '/filelib.php');
 
 $page        = optional_param('page', 0, PARAM_INT);
 $search      = optional_param('search', '', PARAM_CLEAN);
@@ -71,17 +72,13 @@ if ($hide) {
     require_sesskey();
     $badge = new badge($download);
     $name = str_replace(' ', '_', $badge->name) . '.png';
-    ob_start();
-    $file = badges_bake($hash, $download);
-    header('Content-Type: image/png');
-    header('Content-Disposition: attachment; filename="'. $name .'"');
-    readfile($file);
-    ob_flush();
+    $filehash = badges_bake($hash, $download, $USER->id, true);
+    $fs = get_file_storage();
+    $file = $fs->get_file_by_hash($filehash);
+    send_stored_file($file, 0, 0, true, array('filename' => $name));
 } else if ($downloadall) {
     require_sesskey();
-    ob_start();
     badges_download($USER->id);
-    ob_flush();
 }
 
 $context = context_user::instance($USER->id);
index 0061a9b..333893d 100644 (file)
@@ -547,9 +547,21 @@ class block_base {
             && $page->context->contextlevel == CONTEXT_USER // Page belongs to a user
             && $page->context->instanceid == $USER->id // Page belongs to this user
             && $page->pagetype == 'my-index') { // Ensure we are on the My Moodle page
-            $capability = 'block/' . $this->name() . ':myaddinstance';
-            return $this->has_add_block_capability($page, $capability)
-                    && has_capability('moodle/my:manageblocks', $page->context);
+
+            // If the block cannot be displayed on /my it is ok if the myaddinstance capability is not defined.
+            $formats = $this->applicable_formats();
+            // Is 'my' explicitly forbidden?
+            // If 'all' has not been allowed, has 'my' been explicitly allowed?
+            if ((isset($formats['my']) && $formats['my'] == false)
+                || (empty($formats['all']) && empty($formats['my']))) {
+
+                // Block cannot be added to /my regardless of capabilities.
+                return false;
+            } else {
+                $capability = 'block/' . $this->name() . ':myaddinstance';
+                return $this->has_add_block_capability($page, $capability)
+                       && has_capability('moodle/my:manageblocks', $page->context);
+            }
         }
 
         $capability = 'block/' . $this->name() . ':addinstance';
index c92ef41..74cc088 100644 (file)
@@ -51,7 +51,7 @@ class block_navigation_renderer extends plugin_renderer_base {
     /**
      * Produces a navigation node for the navigation tree
      *
-     * @param array $items
+     * @param navigation_node[] $items
      * @param array $attrs
      * @param int $expansionlimit
      * @param array $options
@@ -59,13 +59,12 @@ class block_navigation_renderer extends plugin_renderer_base {
      * @return string
      */
     protected function navigation_node($items, $attrs=array(), $expansionlimit=null, array $options = array(), $depth=1) {
-
-        // exit if empty, we don't want an empty ul element
-        if (count($items)==0) {
+        // Exit if empty, we don't want an empty ul element.
+        if (count($items) === 0) {
             return '';
         }
 
-        // array of nested li elements
+        // Turn our navigation items into list items.
         $lis = array();
         foreach ($items as $item) {
             if (!$item->display && !$item->contains_active_node()) {
@@ -86,10 +85,13 @@ class block_navigation_renderer extends plugin_renderer_base {
 
             if ($hasicon) {
                 $icon = $this->output->render($item->icon);
+                // Because an icon is being used we're going to wrap the actual content in a span.
+                // This will allow designers to create columns for the content, as we've done in styles.css.
+                $content = $icon . html_writer::span($content, 'item-content-wrap');
             } else {
                 $icon = '';
             }
-            $content = $icon.$content; // use CSS for spacing of icons
+
             if ($item->helpbutton !== null) {
                 $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton'));
             }
@@ -116,13 +118,16 @@ class block_navigation_renderer extends plugin_renderer_base {
                 $link->text = $icon.$link->text;
                 $link->attributes = array_merge($link->attributes, $attributes);
                 $content = $this->output->render($link);
-                $linkrendered = true;
             } else if ($item->action instanceof moodle_url) {
                 $content = html_writer::link($item->action, $content, $attributes);
             }
 
-            // this applies to the li item which contains all child lists too
+            // This applies to the li item which contains all child lists too.
             $liclasses = array($item->get_css_type(), 'depth_'.$depth);
+
+            // Class attribute on the div item which only contains the item content.
+            $divclasses = array('tree_item');
+
             $liexpandable = array();
             if ($item->has_children() && (!$item->forceopen || $item->collapse)) {
                 $liclasses[] = 'collapsed';
@@ -130,30 +135,30 @@ class block_navigation_renderer extends plugin_renderer_base {
             if ($isbranch) {
                 $liclasses[] = 'contains_branch';
                 $liexpandable = array('aria-expanded' => in_array('collapsed', $liclasses) ? "false" : "true");
-            } else if ($hasicon) {
-                $liclasses[] = 'item_with_icon';
-            }
-            if ($item->isactive === true) {
-                $liclasses[] = 'current_branch';
-            }
-            $liattr = array('class' => join(' ',$liclasses)) + $liexpandable;
-            // class attribute on the div item which only contains the item content
-            $divclasses = array('tree_item');
-            if ($isbranch) {
                 $divclasses[] = 'branch';
             } else {
                 $divclasses[] = 'leaf';
             }
             if ($hasicon) {
+                // Add this class if the item has an icon, whether it is a branch or not.
+                $liclasses[] = 'item_with_icon';
                 $divclasses[] = 'hasicon';
             }
+            if ($item->isactive === true) {
+                $liclasses[] = 'current_branch';
+            }
             if (!empty($item->classes) && count($item->classes)>0) {
                 $divclasses[] = join(' ', $item->classes);
             }
+
+            // Now build attribute arrays.
+            $liattr = array('class' => join(' ', $liclasses)) + $liexpandable;
             $divattr = array('class'=>join(' ', $divclasses));
             if (!empty($item->id)) {
                 $divattr['id'] = $item->id;
             }
+
+            // Create the structure.
             $content = html_writer::tag('p', $content, $divattr);
             if ($isexpandable) {
                 $content .= $this->navigation_node($item->children, array(), $expansionlimit, $options, $depth+1);
@@ -165,11 +170,14 @@ class block_navigation_renderer extends plugin_renderer_base {
             $lis[] = $content;
         }
 
-        if (count($lis)) {
-            return html_writer::tag('ul', implode("\n", $lis), $attrs);
-        } else {
+        if (count($lis) === 0) {
+            // There is still a chance, despite having items, that nothing had content and no list items were created.
             return '';
         }
+
+        // We used to separate using new lines, however we don't do that now, instead we'll save a few chars.
+        // The source is complex already anyway.
+        return html_writer::tag('ul', implode('', $lis), $attrs);
     }
 
 }
index c25945c..c35c764 100644 (file)
-/** General display rules **/
-.block_navigation .block_tree {margin:5px;padding-left:0px;overflow:visible;}
-.block_navigation .block_tree li {margin:3px;list-style: none;padding:0;}
-.block_navigation .block_tree li.item_with_icon > p {position:relative; padding-left: 21px;}
-.block_navigation .block_tree li.item_with_icon > p img,
-.block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
-.block_navigation .block_tree li > p.hasicon img {vertical-align:middle;position:absolute;left:0;top:-1px;width:16px;height:16px;}
-.block_navigation .block_tree li.item_with_icon.contains_branch > p img {left:16px;}
-.block_navigation .block_tree .type_activity > p.branch.hasicon,
-.block_navigation .block_tree .type_activity > p.emptybranch.hasicon,
-.block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-left:37px;}
-
-.block_navigation .block_tree li ul {padding-left:0;margin:0;}
-.block_navigation .block_tree li.depth_2 ul {padding-left:16px;margin:0;}
-.block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
-.block_navigation .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
-
-.block_navigation .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 0;background-repeat: no-repeat;}
-.block_navigation .block_tree .tree_item.branch.navigation_node {background-image:none;padding-left:0;}
-.block_navigation .block_tree .type_activity > .tree_item.emptybranch,
-.block_navigation .block_tree .type_activity > .tree_item.branch {background-image:none;position:relative;}
-.block_navigation .block_tree .type_activity > .tree_item.hasicon.emptybranch img,
-.block_navigation .block_tree .type_activity > .tree_item.branch img {left: 16px;}
-.block_navigation .block_tree .root_node.leaf {padding-left:0px;}
-.block_navigation .block_tree .active_tree_node {font-weight:bold;}
-.block_navigation .block_tree .depth_1.current_branch ul {font-weight:normal;}
-
-.dock .block_navigation .tree_item {white-space: nowrap;}
-
-.jsenabled .block_navigation .block_tree .tree_item.branch {cursor:pointer;}
-.jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0 0;background-repeat: no-repeat;}
-.jsenabled .block_navigation .block_tree .collapsed ul {display: none;}
-.jsenabled .block_navigation .block_tree .type_activity > .tree_item.branch {background-image: url([[pix:t/expanded]]);}
-.jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
-.jsenabled .block_navigation .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
-
-/** JavaScript state rules **/
-.jsenabled .block_navigation.dock_on_load,
-.block_navigation .block_tree_box .requiresjs {display:none;}
-.jsenabled .block_navigation .block_tree_box .requiresjs {display:inline;}
-
-/** Internet explorer specific rules **/
-.ie6 .block_navigation .block_tree .tree_item {width:100%;}
-
-/** Overide for RTL layout **/
-.dir-rtl .block_navigation .block_tree li.depth_2 ul {padding-left: 0; padding-right: 16px;}
-.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
-.dir-rtl .block_navigation .block_tree .tree_item {padding-right: 21px;text-align:right;}
-
-.dir-rtl .block_navigation .block_tree .tree_item.branch {background-position: center right;}
-
-.dir-rtl .block_navigation .block_tree,
-.dir-rtl .block_navigation .block_tree li ul,
-.dir-rtl .block_navigation .block_tree .navigation_node.tree_item.branch,
-.dir-rtl .block_navigation .block_tree .root_node.leaf {padding-right:0;}
-
-.dir-rtl .block_navigation .block_tree li.item_with_icon > p img,
-.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
-.dir-rtl .block_navigation .block_tree li > p.hasicon img {left:auto; right:0;}
-.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > p img {left: auto; right:16px;}
-.dir-rtl .block_navigation .block_tree .type_activity > p.branch.hasicon,
-.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-right:37px; padding-left: 0;}
-.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {right: 16px; left: auto;}
-
-.jsenabled.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
-.jsenabled.dir-rtl .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
+.block_navigation .block_tree {
+    margin: 0;
+    list-style: none;
+}
+
+.block_navigation .block_tree .depth_1 > .tree_item.branch,
+.block_navigation .block_tree .depth_1 > .tree_item.emptybranch {
+    padding-left: 0;
+    background-image: none;
+}
+
+.block_navigation .block_tree .tree_item {
+    margin: 3px 0;
+    background-position: 0 50%;
+    background-repeat: no-repeat;
+}
+
+.block_navigation .block_tree .tree_item.branch {
+    padding-left: 21px;
+    cursor: pointer;
+    background-image: url('[[pix:t/expanded]]');
+}
+
+.block_navigation .block_tree .tree_item.emptybranch {
+    padding-left: 21px;
+    background-image: url('[[pix:t/collapsed_empty]]');
+}
+
+.block_navigation .block_tree .tree_item.loadingbranch {
+    background-image: url('[[pix:i/loading_small]]');
+}
+
+.block_navigation .block_tree .tree_item img {
+    width: 16px;
+    height: 16px;
+    margin-top: 3px;
+    margin-right: 5px;
+    vertical-align: top;
+}
+
+.block_navigation .block_tree .tree_item.active_tree_node {
+    font-weight: bold;
+}
+
+.block_navigation .block_tree .tree_item.hasicon {
+    white-space: nowrap;
+}
+
+.block_navigation .block_tree .tree_item.hasicon .item-content-wrap {
+    display: inline-block;
+    white-space: normal;
+}
+
+.block_navigation .block_tree ul {
+    margin: 0;
+}
+
+.block_navigation .block_tree ul ul {
+    margin: 0 0 0 16px;
+    list-style: none;
+}
+
+.jsenabled .block_navigation .block_tree li.collapsed ul {
+    display: none;
+}
+
+.jsenabled .block_navigation .block_tree li.collapsed .tree_item.branch {
+    background-image: url('[[pix:t/collapsed]]');
+}
+
+.jsenabled .block_navigation.dock_on_load {
+    display: none;
+}
+
+.dir-rtl .block_navigation .block_tree .depth_1 .tree_item {
+    padding-left: 0;
+}
+
+.dir-rtl .block_navigation .block_tree .tree_item {
+    background-position: 100% 50%;
+}
+
+.dir-rtl .block_navigation .block_tree .tree_item.branch {
+    padding-right: 21px;
+    padding-left: 0;
+}
+
+.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {
+    padding-right: 21px;
+    padding-left: 0;
+    background-image: url('[[pix:t/collapsed_empty_rtl]]');
+}
+
+.dir-rtl .block_navigation .block_tree .tree_item img {
+    margin-right: 0;
+    margin-left: 5px;
+}
+
+.dir-rtl .block_navigation .block_tree ul {
+    margin: 0 16px 0 0;
+}
+
+.dir-rtl.jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {
+    background-image: url('[[pix:t/collapsed_rtl]]');
+}
index 2073700..2d58702 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js differ
index 962ebaa..7c67d98 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js differ
index 0f46ccd..b661bfd 100644 (file)
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js and b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js differ
index e237023..3678bff 100644 (file)
@@ -481,6 +481,9 @@ BRANCH.prototype = {
      * This function creates a DOM structure for the branch and then injects
      * it into the navigation tree at the correct point.
      *
+     * It is important that this is kept in check with block_navigation_renderer::navigation_node as that produces
+     * the same thing as this but on the php side.
+     *
      * @method draw
      * @chainable
      * @param {Node} element
@@ -492,6 +495,7 @@ BRANCH.prototype = {
         var branchli = Y.Node.create('<li></li>');
         var link = this.get('link');
         var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
+        var name;
         if (!link) {
             //add tab focus if not link (so still one focus per menu node).
             // it was suggested to have 2 foci. one for the node and one for the link in MDL-27428.
@@ -506,10 +510,11 @@ BRANCH.prototype = {
         // Prepare the icon, should be an object representing a pix_icon
         var branchicon = false;
         var icon = this.get('icon');
-        if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY)) {
+        if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY || this.get('type') === NODETYPE.RESOURCE)) {
             branchicon = Y.Node.create('<img alt="" />');
             branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
             branchli.addClass('item_with_icon');
+            branchp.addClass('hasicon');
             if (icon.alt) {
                 branchicon.setAttribute('alt', icon.alt);
             }
@@ -527,8 +532,11 @@ BRANCH.prototype = {
             var branchspan = Y.Node.create('<span></span>');
             if (branchicon) {
                 branchspan.appendChild(branchicon);
+                name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
+            } else {
+                name = this.get('name');
             }
-            branchspan.append(this.get('name'));
+            branchspan.append(name);
             if (this.get('hidden')) {
                 branchspan.addClass('dimmed_text');
             }
@@ -537,8 +545,11 @@ BRANCH.prototype = {
             var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
             if (branchicon) {
                 branchlink.appendChild(branchicon);
+                name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
+            } else {
+                name = this.get('name');
             }
-            branchlink.append(this.get('name'));
+            branchlink.append(name);
             if (this.get('hidden')) {
                 branchlink.addClass('dimmed');
             }
diff --git a/blocks/tests/behat/hide_blocks.feature b/blocks/tests/behat/hide_blocks.feature
new file mode 100644 (file)
index 0000000..72822ba
--- /dev/null
@@ -0,0 +1,28 @@
+@core @core_block
+Feature: Block visibility
+  In order to configure blocks visibility
+  As a teacher
+  I need to show and hide blocks on a page
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And I log in as "admin"
+    And I am on homepage
+    And I follow "Course 1"
+    And I follow "Turn editing on"
+
+  @javascript
+  Scenario: Hiding all blocks on the page should remove the column they're in
+    Given I open the "Search forums" blocks action menu
+    And I click on "Hide Search forums block" "link" in the "Search forums" "block"
+    And I open the "Latest news" blocks action menu
+    And I click on "Hide Latest news block" "link" in the "Latest news" "block"
+    And I open the "Upcoming events" blocks action menu
+    And I click on "Hide Upcoming events block" "link" in the "Upcoming events" "block"
+    And I open the "Recent activity" blocks action menu
+    When I click on "Hide Recent activity block" "link" in the "Recent activity" "block"
+    Then ".empty-region-side-post" "css_element" should not exist in the "body" "css_element"
+    And I follow "Turn editing off"
+    And ".empty-region-side-post" "css_element" should exist in the "body" "css_element"
index 4f0586b..e272ee4 100644 (file)
@@ -346,9 +346,10 @@ class cache_helper {
     /**
      * Ensure that the stats array is ready to collect information for the given store and definition.
      * @param string $store
-     * @param string $definition
+     * @param string $definition A string that identifies the definition.
+     * @param int $mode One of cache_store::MODE_*. Since 2.9.
      */
-    protected static function ensure_ready_for_stats($store, $definition) {
+    protected static function ensure_ready_for_stats($store, $definition, $mode = cache_store::MODE_APPLICATION) {
         // This function is performance-sensitive, so exit as quickly as possible
         // if we do not need to do anything.
         if (isset(self::$stats[$definition][$store])) {
@@ -356,14 +357,17 @@ class cache_helper {
         }
         if (!array_key_exists($definition, self::$stats)) {
             self::$stats[$definition] = array(
-                $store => array(
-                    'hits' => 0,
-                    'misses' => 0,
-                    'sets' => 0,
+                'mode' => $mode,
+                'stores' => array(
+                    $store => array(
+                        'hits' => 0,
+                        'misses' => 0,
+                        'sets' => 0,
+                    )
                 )
             );
         } else if (!array_key_exists($store, self::$stats[$definition])) {
-            self::$stats[$definition][$store] = array(
+            self::$stats[$definition]['stores'][$store] = array(
                 'hits' => 0,
                 'misses' => 0,
                 'sets' => 0,
@@ -371,43 +375,80 @@ class cache_helper {
         }
     }
 
+    /**
+     * Returns a string to describe the definition.
+     *
+     * This method supports the definition as a string due to legacy requirements.
+     * It is backwards compatible when a string is passed but is not accurate.
+     *
+     * @since 2.9
+     * @param cache_definition|string $definition
+     * @return string
+     */
+    protected static function get_definition_stat_id_and_mode($definition) {
+        if (!($definition instanceof cache_definition)) {
+            // All core calls to this method have been updated, this is the legacy state.
+            // We'll use application as the default as that is the most common, really this is not accurate of course but
+            // at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
+            // be none) I think that is fine.
+            debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER);
+            return array((string)$definition, cache_store::MODE_APPLICATION);
+        }
+        return array($definition->get_id(), $definition->get_mode());
+    }
+
     /**
      * Record a cache hit in the stats for the given store and definition.
      *
+     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
+     * cache_definition instance. It is preferable to pass a cache definition instance.
+     *
      * @internal
-     * @param string $store
-     * @param string $definition
+     * @param cache_definition $store
+     * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
+     *      actual cache_definition object now.
      * @param int $hits The number of hits to record (by default 1)
      */
     public static function record_cache_hit($store, $definition, $hits = 1) {
-        self::ensure_ready_for_stats($store, $definition);
-        self::$stats[$definition][$store]['hits'] += $hits;
+        list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
+        self::ensure_ready_for_stats($store, $definitionstr, $mode);
+        self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
     }
 
     /**
      * Record a cache miss in the stats for the given store and definition.
      *
+     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
+     * cache_definition instance. It is preferable to pass a cache definition instance.
+     *
      * @internal
      * @param string $store
-     * @param string $definition
+     * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
+     *      actual cache_definition object now.
      * @param int $misses The number of misses to record (by default 1)
      */
     public static function record_cache_miss($store, $definition, $misses = 1) {
-        self::ensure_ready_for_stats($store, $definition);
-        self::$stats[$definition][$store]['misses'] += $misses;
+        list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
+        self::ensure_ready_for_stats($store, $definitionstr, $mode);
+        self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
     }
 
     /**
      * Record a cache set in the stats for the given store and definition.
      *
+     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
+     * cache_definition instance. It is preferable to pass a cache definition instance.
+     *
      * @internal
      * @param string $store
-     * @param string $definition
+     * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
+     *      actual cache_definition object now.
      * @param int $sets The number of sets to record (by default 1)
      */
     public static function record_cache_set($store, $definition, $sets = 1) {
-        self::ensure_ready_for_stats($store, $definition);
-        self::$stats[$definition][$store]['sets'] += $sets;
+        list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
+        self::ensure_ready_for_stats($store, $definitionstr, $mode);
+        self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
     }
 
     /**
index 6cd588a..9d623e2 100644 (file)
@@ -317,7 +317,7 @@ class cache implements cache_loader {
         $setaftervalidation = false;
         if ($result === false) {
             if ($this->perfdebug) {
-                cache_helper::record_cache_miss($this->storetype, $this->definition->get_id());
+                cache_helper::record_cache_miss($this->storetype, $this->definition);
             }
             if ($this->loader !== false) {
                 // We must pass the original (unparsed) key to the next loader in the chain.
@@ -329,7 +329,7 @@ class cache implements cache_loader {
             }
             $setaftervalidation = ($result !== false);
         } else if ($this->perfdebug) {
-            cache_helper::record_cache_hit($this->storetype, $this->definition->get_id());
+            cache_helper::record_cache_hit($this->storetype, $this->definition);
         }
         // 5. Validate strictness.
         if ($strictness === MUST_EXIST && $result === false) {
@@ -473,8 +473,8 @@ class cache implements cache_loader {
                     $hits++;
                 }
             }
-            cache_helper::record_cache_hit($this->storetype, $this->definition->get_id(), $hits);
-            cache_helper::record_cache_miss($this->storetype, $this->definition->get_id(), $misses);
+            cache_helper::record_cache_hit($this->storetype, $this->definition, $hits);
+            cache_helper::record_cache_miss($this->storetype, $this->definition, $misses);
         }
 
         // Return the result. Phew!
@@ -500,7 +500,7 @@ class cache implements cache_loader {
      */
     public function set($key, $data) {
         if ($this->perfdebug) {
-            cache_helper::record_cache_set($this->storetype, $this->definition->get_id());
+            cache_helper::record_cache_set($this->storetype, $this->definition);
         }
         if ($this->loader !== false) {
             // We have a loader available set it there as well.
@@ -649,7 +649,7 @@ class cache implements cache_loader {
         }
         $successfullyset = $this->store->set_many($data);
         if ($this->perfdebug && $successfullyset) {
-            cache_helper::record_cache_set($this->storetype, $this->definition->get_id(), $successfullyset);
+            cache_helper::record_cache_set($this->storetype, $this->definition, $successfullyset);
         }
         return $successfullyset;
     }
@@ -1039,7 +1039,7 @@ class cache implements cache_loader {
         }
         if ($result) {
             if ($this->perfdebug) {
-                cache_helper::record_cache_hit('** static acceleration **', $this->definition->get_id());
+                cache_helper::record_cache_hit('** static acceleration **', $this->definition);
             }
             if ($this->staticaccelerationsize > 1 && $this->staticaccelerationcount > 1) {
                 // Check to see if this is the last item on the static acceleration keys array.
@@ -1053,7 +1053,7 @@ class cache implements cache_loader {
             return $result;
         } else {
             if ($this->perfdebug) {
-                cache_helper::record_cache_miss('** static acceleration **', $this->definition->get_id());
+                cache_helper::record_cache_miss('** static acceleration **', $this->definition);
             }
             return false;
         }
@@ -1779,7 +1779,7 @@ class cache_session extends cache {
         // 4. Load if from the loader/datasource if we don't already have it.
         if ($result === false) {
             if ($this->perfdebug) {
-                cache_helper::record_cache_miss($this->storetype, $this->get_definition()->get_id());
+                cache_helper::record_cache_miss($this->storetype, $this->get_definition());
             }
             if ($this->get_loader() !== false) {
                 // We must pass the original (unparsed) key to the next loader in the chain.
@@ -1794,7 +1794,7 @@ class cache_session extends cache {
                 $this->set($key, $result);
             }
         } else if ($this->perfdebug) {
-            cache_helper::record_cache_hit($this->storetype, $this->get_definition()->get_id());
+            cache_helper::record_cache_hit($this->storetype, $this->get_definition());
         }
         // 5. Validate strictness.
         if ($strictness === MUST_EXIST && $result === false) {
@@ -1838,7 +1838,7 @@ class cache_session extends cache {
             $loader->set($key, $data);
         }
         if ($this->perfdebug) {
-            cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id());
+            cache_helper::record_cache_set($this->storetype, $this->get_definition());
         }
         if (is_object($data) && $data instanceof cacheable_object) {
             $data = new cache_cached_object($data);
@@ -1962,8 +1962,8 @@ class cache_session extends cache {
                     $hits++;
                 }
             }
-            cache_helper::record_cache_hit($this->storetype, $this->get_definition()->get_id(), $hits);
-            cache_helper::record_cache_miss($this->storetype, $this->get_definition()->get_id(), $misses);
+            cache_helper::record_cache_hit($this->storetype, $this->get_definition(), $hits);
+            cache_helper::record_cache_miss($this->storetype, $this->get_definition(), $misses);
         }
         return $return;
 
@@ -2040,7 +2040,7 @@ class cache_session extends cache {
         }
         $successfullyset = $this->get_store()->set_many($data);
         if ($this->perfdebug && $successfullyset) {
-            cache_helper::record_cache_set($this->storetype, $definitionid, $successfullyset);
+            cache_helper::record_cache_set($this->storetype, $this->get_definition(), $successfullyset);
         }
         return $successfullyset;
     }
index 760438e..1c1b133 100644 (file)
@@ -12,6 +12,7 @@ Information provided here is intended especially for developers.
    - cache::make Argument 4 (final arg) is now unused.
 * cache_config_phpunittest has been renamed to cache_config_testing
 * New method cache_store::ready_to_be_used_for_testing() that returns true|false if the store is suitable and ready for use as the primary store during unit and acceptance tests.
+* cache_helper::get_stats structure we changed to include the cache mode.
 
 === 2.7 ===
 * cache_store::is_ready is no longer abstract, calling cache_store::are_requirements_met by default.
index 39704f0..4882914 100644 (file)
@@ -991,7 +991,7 @@ function calendar_filter_controls_element(moodle_url $url, $type) {
         $str = get_string('show'.$typeforhumans.'events', 'calendar');
     }
     $content = html_writer::start_tag('li', array('class' => 'calendar_event'));
-    $content .= html_writer::start_tag('a', array('href' => $url));
+    $content .= html_writer::start_tag('a', array('href' => $url, 'rel' => 'nofollow'));
     $content .= html_writer::tag('span', $icon, array('class' => $class));
     $content .= html_writer::tag('span', $str, array('class' => 'eventname'));
     $content .= html_writer::end_tag('a');
index bc9d7d5..46e6c56 100644 (file)
@@ -176,7 +176,7 @@ class core_calendar_renderer extends plugin_renderer_base {
 
         if (empty($events)) {
             // There is nothing to display today.
-            $output .= $this->output->heading(get_string('daywithnoevents', 'calendar'), 3);
+            $output .= html_writer::span(get_string('daywithnoevents', 'calendar'), 'calendar-information calendar-no-results');
         } else {
             $output .= html_writer::start_tag('div', array('class' => 'eventlist'));
             $underway = array();
@@ -194,7 +194,8 @@ class core_calendar_renderer extends plugin_renderer_base {
 
             // Then, show a list of all events that just span this day
             if (!empty($underway)) {
-                $output .= $this->output->heading(get_string('spanningevents', 'calendar'), 3);
+                $output .= html_writer::span(get_string('spanningevents', 'calendar'),
+                    'calendar-information calendar-span-multiple-days');
                 foreach ($underway as $event) {
                     $event->time = calendar_format_event_time($event, time(), null, false, $calendar->timestamp_today());
                     $output .= $this->event($event);
@@ -536,7 +537,7 @@ class core_calendar_renderer extends plugin_renderer_base {
             }
             $output .= html_writer::end_tag('div');
         } else {
-            $output .= $this->output->heading(get_string('noupcomingevents', 'calendar'));
+            $output .= html_writer::span(get_string('noupcomingevents', 'calendar'), 'calendar-information calendar-no-results');
         }
 
         return $output;
index 469cbf9..5121a98 100644 (file)
@@ -124,6 +124,7 @@ $calendar->add_sidecalendar_blocks($renderer, true, $view);
 echo $OUTPUT->header();
 echo $renderer->start_layout();
 echo html_writer::start_tag('div', array('class'=>'heightcontainer'));
+echo $OUTPUT->heading(get_string('calendar', 'calendar'));
 
 switch($view) {
     case 'day':
@@ -169,4 +170,4 @@ if (!empty($CFG->enablecalendarexport)) {
 echo $OUTPUT->container_end();
 echo html_writer::end_tag('div');
 echo $renderer->complete_layout();
-echo $OUTPUT->footer();
\ No newline at end of file
+echo $OUTPUT->footer();
index deed367..beeaea9 100644 (file)
@@ -52,8 +52,8 @@ if (isset($courseformatoptions['numsections'])) {
     // Don't go less than 0, intentionally redirect silently (for the case of
     // double clicks).
     if ($courseformatoptions['numsections'] >= 0) {
-        course_get_format($course)->update_course_format_options(
-                array('numsections' => $courseformatoptions['numsections']));
+        update_course((object)array('id' => $course->id,
+            'numsections' => $courseformatoptions['numsections']));
     }
 }
 
index 6b8ce0a..b1e01db 100644 (file)
@@ -29,6 +29,7 @@ require_once($CFG->libdir . '/formslib.php');
 
 $id = required_param('id', PARAM_INT);    // course_sections.id
 $sectionreturn = optional_param('sr', 0, PARAM_INT);
+$deletesection = optional_param('delete', 0, PARAM_BOOL);
 
 $PAGE->set_url('/course/editsection.php', array('id'=>$id, 'sr'=> $sectionreturn));
 
@@ -43,6 +44,41 @@ require_capability('moodle/course:update', $context);
 // Get section_info object with all availability options.
 $sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
 
+// Deleting the section.
+if ($deletesection) {
+    $cancelurl = course_get_url($course, $sectioninfo, array('sr' => $sectionreturn));
+    if (course_can_delete_section($course, $sectioninfo)) {
+        $confirm = optional_param('confirm', false, PARAM_BOOL) && confirm_sesskey();
+        if ($confirm) {
+            course_delete_section($course, $sectioninfo, true);
+            $courseurl = course_get_url($course, 0, array('sr' => $sectionreturn));
+            redirect($courseurl);
+        } else {
+            if (get_string_manager()->string_exists('deletesection', 'format_' . $course->format)) {
+                $strdelete = get_string('deletesection', 'format_' . $course->format);
+            } else {
+                $strdelete = get_string('deletesection');
+            }
+            $PAGE->navbar->add($strdelete);
+            $PAGE->set_title($strdelete);
+            $PAGE->set_heading($course->fullname);
+            echo $OUTPUT->header();
+            echo $OUTPUT->box_start('noticebox');
+            $optionsyes = array('id' => $id, 'confirm' => 1, 'delete' => 1, 'sesskey' => sesskey());
+            $deleteurl = new moodle_url('/course/editsection.php', $optionsyes);
+            $formcontinue = new single_button($deleteurl, get_string('yes'));
+            $formcancel = new single_button($cancelurl, get_string('no'), 'get');
+            echo $OUTPUT->confirm(get_string('confirmdeletesection', '',
+                get_section_name($course, $sectioninfo)), $formcontinue, $formcancel);
+            echo $OUTPUT->box_end();
+            echo $OUTPUT->footer();
+            exit;
+        }
+    } else {
+        notice(get_string('nopermissions', 'error', get_string('deletesection')), $cancelurl);
+    }
+}
+
 $editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true);
 $mform = course_get_format($course->id)->editsection_form($PAGE->url,
         array('cs' => $sectioninfo, 'editoroptions' => $editoroptions));
index c5f5b34..ff3dbfa 100644 (file)
@@ -217,6 +217,7 @@ class core_course_external extends external_api {
                                     'id' => new external_value(PARAM_INT, 'activity id'),
                                     'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
                                     'name' => new external_value(PARAM_RAW, 'activity module name'),
+                                    'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
                                     'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
                                     'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
                                     'modicon' => new external_value(PARAM_URL, 'activity icon url'),
@@ -1982,6 +1983,15 @@ class moodle_course_external extends external_api {
         return core_course_external::get_courses_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_courses_is_deprecated() {
+        return true;
+    }
+
     /**
      * Returns description of method parameters
      *
@@ -2019,4 +2029,12 @@ class moodle_course_external extends external_api {
         return core_course_external::create_courses_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function create_courses_is_deprecated() {
+        return true;
+    }
 }
index eaeaff7..51709b9 100644 (file)
@@ -935,6 +935,85 @@ abstract class format_base {
      */
     public function section_get_available_hook(section_info $section, &$available, &$availableinfo) {
     }
+
+    /**
+     * Whether this format allows to delete sections
+     *
+     * If format supports deleting sections it is also recommended to define language string
+     * 'deletesection' inside the format.
+     *
+     * Do not call this function directly, instead use {@link course_can_delete_section()}
+     *
+     * @param int|stdClass|section_info $section
+     * @return bool
+     */
+    public function can_delete_section($section) {
+        return false;
+    }
+
+    /**
+     * Deletes a section
+     *
+     * Do not call this function directly, instead call {@link course_delete_section()}
+     *
+     * @param int|stdClass|section_info $section
+     * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it.
+     * @return bool whether section was deleted
+     */
+    public function delete_section($section, $forcedeleteifnotempty = false) {
+        global $DB;
+        if (!$this->uses_sections()) {
+            // Not possible to delete section if sections are not used.
+            return false;
+        }
+        if (!is_object($section)) {
+            $section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section),
+                'id,section,sequence');
+        }
+        if (!$section || !$section->section) {
+            // Not possible to delete 0-section.
+            return false;
+        }
+
+        if (!$forcedeleteifnotempty && !empty($section->sequence)) {
+            return false;
+        }
+
+        $course = $this->get_course();
+
+        // Remove the marker if it points to this section.
+        if ($section->section == $course->marker) {
+            course_set_marker($course->id, 0);
+        }
+
+        $lastsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                            WHERE course = ?', array($course->id));
+
+        // Find out if we need to descrease the 'numsections' property later.
+        $courseformathasnumsections = array_key_exists('numsections',
+            $this->get_format_options());
+        $decreasenumsections = $courseformathasnumsections && ($section->section <= $course->numsections);
+
+        // Move the section to the end.
+        move_section_to($course, $section->section, $lastsection, true);
+
+        // Delete all modules from the section.
+        foreach (preg_split('/,/', $section->sequence, -1, PREG_SPLIT_NO_EMPTY) as $cmid) {
+            course_delete_module($cmid);
+        }
+
+        // Delete section and it's format options.
+        $DB->delete_records('course_format_options', array('sectionid' => $section->id));
+        $DB->delete_records('course_sections', array('id' => $section->id));
+        rebuild_course_cache($course->id, true);
+
+        // Descrease 'numsections' if needed.
+        if ($decreasenumsections) {
+            $this->update_course_format_options(array('numsections' => $course->numsections - 1));
+        }
+
+        return true;
+    }
 }
 
 /**
index d9afaa0..af8533c 100644 (file)
@@ -226,6 +226,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         }
 
         $coursecontext = context_course::instance($course->id);
+        $isstealth = isset($course->numsections) && ($section->section > $course->numsections);
 
         if ($onsectionpage) {
             $baseurl = course_get_url($course, $section->section);
@@ -237,7 +238,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $controls = array();
 
         $url = clone($baseurl);
-        if (has_capability('moodle/course:sectionvisibility', $coursecontext)) {
+        if (!$isstealth && has_capability('moodle/course:sectionvisibility', $coursecontext)) {
             if ($section->visible) { // Show the hide/show eye.
                 $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format);
                 $url->param('hide', $section->section);
@@ -255,7 +256,21 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             }
         }
 
-        if (!$onsectionpage && has_capability('moodle/course:movesections', $coursecontext)) {
+        if (course_can_delete_section($course, $section)) {
+            if (get_string_manager()->string_exists('deletesection', 'format_'.$course->format)) {
+                $strdelete = get_string('deletesection', 'format_'.$course->format);
+            } else {
+                $strdelete = get_string('deletesection');
+            }
+            $url = new moodle_url('/course/editsection.php', array('id' => $section->id,
+                'sr' => $onsectionpage ? $section->section : 0, 'delete' => 1));
+            $controls[] = html_writer::link($url,
+                html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/delete'),
+                    'class' => 'icon delete', 'alt' => $strdelete)),
+                array('title' => $strdelete));
+        }
+
+        if (!$isstealth && !$onsectionpage && has_capability('moodle/course:movesections', $coursecontext)) {
             $url = clone($baseurl);
             if ($section->section > 1) { // Add a arrow to move section up.
                 $url->param('section', $section->section);
@@ -530,7 +545,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $o = '';
         $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix orphaned hidden'));
         $o.= html_writer::tag('div', '', array('class' => 'left side'));
-        $o.= html_writer::tag('div', '', array('class' => 'right side'));
+        $course = course_get_format($this->page->course)->get_course();
+        $section = course_get_format($this->page->course)->get_section($sectionno);
+        $rightcontent = $this->section_right_content($section, $course, false);
+        $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side'));
         $o.= html_writer::start_tag('div', array('class' => 'content'));
         $o.= $this->output->heading(get_string('orphanedactivitiesinsectionno', '', $sectionno), 3, 'sectionname');
         return $o;
index f9766d9..37e2f11 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 $string['currentsection'] = 'This topic';
+$string['deletesection'] = 'Delete topic';
 $string['sectionname'] = 'Topic';
 $string['pluginname'] = 'Topics format';
 $string['section0name'] = 'General';
index cb0a7ed..034b58e 100644 (file)
@@ -315,8 +315,8 @@ class format_topics extends format_base {
      */
     public function update_course_format_options($data, $oldcourse = null) {
         global $DB;
+        $data = (array)$data;
         if ($oldcourse !== null) {
-            $data = (array)$data;
             $oldcourse = (array)$oldcourse;
             $options = $this->course_format_options();
             foreach ($options as $key => $unused) {
@@ -337,6 +337,30 @@ class format_topics extends format_base {
                 }
             }
         }
-        return $this->update_format_options($data);
+        $changed = $this->update_format_options($data);
+        if ($changed && array_key_exists('numsections', $data)) {
+            // If the numsections was decreased, try to completely delete the orphaned sections (unless they are not empty).
+            $numsections = (int)$data['numsections'];
+            $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                        WHERE course = ?', array($this->courseid));
+            for ($sectionnum = $maxsection; $sectionnum > $numsections; $sectionnum--) {
+                if (!$this->delete_section($sectionnum, false)) {
+                    break;
+                }
+            }
+        }
+        return $changed;
+    }
+
+    /**
+     * Whether this format allows to delete sections
+     *
+     * Do not call this function directly, instead use {@link course_can_delete_section()}
+     *
+     * @param int|stdClass|section_info $section
+     * @return bool
+     */
+    public function can_delete_section($section) {
+        return true;
     }
 }
index ffc7be6..6e2b43b 100644 (file)
@@ -97,8 +97,9 @@ class format_topics_renderer extends format_section_renderer_base {
         }
         $url->param('sesskey', sesskey());
 
+        $isstealth = $section->section > $course->numsections;
         $controls = array();
-        if (has_capability('moodle/course:setcurrentsection', $coursecontext)) {
+        if (!$isstealth && has_capability('moodle/course:setcurrentsection', $coursecontext)) {
             if ($course->marker == $section->section) {  // Show the "light globe" on/off.
                 $url->param('marker', 0);
                 $controls[] = html_writer::link($url,
diff --git a/course/format/topics/tests/behat/edit_delete_sections.feature b/course/format/topics/tests/behat/edit_delete_sections.feature
new file mode 100644 (file)
index 0000000..81e8a25
--- /dev/null
@@ -0,0 +1,85 @@
+@format @format_topics
+Feature: Sections can be edited and deleted in topics format
+  In order to rearrange my course contents
+  As a teacher
+  I need to edit and Delete topics
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email            |
+      | teacher1 | Teacher   | 1        | teacher1@asd.com |
+    And the following "courses" exist:
+      | fullname | shortname | format | coursedisplay | numsections |
+      | Course 1 | C1        | topics | 0             | 5           |
+    And the following "activities" exist:
+      | activity   | name                   | intro                         | course | idnumber    | section |
+      | assign     | Test assignment name   | Test assignment description   | C1     | assign1     | 0       |
+      | book       | Test book name         | Test book description         | C1     | book1       | 1       |
+      | chat       | Test chat name         | Test chat description         | C1     | chat1       | 4       |
+      | choice     | Test choice name       | Test choice description       | C1     | choice1     | 5       |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+
+  Scenario: Edit section summary in topics format
+    When I click on "Edit summary" "link" in the "li#section-2" "css_element"
+    And I set the following fields to these values:
+      | Summary | Welcome to section 2 |
+    And I press "Save changes"
+    Then I should see "Welcome to section 2" in the "li#section-2" "css_element"
+
+  Scenario: Edit section default name in topics format
+    When I click on "Edit summary" "link" in the "li#section-2" "css_element"
+    And I set the following fields to these values:
+      | Use default section name | 0                        |
+      | name                     | This is the second topic |
+    And I press "Save changes"
+    Then I should see "This is the second topic" in the "li#section-2" "css_element"
+    And I should not see "Topic 2" in the "li#section-2" "css_element"
+
+  Scenario: Deleting the last section in topics format
+    When I click on "Delete topic" "link" in the "li#section-5" "css_element"
+    Then I should see "Are you absolutely sure you want to delete \"Topic 5\"? All activities will be also deleted"
+    And I press "Yes"
+    And I should not see "Topic 5"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And the field "Number of sections" matches value "4"
+
+  Scenario: Deleting the middle section in topics format
+    When I click on "Delete topic" "link" in the "li#section-4" "css_element"
+    And I press "Yes"
+    Then I should not see "Topic 5"
+    And I should not see "Test chat name"
+    And I should see "Test choice name" in the "li#section-4" "css_element"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And the field "Number of sections" matches value "4"
+
+  Scenario: Deleting the orphaned section in topics format
+    When I follow "Reduce the number of sections"
+    Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element"
+    And I click on "Delete topic" "link" in the "li#section-5" "css_element"
+    And I press "Yes"
+    And I should not see "Topic 5"
+    And I should not see "Orphaned activities"
+    And "li#section-5" "css_element" should not exist
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And the field "Number of sections" matches value "4"
+
+  Scenario: Deleting a section when orphaned section is present in topics format
+    When I follow "Reduce the number of sections"
+    Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element"
+    And "li#section-5.orphaned" "css_element" should exist
+    And "li#section-4.orphaned" "css_element" should not exist
+    And I click on "Delete topic" "link" in the "li#section-1" "css_element"
+    And I press "Yes"
+    And I should not see "Test book name"
+    And I should see "Orphaned activities (section 4)" in the "li#section-4" "css_element"
+    And "li#section-5" "css_element" should not exist
+    And "li#section-4.orphaned" "css_element" should exist
+    And "li#section-3.orphaned" "css_element" should not exist
diff --git a/course/format/topics/tests/format_topics_test.php b/course/format/topics/tests/format_topics_test.php
new file mode 100644 (file)
index 0000000..09dea51
--- /dev/null
@@ -0,0 +1,64 @@
+<?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/>.
+
+/**
+ * format_topics related unit tests
+ *
+ * @package    format_topics
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/course/lib.php');
+
+/**
+ * format_topics related unit tests
+ *
+ * @package    format_topics
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_topics_testcase extends advanced_testcase {
+
+    public function test_update_course_numsections() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+
+        $course = $generator->create_course(array('numsections' => 10, 'format' => 'topics'),
+            array('createsections' => true));
+        $generator->create_module('assign', array('course' => $course, 'section' => 7));
+
+        $this->setAdminUser();
+
+        $this->assertEquals(11, $DB->count_records('course_sections', array('course' => $course->id)));
+
+        // Change the numsections to 8, last two sections did not have any activities, they should be deleted.
+        update_course((object)array('id' => $course->id, 'numsections' => 8));
+        $this->assertEquals(9, $DB->count_records('course_sections', array('course' => $course->id)));
+        $this->assertEquals(9, count(get_fast_modinfo($course)->get_section_info_all()));
+
+        // Change the numsections to 5, section 8 should be deleted but section 7 should remain as it has activities.
+        update_course((object)array('id' => $course->id, 'numsections' => 6));
+        $this->assertEquals(8, $DB->count_records('course_sections', array('course' => $course->id)));
+        $this->assertEquals(8, count(get_fast_modinfo($course)->get_section_info_all()));
+        $this->assertEquals(6, course_get_format($course)->get_course()->numsections);
+    }
+}
index 7916854..6919320 100644 (file)
@@ -2,6 +2,11 @@ This files describes API changes for course formats
 
 Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
 
+=== 2.9 ===
+* Course formats may support deleting sections, see MDL-10405 for more details.
+  format_section_renderer_base::section_edit_controls() is now also called for
+  stealth sections and it also returns "delete" control.
+
 === 2.8 ===
 * The activity chooser now uses M.course.format.get_sectionwrapperclass()
   to determine the section selector, rather than a hard-coded `li.section`.
index fd6c24d..86e24b7 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 $string['currentsection'] = 'This week';
+$string['deletesection'] = 'Delete week';
 $string['sectionname'] = 'Week';
 $string['pluginname'] = 'Weekly format';
 $string['section0name'] = 'General';
index 66152d2..59a58b3 100644 (file)
@@ -324,8 +324,8 @@ class format_weeks extends format_base {
      */
     public function update_course_format_options($data, $oldcourse = null) {
         global $DB;
+        $data = (array)$data;
         if ($oldcourse !== null) {
-            $data = (array)$data;
             $oldcourse = (array)$oldcourse;
             $options = $this->course_format_options();
             foreach ($options as $key => $unused) {
@@ -346,7 +346,19 @@ class format_weeks extends format_base {
                 }
             }
         }
-        return $this->update_format_options($data);
+        $changed = $this->update_format_options($data);
+        if ($changed && array_key_exists('numsections', $data)) {
+            // If the numsections was decreased, try to completely delete the orphaned sections (unless they are not empty).
+            $numsections = (int)$data['numsections'];
+            $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                        WHERE course = ?', array($this->courseid));
+            for ($sectionnum = $maxsection; $sectionnum > $numsections; $sectionnum--) {
+                if (!$this->delete_section($sectionnum, false)) {
+                    break;
+                }
+            }
+        }
+        return $changed;
     }
 
     /**
@@ -393,4 +405,16 @@ class format_weeks extends format_base {
         $dates = $this->get_section_dates($section);
         return (($timenow >= $dates->start) && ($timenow < $dates->end));
     }
+
+    /**
+     * Whether this format allows to delete sections
+     *
+     * Do not call this function directly, instead use {@link course_can_delete_section()}
+     *
+     * @param int|stdClass|section_info $section
+     * @return bool
+     */
+    public function can_delete_section($section) {
+        return true;
+    }
 }
diff --git a/course/format/weeks/tests/behat/edit_delete_sections.feature b/course/format/weeks/tests/behat/edit_delete_sections.feature
new file mode 100644 (file)
index 0000000..ae1524c
--- /dev/null
@@ -0,0 +1,88 @@
+@format @format_weeks
+Feature: Sections can be edited and deleted in weeks format
+  In order to rearrange my course contents
+  As a teacher
+  I need to edit and Delete weeks
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email            |
+      | teacher1 | Teacher   | 1        | teacher1@asd.com |
+    And the following "courses" exist:
+      | fullname | shortname | format | coursedisplay | numsections | startdate |
+      | Course 1 | C1        | weeks  | 0             | 5           | 957139200 |
+    And the following "activities" exist:
+      | activity   | name                   | intro                         | course | idnumber    | section |
+      | assign     | Test assignment name   | Test assignment description   | C1     | assign1     | 0       |
+      | book       | Test book name         | Test book description         | C1     | book1       | 1       |
+      | chat       | Test chat name         | Test chat description         | C1     | chat1       | 4       |
+      | choice     | Test choice name       | Test choice description       | C1     | choice1     | 5       |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+
+  Scenario: Edit section summary in weeks format
+    When I click on "Edit summary" "link" in the "li#section-2" "css_element"
+    And I set the following fields to these values:
+      | Summary | Welcome to section 2 |
+    And I press "Save changes"
+    Then I should see "Welcome to section 2" in the "li#section-2" "css_element"
+
+  Scenario: Edit section default name in weeks format
+    Given I should see "8 May - 14 May" in the "li#section-2" "css_element"
+    When I click on "Edit summary" "link" in the "li#section-2" "css_element"
+    And I set the following fields to these values:
+      | Use default section name | 0                       |
+      | name                     | This is the second week |
+    And I press "Save changes"
+    Then I should see "This is the second week" in the "li#section-2" "css_element"
+    And I should not see "8 May - 14 May" in the "li#section-2" "css_element"
+
+  Scenario: Deleting the last section in weeks format
+    Given I should see "29 May - 4 June" in the "li#section-5" "css_element"
+    When I click on "Delete week" "link" in the "li#section-5" "css_element"
+    Then I should see "Are you absolutely sure you want to delete \"29 May - 4 June\"? All activities will be also deleted"
+    And I press "Yes"
+    And I should not see "29 May - 4 June"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And the field "Number of sections" matches value "4"
+
+  Scenario: Deleting the middle section in weeks format
+    Given I should see "29 May - 4 June" in the "li#section-5" "css_element"
+    When I click on "Delete week" "link" in the "li#section-4" "css_element"
+    And I press "Yes"
+    Then I should not see "29 May - 4 June"
+    And I should not see "Test chat name"
+    And I should see "Test choice name" in the "li#section-4" "css_element"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And the field "Number of sections" matches value "4"
+
+  Scenario: Deleting the orphaned section in weeks format
+    When I follow "Reduce the number of sections"
+    Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element"
+    And I click on "Delete week" "link" in the "li#section-5" "css_element"
+    And I press "Yes"
+    And I should not see "29 May - 4 June"
+    And I should not see "Orphaned activities"
+    And "li#section-5" "css_element" should not exist
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And the field "Number of sections" matches value "4"
+
+  Scenario: Deleting a section when orphaned section is present in weeks format
+    When I follow "Reduce the number of sections"
+    Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element"
+    And "li#section-5.orphaned" "css_element" should exist
+    And "li#section-4.orphaned" "css_element" should not exist
+    And I click on "Delete week" "link" in the "li#section-1" "css_element"
+    And I press "Yes"
+    And I should not see "Test book name"
+    And I should see "Orphaned activities (section 4)" in the "li#section-4" "css_element"
+    And "li#section-5" "css_element" should not exist
+    And "li#section-4.orphaned" "css_element" should exist
+    And "li#section-3.orphaned" "css_element" should not exist
diff --git a/course/format/weeks/tests/format_weeks_test.php b/course/format/weeks/tests/format_weeks_test.php
new file mode 100644 (file)
index 0000000..a388b59
--- /dev/null
@@ -0,0 +1,64 @@
+<?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/>.
+
+/**
+ * format_weeks related unit tests
+ *
+ * @package    format_weeks
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/course/lib.php');
+
+/**
+ * format_weeks related unit tests
+ *
+ * @package    format_weeks
+ * @copyright  2015 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_weeks_testcase extends advanced_testcase {
+
+    public function test_update_course_numsections() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+
+        $course = $generator->create_course(array('numsections' => 10, 'format' => 'weeks'),
+            array('createsections' => true));
+        $generator->create_module('assign', array('course' => $course, 'section' => 7));
+
+        $this->setAdminUser();
+
+        $this->assertEquals(11, $DB->count_records('course_sections', array('course' => $course->id)));
+
+        // Change the numsections to 8, last two sections did not have any activities, they should be deleted.
+        update_course((object)array('id' => $course->id, 'numsections' => 8));
+        $this->assertEquals(9, $DB->count_records('course_sections', array('course' => $course->id)));
+        $this->assertEquals(9, count(get_fast_modinfo($course)->get_section_info_all()));
+
+        // Change the numsections to 5, section 8 should be deleted but section 7 should remain as it has activities.
+        update_course((object)array('id' => $course->id, 'numsections' => 6));
+        $this->assertEquals(8, $DB->count_records('course_sections', array('course' => $course->id)));
+        $this->assertEquals(8, count(get_fast_modinfo($course)->get_section_info_all()));
+        $this->assertEquals(6, course_get_format($course)->get_course()->numsections);
+    }
+}
index b8212d5..0dabb51 100644 (file)
@@ -1771,9 +1771,10 @@ function delete_mod_from_section($modid, $sectionid) {
  * @param object $course
  * @param int $section Section number (not id!!!)
  * @param int $destination
+ * @param bool $ignorenumsections
  * @return boolean Result
  */
-function move_section_to($course, $section, $destination) {
+function move_section_to($course, $section, $destination, $ignorenumsections = false) {
 /// Moves a whole course section up and down within the course
     global $USER, $DB;
 
@@ -1783,7 +1784,7 @@ function move_section_to($course, $section, $destination) {
 
     // compartibility with course formats using field 'numsections'
     $courseformatoptions = course_get_format($course)->get_format_options();
-    if ((array_key_exists('numsections', $courseformatoptions) &&
+    if ((!$ignorenumsections && array_key_exists('numsections', $courseformatoptions) &&
             ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) {
         return false;
     }
@@ -1825,6 +1826,57 @@ function move_section_to($course, $section, $destination) {
     return true;
 }
 
+/**
+ * This method will delete a course section and may delete all modules inside it.
+ *
+ * No permissions are checked here, use {@link course_can_delete_section()} to
+ * check if section can actually be deleted.
+ *
+ * @param int|stdClass $course
+ * @param int|stdClass|section_info $section
+ * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it.
+ * @return bool whether section was deleted
+ */
+function course_delete_section($course, $section, $forcedeleteifnotempty = true) {
+    return course_get_format($course)->delete_section($section, $forcedeleteifnotempty);
+}
+
+/**
+ * Checks if the current user can delete a section (if course format allows it and user has proper permissions).
+ *
+ * @param int|stdClass $course
+ * @param int|stdClass|section_info $section
+ * @return bool
+ */
+function course_can_delete_section($course, $section) {
+    if (is_object($section)) {
+        $section = $section->section;
+    }
+    if (!$section) {
+        // Not possible to delete 0-section.
+        return false;
+    }
+    // Course format should allow to delete sections.
+    if (!course_get_format($course)->can_delete_section($section)) {
+        return false;
+    }
+    // Make sure user has capability to update course and move sections.
+    $context = context_course::instance(is_object($course) ? $course->id : $course);
+    if (!has_all_capabilities(array('moodle/course:movesections', 'moodle/course:update'), $context)) {
+        return false;
+    }
+    // Make sure user has capability to delete each activity in this section.
+    $modinfo = get_fast_modinfo($course);
+    if (!empty($modinfo->sections[$section])) {
+        foreach ($modinfo->sections[$section] as $cmid) {
+            if (!has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 /**
  * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
  * an original position number and a target position number, rebuilds the array so that the
index c3a965e..d1e4def 100644 (file)
@@ -42,18 +42,18 @@ Feature: Course activity controls works as expected
     And I click on "Actions" "link" in the "Recent activity" "block"
     And I click on "Delete Recent activity block" "link"
     And I press "Yes"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name 1 |
       | Description | Test forum description 1 |
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name 2 |
       | Description | Test forum description 2 |
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I indent right "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I indent left "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I open "Test forum name 1" actions menu
     And I click on "Edit settings" "link" in the "Test forum name 1" activity
     And I should see "Updating Forum"
@@ -63,38 +63,38 @@ Feature: Course activity controls works as expected
       | Description | Just to check that I can edit the description |
       | Display description on course page | 1 |
     And I click on "Cancel" "button"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I open "Test forum name 1" actions menu
     And I click on "Hide" "link" in the "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I delete "Test forum name 1" activity
     And I should not see "Test forum name 1" in the "#region-main" "css_element"
     And I duplicate "Test forum name 2" activity editing the new copy with:
       | Forum name | Edited test forum name 2 |
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I should see "Test forum name 2"
     And I should see "Edited test forum name 2"
     And I hide section "1"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And section "1" should be hidden
     And all activities in section "1" should be hidden
     And I show section "1"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And section "1" should be visible
     And I add the "Section links" block
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I should see "1 2 3 4 5" in the "Section links" "block"
     And I click on "2" "link" in the "Section links" "block"
     And I <should_see_other_sections_following_block_sections_links> see "Test forum name 2"
 
     Examples:
-      | courseformat | coursedisplay | targetpage              | should_see_other_sections | should_see_other_sections_following_block_sections_links |
-      | topics       | 0             | "Course 1"              | should                    | should                                                   |
-      | topics       | 1             | "Topic 1"               | should not                | should not                                               |
-      | topics       | 1             | "Course 1"              | should                    | should not                                               |
-      | weeks        | 0             | "Course 1"              | should                    | should                                                   |
-      | weeks        | 1             | "1 January - 7 January" | should not                | should not                                               |
-      | weeks        | 1             | "Course 1"              | should                    | should not                                               |
+      | courseformat | coursedisplay | targetpage              | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage                |
+      | topics       | 0             | "Course 1"              | should                    | should                                                   | "Topic 2"                |
+      | topics       | 1             | "Topic 1"               | should not                | should not                                               | "Topic 2"                |
+      | topics       | 1             | "Course 1"              | should                    | should not                                               | "Topic 2"                |
+      | weeks        | 0             | "Course 1"              | should                    | should                                                   | "8 January - 14 January" |
+      | weeks        | 1             | "1 January - 7 January" | should not                | should not                                               | "8 January - 14 January" |
+      | weeks        | 1             | "Course 1"              | should                    | should not                                               | "8 January - 14 January" |
 
   Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected
     Given the following "users" exist:
@@ -122,51 +122,51 @@ Feature: Course activity controls works as expected
     And I click on "Actions" "link" in the "Recent activity" "block"
     And I click on "Delete Recent activity block" "link"
     And I press "Yes"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name 1 |
       | Description | Test forum description 1 |
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name 2 |
       | Description | Test forum description 2 |
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I indent right "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I indent left "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I click on "Edit settings" "link" in the "Test forum name 1" activity
     And I should see "Updating Forum"
     And I should see "Display description on course page"
     And I press "Save and return to course"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I click on "Hide" "link" in the "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I delete "Test forum name 1" activity
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I should not see "Test forum name 1" in the "#region-main" "css_element"
     And I duplicate "Test forum name 2" activity editing the new copy with:
       | Forum name | Edited test forum name 2 |
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I should see "Test forum name 2"
     And I should see "Edited test forum name 2"
     And I hide section "1"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And section "1" should be hidden
     And all activities in section "1" should be hidden
     And I show section "1"
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And section "1" should be visible
     And I add the "Section links" block
-    And "#section-2" "css_element" <should_see_other_sections> exist
+    And <belowpage> "section" <should_see_other_sections> exist
     And I should see "1 2 3 4 5" in the "Section links" "block"
     And I click on "2" "link" in the "Section links" "block"
     And I <should_see_other_sections_following_block_sections_links> see "Test forum name 2"
 
     Examples:
-      | courseformat | coursedisplay | targetpage              | should_see_other_sections | should_see_other_sections_following_block_sections_links |
-      | topics       | 0             | "Course 1"              | should                    | should                                                   |
-      | topics       | 1             | "Topic 1"               | should not                | should not                                               |
-      | topics       | 1             | "Course 1"              | should                    | should not                                               |
-      | weeks        | 0             | "Course 1"              | should                    | should                                                   |
-      | weeks        | 1             | "1 January - 7 January" | should not                | should not                                               |
-      | weeks        | 1             | "Course 1"              | should                    | should not                                               |
+      | courseformat | coursedisplay | targetpage              | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage                |
+      | topics       | 0             | "Course 1"              | should                    | should                                                   | "Topic 2"                |
+      | topics       | 1             | "Topic 1"               | should not                | should not                                               | "Topic 2"                |
+      | topics       | 1             | "Course 1"              | should                    | should not                                               | "Topic 2"                |
+      | weeks        | 0             | "Course 1"              | should                    | should                                                   | "8 January - 14 January" |
+      | weeks        | 1             | "1 January - 7 January" | should not                | should not                                               | "8 January - 14 January" |
+      | weeks        | 1             | "Course 1"              | should                    | should not                                               | "8 January - 14 January" |
index f2cde5b..2729055 100644 (file)
@@ -29,8 +29,8 @@ Feature: Activities can be moved between sections
 
   Scenario: Move activities in a single page course with Javascript disabled
     When I move "Test forum name" activity to section "2"
-    Then I should see "Test forum name" in the "#section-2" "css_element"
-    And I should not see "Test forum name" in the "#section-1" "css_element"
+    Then I should see "Test forum name" in the "Topic 2" "section"
+    And I should not see "Test forum name" in the "Topic 1" "section"
 
   Scenario: Move activities in the course home with Javascript disabled using paged mode
     Given I click on "Edit settings" "link" in the "Administration" "block"
@@ -38,8 +38,8 @@ Feature: Activities can be moved between sections
       | Course layout | Show one section per page |
     And I press "Save and display"
     When I move "Test forum name" activity to section "2"
-    Then I should see "Test forum name" in the "#section-2" "css_element"
-    And I should not see "Test forum name" in the "#section-1" "css_element"
+    Then I should see "Test forum name" in the "Topic 2" "section"
+    And I should not see "Test forum name" in the "Topic 1" "section"
 
   Scenario: Move activities in a course section with Javascript disabled using paged mode
     Given I click on "Edit settings" "link" in the "Administration" "block"
index a4772fe..932ba31 100644 (file)
@@ -23,9 +23,9 @@ Feature: Sections can be moved
       | Forum name | Test forum name |
       | Description | Test forum description |
     When I move down section "1"
-    Then I should see "Test forum name" in the "#section-2" "css_element"
+    Then I should see "Test forum name" in the "Topic 2" "section"
     And I move up section "2"
-    And I should see "Test forum name" in the "#section-1" "css_element"
+    And I should see "Test forum name" in the "Topic 1" "section"
 
   Scenario: Move up and down a section with Javascript disabled in the course home of a course using paged mode
     Given I click on "Edit settings" "link" in the "Administration" "block"
@@ -36,9 +36,9 @@ Feature: Sections can be moved
       | Forum name | Test forum name |
       | Description | Test forum description |
     When I move down section "1"
-    Then I should see "Test forum name" in the "#section-2" "css_element"
+    Then I should see "Test forum name" in the "Topic 2" "section"
     And I move up section "2"
-    And I should see "Test forum name" in the "#section-1" "css_element"
+    And I should see "Test forum name" in the "Topic 1" "section"
 
   Scenario: Sections can not be moved with Javascript disabled in a section page of a course using paged mode
     Given I click on "Edit settings" "link" in the "Administration" "block"
@@ -49,7 +49,7 @@ Feature: Sections can be moved
       | Forum name | Test forum name |
       | Description | Test forum description |
     When I follow "Topic 2"
-    Then "#section-1" "css_element" should not exist
-    And "#section-3" "css_element" should not exist
+    Then "Topic 1" "section" should not exist
+    And "Topic 3" "section" should not exist
     And "Move down" "link" should not exist
     And "Move up" "link" should not exist
index 0371c12..b6d66f9 100644 (file)
@@ -11,11 +11,11 @@ Feature: Course paged mode
       | Course 1 | C1 | 0 | <courseformat> | 1 | 3 |
     And I log in as "admin"
     And I follow "Course 1"
-    Then I click on <section2> "link" in the "#section-2" "css_element"
+    Then I click on <section2> "link" in the <section2> "section"
     And I follow "C1"
-    And I click on <section3> "link" in the "#section-3" "css_element"
+    And I click on <section3> "link" in the <section3> "section"
     And I follow "C1"
-    And I click on <section1> "link" in the "#section-1" "css_element"
+    And I click on <section1> "link" in the <section1> "section"
     And I should see <section1> in the "div.single-section" "css_element"
     And I should see <section2> in the ".single-section span.mdl-right" "css_element"
     And I should not see <prevunexistingsection> in the ".single-section" "css_element"
@@ -44,11 +44,11 @@ Feature: Course paged mode
       | Course 1 | C1 | 0 | <courseformat> | 1 | 3 |
     And I log in as "admin"
     And I follow "Course 1"
-    Then I click on <section2> "link" in the "#section-2" "css_element"
+    Then I click on <section2> "link" in the <section2> "section"
     And I follow "C1"
-    And I click on <section3> "link" in the "#section-3" "css_element"
+    And I click on <section3> "link" in the <section3> "section"
     And I follow "C1"
-    And I click on <section1> "link" in the "#section-1" "css_element"
+    And I click on <section1> "link" in the <section1> "section"
     And I should see <section1> in the "div.single-section" "css_element"
     And I should see <section2> in the ".single-section span.mdl-right" "css_element"
     And I should not see <prevunexistingsection> in the ".single-section" "css_element"
index b60e5ac..a40bea6 100644 (file)
@@ -859,6 +859,130 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals(3, $course->marker);
     }
 
+    public function test_course_can_delete_section() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+
+        $courseweeks = $generator->create_course(
+            array('numsections' => 5, 'format' => 'weeks'),
+            array('createsections' => true));
+        $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1));
+        $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2));
+
+        $coursetopics = $generator->create_course(
+            array('numsections' => 5, 'format' => 'topics'),
+            array('createsections' => true));
+
+        $coursesingleactivity = $generator->create_course(
+            array('format' => 'singleactivity'),
+            array('createsections' => true));
+
+        // Enrol student and teacher.
+        $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
+        $student = $generator->create_user();
+        $teacher = $generator->create_user();
+
+        $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']);
+        $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']);
+
+        $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']);
+        $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']);
+
+        $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']);
+        $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']);
+
+        // Teacher should be able to delete sections (except for 0) in topics and weeks format.
+        $this->setUser($teacher);
+
+        // For topics and weeks formats will return false for section 0 and true for any other section.
+        $this->assertFalse(course_can_delete_section($courseweeks, 0));
+        $this->assertTrue(course_can_delete_section($courseweeks, 1));
+
+        $this->assertFalse(course_can_delete_section($coursetopics, 0));
+        $this->assertTrue(course_can_delete_section($coursetopics, 1));
+
+        // For singleactivity course format no section can be deleted.
+        $this->assertFalse(course_can_delete_section($coursesingleactivity, 0));
+        $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
+
+        // Now let's revoke a capability from teacher to manage activity in section 1.
+        $modulecontext = context_module::instance($assign1->cmid);
+        assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'],
+            $modulecontext);
+        $modulecontext->mark_dirty();
+        $this->assertFalse(course_can_delete_section($courseweeks, 1));
+        $this->assertTrue(course_can_delete_section($courseweeks, 2));
+
+        // Student does not have permissions to delete sections.
+        $this->setUser($student);
+        $this->assertFalse(course_can_delete_section($courseweeks, 1));
+        $this->assertFalse(course_can_delete_section($coursetopics, 1));
+        $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
+    }
+
+    public function test_course_delete_section() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+
+        $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'),
+            array('createsections' => true));
+        $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0));
+        $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1));
+        $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
+        $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
+        $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3));
+        $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5));
+        $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6));
+
+        $this->setAdminUser();
+
+        // Attempt to delete non-existing section.
+        $this->assertFalse(course_delete_section($course, 10, false));
+        $this->assertFalse(course_delete_section($course, 9, true));
+
+        // Attempt to delete 0-section.
+        $this->assertFalse(course_delete_section($course, 0, true));
+        $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid)));
+
+        // Delete last section.
+        $this->assertTrue(course_delete_section($course, 6, true));
+        $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid)));
+        $this->assertEquals(5, course_get_format($course)->get_course()->numsections);
+
+        // Delete empty section.
+        $this->assertTrue(course_delete_section($course, 4, false));
+        $this->assertEquals(4, course_get_format($course)->get_course()->numsections);
+
+        // Delete section in the middle (2).
+        $this->assertFalse(course_delete_section($course, 2, false));
+        $this->assertTrue(course_delete_section($course, 2, true));
+        $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid)));
+        $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid)));
+        $this->assertEquals(3, course_get_format($course)->get_course()->numsections);
+        $this->assertEquals(array(0 => array($assign0->cmid),
+            1 => array($assign1->cmid),
+            2 => array($assign3->cmid),
+            3 => array($assign5->cmid)), get_fast_modinfo($course)->sections);
+
+        // Make last section orphaned.
+        update_course((object)array('id' => $course->id, 'numsections' => 2));
+        $this->assertEquals(2, course_get_format($course)->get_course()->numsections);
+
+        // Remove orphaned section.
+        $this->assertTrue(course_delete_section($course, 3, true));
+        $this->assertEquals(2, course_get_format($course)->get_course()->numsections);
+
+        // Remove marked section.
+        course_set_marker($course->id, 1);
+        $this->assertTrue(course_get_format($course)->is_section_current(1));
+        $this->assertTrue(course_delete_section($course, 1, true));
+        $this->assertFalse(course_get_format($course)->is_section_current(1));
+    }
+
     public function test_get_course_display_name_for_list() {
         global $CFG;
         $this->resetAfterTest(true);
index 2cbfcd1..7adad2c 100644 (file)
@@ -640,12 +640,14 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
                 $formattedtext = format_text($cm->content, FORMAT_HTML,
                     array('noclean' => true, 'para' => false, 'filter' => false));
                 $this->assertEquals($formattedtext, $module['description']);
+                $this->assertEquals($forumcm->instance, $module['instance']);
                 $testexecuted = $testexecuted + 1;
             } else if ($module['id'] == $labelcm->id and $module['modname'] == 'label') {
                 $cm = $modinfo->cms[$labelcm->id];
                 $formattedtext = format_text($cm->content, FORMAT_HTML,
                     array('noclean' => true, 'para' => false, 'filter' => false));
                 $this->assertEquals($formattedtext, $module['description']);
+                $this->assertEquals($labelcm->instance, $module['instance']);
                 $testexecuted = $testexecuted + 1;
             }
         }
index 619cea0..711a1e0 100644 (file)
@@ -922,6 +922,15 @@ class moodle_enrol_external extends external_api {
         );
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_enrolled_users_is_deprecated() {
+        return true;
+    }
+
     /**
      * Returns description of method parameters
      *
@@ -960,6 +969,14 @@ class moodle_enrol_external extends external_api {
         return core_enrol_external::get_users_courses_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_users_courses_is_deprecated() {
+        return true;
+    }
 
     /**
      * Returns description of method parameters
@@ -997,6 +1014,14 @@ class moodle_enrol_external extends external_api {
         return core_role_external::assign_roles_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function role_assign_is_deprecated() {
+        return true;
+    }
 
     /**
      * Returns description of method parameters
@@ -1033,4 +1058,13 @@ class moodle_enrol_external extends external_api {
     public static function role_unassign_returns() {
         return core_role_external::unassign_roles_returns();
     }
+
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function role_unassign_is_deprecated() {
+        return true;
+    }
 }
index 7b08046..87e4e30 100644 (file)
@@ -27,8 +27,8 @@ $functions = array(
 
     // === enrol related functions ===
     'moodle_enrol_manual_enrol_users' => array(
-        'classname'   => 'enrol_manual_external',
-        'methodname'  => 'enrol_users',
+        'classname'   => 'moodle_enrol_manual_external',
+        'methodname'  => 'manual_enrol_users',
         'classpath'   => 'enrol/manual/externallib.php',
         'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as enrol_manual_enrol_users()',
         'capabilities'=> 'enrol/manual:enrol',
index 7cec282..d24cc1c 100644 (file)
@@ -207,4 +207,12 @@ class moodle_enrol_manual_external extends external_api {
         return enrol_manual_external::enrol_users_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function manual_enrol_users_is_deprecated() {
+        return true;
+    }
 }
index fdf63c5..389cc73 100644 (file)
@@ -74,6 +74,7 @@ class core_files_external extends external_api {
      * @param string $contextlevel The context level for the file location.
      * @param int $instanceid The instance id for where the file is located.
      * @return array
+     * @since Moodle 2.9 Returns additional fields (timecreated, filesize, author, license)
      * @since Moodle 2.2
      */
     public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null,
@@ -139,6 +140,7 @@ class core_files_external extends external_api {
 
                 $params = $child->get_params();
                 $timemodified = $child->get_timemodified();
+                $timecreated = $child->get_timecreated();
 
                 if ($child->is_directory()) {
                     if ((is_null($modified)) or ($modified < $timemodified)) {
@@ -151,7 +153,11 @@ class core_files_external extends external_api {
                             'filename'  => $child->get_visible_name(),
                             'url'       => null,
                             'isdir'     => true,
-                            'timemodified' => $timemodified
+                            'timemodified' => $timemodified,
+                            'timecreated' => $timecreated,
+                            'filesize' => 0,
+                            'author' => null,
+                            'license' => null
                            );
                            $list[] = $node;
                     }
@@ -166,7 +172,11 @@ class core_files_external extends external_api {
                             'filename'  => $child->get_visible_name(),
                             'url'       => $child->get_url(),
                             'isdir'     => false,
-                            'timemodified' => $timemodified
+                            'timemodified' => $timemodified,
+                            'timecreated' => $timecreated,
+                            'filesize' => $child->get_filesize(),
+                            'author' => $child->get_author(),
+                            'license' => $child->get_license()
                         );
                            $list[] = $node;
                     }
@@ -181,6 +191,7 @@ class core_files_external extends external_api {
      * Returns description of get_files returns
      *
      * @return external_single_structure
+     * @since Moodle 2.9 Returns additional fields for files (timecreated, filesize, author, license)
      * @since Moodle 2.2
      */
     public static function get_files_returns() {
@@ -210,6 +221,10 @@ class core_files_external extends external_api {
                             'isdir'    => new external_value(PARAM_BOOL, ''),
                             'url'      => new external_value(PARAM_TEXT, ''),
                             'timemodified' => new external_value(PARAM_INT, ''),
+                            'timecreated' => new external_value(PARAM_INT, 'Time created', VALUE_OPTIONAL),
+                            'filesize' => new external_value(PARAM_INT, 'File size', VALUE_OPTIONAL),
+                            'author' => new external_value(PARAM_TEXT, 'File owner', VALUE_OPTIONAL),
+                            'license' => new external_value(PARAM_TEXT, 'File license', VALUE_OPTIONAL),
                         )
                     )
                 )
@@ -404,13 +419,18 @@ class moodle_file_external extends external_api {
      * @param int $itemid
      * @param string $filepath
      * @param string $filename
+     * @param int $modified timestamp to return files changed after this time.
+     * @param string $contextlevel The context level for the file location.
+     * @param int $instanceid The instance id for where the file is located.
      * @return array
      * @since Moodle 2.0
      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
      * @see core_files_external::get_files()
      */
-    public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename) {
-        return core_files_external::get_files($contextid, $component, $filearea, $itemid, $filepath, $filename);
+    public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null,
+                                     $contextlevel = null, $instanceid = null) {
+        return core_files_external::get_files($contextid, $component, $filearea, $itemid, $filepath, $filename,
+            $modified, $contextlevel, $instanceid);
     }
 
     /**
@@ -425,6 +445,15 @@ class moodle_file_external extends external_api {
         return core_files_external::get_files_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_files_is_deprecated() {
+        return true;
+    }
+
     /**
      * Returns description of upload parameters
      *
@@ -447,13 +476,16 @@ class moodle_file_external extends external_api {
      * @param string $filepath
      * @param string $filename
      * @param string $filecontent
+     * @param string $contextlevel Context level (block, course, coursecat, system, user or module)
+     * @param int    $instanceid   Instance id of the item associated with the context level
      * @return array
      * @since Moodle 2.0
      * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
      * @see core_files_external::upload()
      */
-    public static function upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent) {
-        return core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent);
+    public static function upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent, $contextlevel, $instanceid) {
+        return core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath, $filename,
+            $filecontent, $contextlevel, $instanceid);
     }
 
     /**
@@ -467,4 +499,14 @@ class moodle_file_external extends external_api {
     public static function upload_returns() {
         return core_files_external::upload_returns();
     }
+
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function upload_is_deprecated() {
+        return true;
+    }
+
 }
index 3922c64..b49ff72 100644 (file)
@@ -244,6 +244,8 @@ class core_files_externallib_testcase extends advanced_testcase {
         // Create a file from the string that we made earlier.
         $file = $fs->create_file_from_string($filerecord, $filecontent);
         $timemodified = $file->get_timemodified();
+        $timecreated = $file->get_timemodified();
+        $filesize = $file->get_filesize();
 
         // Use the web service function to return the information about the file that we just uploaded.
         // The first time is with a valid context ID.
@@ -293,7 +295,12 @@ class core_files_externallib_testcase extends advanced_testcase {
                                         'filename' => 'Simple4.txt',
                                         'url' => 'http://www.example.com/moodle/pluginfile.php/'.$context->id.'/mod_data/content/'.$itemid.'/Simple4.txt',
                                         'isdir' => false,
-                                        'timemodified' => $timemodified);
+                                        'timemodified' => $timemodified,
+                                        'timecreated' => $timecreated,
+                                        'filesize' => $filesize,
+                                        'author' => null,
+                                        'license' => null
+                                        );
         // Make sure that they are the same.
         $this->assertEquals($testdata, $testfilelisting);
 
index 138a9a1..65212dd 100644 (file)
@@ -196,6 +196,8 @@ class grade_export_form extends moodleform {
         $submitstring = get_string('download');
         if (empty($features['simpleui'])) {
             $submitstring = get_string('submit');
+        } else if (!empty($CFG->gradepublishing)) {
+            $submitstring = get_string('export', 'grades');
         }
 
         $this->add_action_buttons(false, $submitstring);
index f08b54c..7dd9a84 100644 (file)
@@ -383,16 +383,30 @@ abstract class grade_export {
             $itemidsparam = '-1';
         }
 
-        $params = array('id'                =>$this->course->id,
-                        'groupid'           =>$this->groupid,
-                        'itemids'           =>$itemidsparam,
-                        'export_letters'    =>$this->export_letters,
-                        'export_feedback'   =>$this->export_feedback,
-                        'updatedgradesonly' =>$this->updatedgradesonly,
-                        'displaytype'       =>$this->displaytype,
-                        'decimalpoints'     =>$this->decimalpoints,
-                        'export_onlyactive' =>$this->onlyactive,
-                        'usercustomfields'  =>$this->usercustomfields);
+        // We have a single grade display type constant.
+        if (!is_array($this->displaytype)) {
+            $displaytypes = $this->displaytype;
+        } else {
+            // Implode the grade display types array as moodle_url function doesn't accept arrays.
+            $displaytypes = implode(',', $this->displaytype);
+        }
+
+        if (!empty($this->updatedgradesonly)) {
+            $updatedgradesonly = $this->updatedgradesonly;
+        } else {
+            $updatedgradesonly = 0;
+        }
+        $params = array('id'                => $this->course->id,
+                        'groupid'           => $this->groupid,
+                        'itemids'           => $itemidsparam,
+                        'export_letters'    => $this->export_letters,
+                        'export_feedback'   => $this->export_feedback,
+                        'updatedgradesonly' => $updatedgradesonly,
+                        'decimalpoints'     => $this->decimalpoints,
+                        'export_onlyactive' => $this->onlyactive,
+                        'usercustomfields'  => $this->usercustomfields,
+                        'displaytype'       => $displaytypes,
+                        'key'               => $this->userkey);
 
         return $params;
     }
@@ -436,6 +450,151 @@ abstract class grade_export {
 
         return;
     }
+
+    /**
+     * Generate the export url.
+     *
+     * Get submitted form data and create the url to be used on the grade publish feature.
+     *
+     * @return moodle_url the url of grade publishing export.
+     */
+    public function get_export_url() {
+        return new moodle_url('/grade/export/'.$this->plugin.'/dump.php', $this->get_export_params());
+    }
+
+    /**
+     * Convert the grade display types parameter into the required array to grade exporting class.
+     *
+     * In order to export, the array key must be the display type name and the value must be the grade display type
+     * constant.
+     *
+     * Note: Added support for combined display types constants like the (GRADE_DISPLAY_TYPE_PERCENTAGE_REAL) as
+     *       the $CFG->grade_export_displaytype config is still used on 2.7 in case of missing displaytype url param.
+     *       In these cases, the file will be exported with a column for each display type.
+     *
+     * @param string $displaytypes can be a single or multiple display type constants comma separated.
+     * @return array $types
+     */
+    public static function convert_flat_displaytypes_to_array($displaytypes) {
+        $types = array();
+
+        // We have a single grade display type constant.
+        if (is_int($displaytypes)) {
+            $displaytype = clean_param($displaytypes, PARAM_INT);
+
+            // Let's set a default value, will be replaced below by the grade display type constant.
+            $display[$displaytype] = 1;
+        } else {
+            // Multiple grade display types constants.
+            $display = array_flip(explode(',', $displaytypes));
+        }
+
+        // Now, create the array in the required format by grade exporting class.
+        foreach ($display as $type => $value) {
+            $type = clean_param($type, PARAM_INT);
+            if ($type == GRADE_DISPLAY_TYPE_LETTER) {
+                $types['letter'] = GRADE_DISPLAY_TYPE_LETTER;
+            } else if ($type == GRADE_DISPLAY_TYPE_PERCENTAGE) {
+                $types['percentage'] = GRADE_DISPLAY_TYPE_PERCENTAGE;
+            } else if ($type == GRADE_DISPLAY_TYPE_REAL) {
+                $types['real'] = GRADE_DISPLAY_TYPE_REAL;
+            } else if ($type == GRADE_DISPLAY_TYPE_REAL_PERCENTAGE) {
+                $types['real'] = GRADE_DISPLAY_TYPE_REAL;
+                $types['percentage'] = GRADE_DISPLAY_TYPE_PERCENTAGE;
+            } else if ($type == GRADE_DISPLAY_TYPE_REAL_LETTER) {
+                $types['real'] = GRADE_DISPLAY_TYPE_REAL;
+                $types['letter'] = GRADE_DISPLAY_TYPE_LETTER;
+            } else if ($type == GRADE_DISPLAY_TYPE_LETTER_REAL) {
+                $types['letter'] = GRADE_DISPLAY_TYPE_LETTER;
+                $types['real'] = GRADE_DISPLAY_TYPE_REAL;
+            } else if ($type == GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE) {
+                $types['letter'] = GRADE_DISPLAY_TYPE_LETTER;
+                $types['percentage'] = GRADE_DISPLAY_TYPE_PERCENTAGE;
+            } else if ($type == GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER) {
+                $types['percentage'] = GRADE_DISPLAY_TYPE_PERCENTAGE;
+                $types['letter'] = GRADE_DISPLAY_TYPE_LETTER;
+            } else if ($type == GRADE_DISPLAY_TYPE_PERCENTAGE_REAL) {
+                $types['percentage'] = GRADE_DISPLAY_TYPE_PERCENTAGE;
+                $types['real'] = GRADE_DISPLAY_TYPE_REAL;
+            }
+        }
+        return $types;
+    }
+
+    /**
+     * Convert the item ids parameter into the required array to grade exporting class.
+     *
+     * In order to export, the array key must be the grade item id and all values must be one.
+     *
+     * @param string $itemids can be a single item id or many item ids comma separated.
+     * @return array $items correctly formatted array.
+     */
+    public static function convert_flat_itemids_to_array($itemids) {
+        $items = array();
+
+        // We just have one single item id.
+        if (is_int($itemids)) {
+            $itemid = clean_param($itemids, PARAM_INT);
+            $items[$itemid] = 1;
+        } else {
+            // Few grade items.
+            $items = array_flip(explode(',', $itemids));
+            foreach ($items as $itemid => $value) {
+                $itemid = clean_param($itemid, PARAM_INT);
+                $items[$itemid] = 1;
+            }
+        }
+        return $items;
+    }
+
+    /**
+     * Create the html code of the grade publishing feature.
+     *
+     * @return string $output html code of the grade publishing.
+     */
+    public function get_grade_publishing_url() {
+        $url = $this->get_export_url();
+        $output =  html_writer::start_div();
+        $output .= html_writer::tag('p', get_string('gradepublishinglink', 'grades', html_writer::link($url, $url)));
+        $output .=  html_writer::end_div();
+        return $output;
+    }
+
+    /**
+     * Create a stdClass object from URL parameters to be used by grade_export class.
+     *
+     * @param int $id course id.
+     * @param string $itemids grade items comma separated.
+     * @param bool $exportfeedback export feedback option.
+     * @param bool $onlyactive only enrolled active students.
+     * @param string $displaytype grade display type constants comma separated.
+     * @param int $decimalpoints grade decimal points.
+     * @param null $updatedgradesonly recently updated grades only (Used by XML exporting only).
+     * @param null $separator separator character: tab, comma, colon and semicolon (Used by TXT exporting only).
+     *
+     * @return stdClass $formdata
+     */
+    public static function export_bulk_export_data($id, $itemids, $exportfeedback, $onlyactive, $displaytype,
+                                                   $decimalpoints, $updatedgradesonly = null, $separator = null) {
+
+        $formdata = new \stdClass();
+        $formdata->id = $id;
+        $formdata->itemids = self::convert_flat_itemids_to_array($itemids);
+        $formdata->exportfeedback = $exportfeedback;
+        $formdata->export_onlyactive = $onlyactive;
+        $formdata->display = self::convert_flat_displaytypes_to_array($displaytype);
+        $formdata->decimals = $decimalpoints;
+
+        if (!empty($updatedgradesonly)) {
+            $formdata->updatedgradesonly = $updatedgradesonly;
+        }
+
+        if (!empty($separator)) {
+            $formdata->separator = $separator;
+        }
+
+        return $formdata;
+    }
 }
 
 /**
index 0310ef8..c2e7a34 100644 (file)
 
 define('NO_MOODLE_COOKIES', true); // session not used here
 require_once '../../../config.php';
+require_once($CFG->dirroot.'/grade/export/ods/grade_export_ods.php');
+
+$id                 = required_param('id', PARAM_INT);
+$groupid            = optional_param('groupid', 0, PARAM_INT);
+$itemids            = required_param('itemids', PARAM_RAW);
+$exportfeedback     = optional_param('export_feedback', 0, PARAM_BOOL);
+$displaytype        = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_RAW);
+$decimalpoints      = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive         = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
-$id = required_param('id', PARAM_INT); // course id
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
 }
@@ -30,9 +38,19 @@ if (empty($CFG->gradepublishing)) {
 }
 
 $context = context_course::instance($id);
+require_capability('moodle/grade:export', $context);
+require_capability('gradeexport/ods:view', $context);
 require_capability('gradeexport/ods:publish', $context);
 
-// use the same page parameters as export.php and append &key=sdhakjsahdksahdkjsahksadjksahdkjsadhksa
-require 'export.php';
+if (!groups_group_visible($groupid, $COURSE)) {
+    print_error('cannotaccessgroup', 'grades');
+}
+
+// Get all url parameters and create an object to simulate a form submission.
+$formdata = grade_export::export_bulk_export_data($id, $itemids, $exportfeedback, $onlyactive, $displaytype,
+        $decimalpoints);
+
+$export = new grade_export_ods($course, $groupid, $formdata);
+$export->print_grades();
 
 
index 827bd67..031f568 100644 (file)
@@ -20,6 +20,7 @@ require_once $CFG->dirroot.'/grade/export/lib.php';
 require_once 'grade_export_ods.php';
 
 $id                = required_param('id', PARAM_INT); // course id
+$PAGE->set_url('/grade/export/ods/export.php', array('id'=>$id));
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -32,6 +33,13 @@ $groupid = groups_get_course_group($course, true);
 require_capability('moodle/grade:export', $context);
 require_capability('gradeexport/ods:view', $context);
 
+// We need to call this method here before any output otherwise the menu won't display.
+// If you use this method without this check, will break the direct grade exporting (without publishing).
+$key = optional_param('key', '', PARAM_RAW);
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    print_grade_page_head($COURSE->id, 'export', 'ods', get_string('exportto', 'grades') . ' ' . get_string('pluginname', 'gradeexport_ods'));
+}
+
 if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
     if (!groups_is_member($groupid, $USER->id)) {
         print_error('cannotaccessgroup', 'grades');
@@ -39,9 +47,13 @@ if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('
 }
 $mform = new grade_export_form(null, array('publishing' => true, 'simpleui' => true, 'multipledisplaytypes' => true));
 $data = $mform->get_data();
-
-// print all the exported data here
 $export = new grade_export_ods($course, $groupid, $data);
-$export->print_grades();
-
 
+// If the gradepublishing is enabled and user key is selected print the grade publishing link.
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    groups_print_course_menu($course, 'index.php?id='.$id);
+    echo $export->get_grade_publishing_url();
+    echo $OUTPUT->footer();
+} else {
+    $export->print_grades();
+}
index 787a28c..5933954 100644 (file)
 
 define('NO_MOODLE_COOKIES', true); // session not used here
 require_once '../../../config.php';
+require_once($CFG->dirroot.'/grade/export/txt/grade_export_txt.php');
+
+$id                 = required_param('id', PARAM_INT);
+$groupid            = optional_param('groupid', 0, PARAM_INT);
+$itemids            = required_param('itemids', PARAM_RAW);
+$exportfeedback     = optional_param('export_feedback', 0, PARAM_BOOL);
+$separator          = optional_param('separator', 'comma', PARAM_ALPHA);
+$displaytype        = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_RAW);
+$decimalpoints      = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive         = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
-$id = required_param('id', PARAM_INT); // course id
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
 }
@@ -30,9 +39,19 @@ if (empty($CFG->gradepublishing)) {
 }
 
 $context = context_course::instance($id);
+require_capability('moodle/grade:export', $context);
 require_capability('gradeexport/txt:publish', $context);
+require_capability('gradeexport/txt:view', $context);
+
+if (!groups_group_visible($groupid, $COURSE)) {
+    print_error('cannotaccessgroup', 'grades');
+}
+
+// Get all url parameters and create an object to simulate a form submission.
+$formdata = grade_export::export_bulk_export_data($id, $itemids, $exportfeedback, $onlyactive, $displaytype,
+        $decimalpoints, null, $separator);
 
-// use the same page parameters as export.php and append &key=sdhakjsahdksahdkjsahksadjksahdkjsadhksa
-require 'export.php';
+$export = new grade_export_txt($course, $groupid, $formdata);
+$export->print_grades();
 
 
index bb3a776..d150e1a 100644 (file)
@@ -20,6 +20,7 @@ require_once $CFG->dirroot.'/grade/export/lib.php';
 require_once 'grade_export_txt.php';
 
 $id                = required_param('id', PARAM_INT); // course id
+$PAGE->set_url('/grade/export/txt/export.php', array('id'=>$id));
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -32,6 +33,13 @@ $groupid = groups_get_course_group($course, true);
 require_capability('moodle/grade:export', $context);
 require_capability('gradeexport/txt:view', $context);
 
+// We need to call this method here before any print otherwise the menu won't display.
+// If you use this method without this check, will break the direct grade exporting (without publishing).
+$key = optional_param('key', '', PARAM_RAW);
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    print_grade_page_head($COURSE->id, 'export', 'txt', get_string('exportto', 'grades') . ' ' . get_string('pluginname', 'gradeexport_txt'));
+}
+
 if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
     if (!groups_is_member($groupid, $USER->id)) {
         print_error('cannotaccessgroup', 'grades');
@@ -46,8 +54,13 @@ $params = array(
 );
 $mform = new grade_export_form(null, $params);
 $data = $mform->get_data();
-
-// Print all the exported data here.
 $export = new grade_export_txt($course, $groupid, $data);
-$export->print_grades();
 
+// If the gradepublishing is enabled and user key is selected print the grade publishing link.
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    groups_print_course_menu($course, 'index.php?id='.$id);
+    echo $export->get_grade_publishing_url();
+    echo $OUTPUT->footer();
+} else {
+    $export->print_grades();
+}
index 654eb68..287c513 100644 (file)
 
 define('NO_MOODLE_COOKIES', true); // session not used here
 require_once '../../../config.php';
+require_once($CFG->dirroot.'/grade/export/xls/grade_export_xls.php');
+
+$id                 = required_param('id', PARAM_INT);
+$groupid            = optional_param('groupid', 0, PARAM_INT);
+$itemids            = required_param('itemids', PARAM_RAW);
+$exportfeedback     = optional_param('export_feedback', 0, PARAM_BOOL);
+$displaytype        = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_RAW);
+$decimalpoints      = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive         = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
-$id = required_param('id', PARAM_INT); // course id
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
 }
@@ -30,9 +38,19 @@ if (empty($CFG->gradepublishing)) {
 }
 
 $context = context_course::instance($id);
+require_capability('moodle/grade:export', $context);
+require_capability('gradeexport/xls:view', $context);
 require_capability('gradeexport/xls:publish', $context);
 
-// use the same page parameters as export.php and append &key=sdhakjsahdksahdkjsahksadjksahdkjsadhksa
-require 'export.php';
+if (!groups_group_visible($groupid, $COURSE)) {
+    print_error('cannotaccessgroup', 'grades');
+}
+
+// Get all url parameters and create an object to simulate a form submission.
+$formdata = grade_export::export_bulk_export_data($id, $itemids, $exportfeedback, $onlyactive, $displaytype,
+        $decimalpoints);
+
+$export = new grade_export_xls($course, $groupid, $formdata);
+$export->print_grades();
 
 
index 409ce48..a844172 100644 (file)
@@ -20,6 +20,7 @@ require_once $CFG->dirroot.'/grade/export/lib.php';
 require_once 'grade_export_xls.php';
 
 $id                = required_param('id', PARAM_INT); // course id
+$PAGE->set_url('/grade/export/xls/export.php', array('id'=>$id));
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -32,6 +33,13 @@ $groupid = groups_get_course_group($course, true);
 require_capability('moodle/grade:export', $context);
 require_capability('gradeexport/xls:view', $context);
 
+// We need to call this method here before any print otherwise the menu won't display.
+// If you use this method without this check, will break the direct grade exporting (without publishing).
+$key = optional_param('key', '', PARAM_RAW);
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    print_grade_page_head($COURSE->id, 'export', 'xls', get_string('exportto', 'grades') . ' ' . get_string('pluginname', 'gradeexport_xls'));
+}
+
 if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
     if (!groups_is_member($groupid, $USER->id)) {
         print_error('cannotaccessgroup', 'grades');
@@ -39,9 +47,14 @@ if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('
 }
 $mform = new grade_export_form(null, array('publishing' => true, 'simpleui' => true, 'multipledisplaytypes' => true));
 $formdata = $mform->get_data();
-
-// print all the exported data here
 $export = new grade_export_xls($course, $groupid, $formdata);
-$export->print_grades();
 
+// If the gradepublishing is enabled and user key is selected print the grade publishing link.
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    groups_print_course_menu($course, 'index.php?id='.$id);
+    echo $export->get_grade_publishing_url();
+    echo $OUTPUT->footer();
+} else {
+    $export->print_grades();
+}
 
index a640be8..a8ed187 100644 (file)
 
 define('NO_MOODLE_COOKIES', true); // session not used here
 require_once '../../../config.php';
+require_once($CFG->dirroot.'/grade/export/xml/grade_export_xml.php');
+
+$id                 = required_param('id', PARAM_INT);
+$groupid            = optional_param('groupid', 0, PARAM_INT);
+$itemids            = required_param('itemids', PARAM_RAW);
+$exportfeedback     = optional_param('export_feedback', 0, PARAM_BOOL);
+$updatedgradesonly  = optional_param('updatedgradesonly', false, PARAM_BOOL);
+$displaytype        = optional_param('displaytype', $CFG->grade_export_displaytype, PARAM_RAW);
+$decimalpoints      = optional_param('decimalpoints', $CFG->grade_export_decimalpoints, PARAM_INT);
+$onlyactive         = optional_param('export_onlyactive', 0, PARAM_BOOL);
 
-$id = required_param('id', PARAM_INT); // course id
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
 }
@@ -30,9 +39,19 @@ if (empty($CFG->gradepublishing)) {
 }
 
 $context = context_course::instance($id);
+require_capability('moodle/grade:export', $context);
 require_capability('gradeexport/xml:publish', $context);
+require_capability('gradeexport/xml:view', $context);
+
+if (!groups_group_visible($groupid, $COURSE)) {
+    print_error('cannotaccessgroup', 'grades');
+}
+
+// Get all url parameters and create an object to simulate a form submission.
+$formdata = grade_export::export_bulk_export_data($id, $itemids, $exportfeedback, $onlyactive, $displaytype,
+        $decimalpoints, $updatedgradesonly, null);
 
-// use the same page parameters as export.php and append &key=sdhakjsahdksahdkjsahksadjksahdkjsadhksa
-require 'export.php';
+$export = new grade_export_xml($course, $groupid, $formdata);
+$export->print_grades();
 
 
index b138f06..a2f1bb3 100644 (file)
@@ -20,6 +20,7 @@ require_once $CFG->dirroot.'/grade/export/lib.php';
 require_once 'grade_export_xml.php';
 
 $id                = required_param('id', PARAM_INT); // course id
+$PAGE->set_url('/grade/export/xml/export.php', array('id'=>$id));
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     print_error('nocourseid');
@@ -32,17 +33,30 @@ $groupid = groups_get_course_group($course, true);
 require_capability('moodle/grade:export', $context);
 require_capability('gradeexport/xml:view', $context);
 
+// We need to call this method here before any print otherwise the menu won't display.
+// If you use this method without this check, will break the direct grade exporting (without publishing).
+$key = optional_param('key', '', PARAM_RAW);
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    print_grade_page_head($COURSE->id, 'export', 'xml', get_string('exportto', 'grades') . ' ' . get_string('pluginname', 'gradeexport_xml'));
+}
+
 if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
     if (!groups_is_member($groupid, $USER->id)) {
         print_error('cannotaccessgroup', 'grades');
     }
 }
 $mform = new grade_export_form(null, array('publishing' => true, 'simpleui' => true, 'multipledisplaytypes' => false,
-        'idnumberrequired' => true));
+        'idnumberrequired' => true, 'updategradesonly' => true));
 $formdata = $mform->get_data();
-
-// print all the exported data here
 $export = new grade_export_xml($course, $groupid, $formdata);
-$export->print_grades();
+
+// If the gradepublishing is enabled and user key is selected print the grade publishing link.
+if (!empty($CFG->gradepublishing) && !empty($key)) {
+    groups_print_course_menu($course, 'index.php?id='.$id);
+    echo $export->get_grade_publishing_url();
+    echo $OUTPUT->footer();
+} else {
+    $export->print_grades();
+}
 
 
index ef5e698..98deb9b 100644 (file)
@@ -41,11 +41,19 @@ class core_grade_external extends external_api {
     }
 
     public static function get_definitions($cmids, $areaname, $activeonly = false) {
-        return core_grading_external::get_definitions($cmids, $areaname, $activeonly = false);
+        return core_grading_external::get_definitions($cmids, $areaname, $activeonly);
     }
 
     public static function get_definitions_returns() {
         return core_grading_external::get_definitions_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_definitions_is_deprecated() {
+        return true;
+    }
 }
index 5286ba3..67d8677 100644 (file)
@@ -33,6 +33,7 @@ $string['grader:manage'] = 'Manage the grader report';
 $string['grader:view'] = 'View the grader report';
 $string['pluginname'] = 'Grader report';
 $string['preferences'] = 'Grader report preferences';
+$string['summarygrader'] = 'A table with the names of students in the first column, with assessable activities grouped by course and category across the top.';
 $string['useractivitygrade'] = '{$a} grade';
 $string['useractivityfeedback'] = '{$a} feedback';
 $string['overriddengrade'] = 'Overridden grade';
index 8712ee5..daaeddc 100644 (file)
@@ -412,8 +412,14 @@ class grade_report_grader extends grade_report {
         // Limit to users with a gradeable role.
         list($gradebookrolessql, $gradebookrolesparams) = $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0');
 
+        // Check the status of showing only active enrolments.
+        $coursecontext = $this->context->get_course_context(true);
+        $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
+        $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
+        $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext);
+
         // Limit to users with an active enrollment.
-        list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context);
+        list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context, '', 0, $showonlyactiveenrol);
 
         // Fields we need from the user table.
         $userfields = user_picture::fields('u', get_extra_user_fields($this->context));
@@ -476,30 +482,29 @@ class grade_report_grader extends grade_report {
             $this->userselect = "AND g.userid $usql";
             $this->userselect_params = $uparams;
 
-            // Add a flag to each user indicating whether their enrolment is active.
-            $sql = "SELECT ue.userid
-                      FROM {user_enrolments} ue
-                      JOIN {enrol} e ON e.id = ue.enrolid
-                     WHERE ue.userid $usql
-                           AND ue.status = :uestatus
-                           AND e.status = :estatus
-                           AND e.courseid = :courseid
-                           AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
-                  GROUP BY ue.userid";
-            $coursecontext = $this->context->get_course_context(true);
-            $time = time();
-            $params = array_merge($uparams, array('estatus' => ENROL_INSTANCE_ENABLED, 'uestatus' => ENROL_USER_ACTIVE,
-                    'courseid' => $coursecontext->instanceid, 'now1' => $time, 'now2' => $time));
-            $useractiveenrolments = $DB->get_records_sql($sql, $params);
-
-            $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
-            $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
-            $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext);
+            // First flag everyone as not suspended.
             foreach ($this->users as $user) {
-                // If we are showing only active enrolments, then remove suspended users from list.
-                if ($showonlyactiveenrol && !array_key_exists($user->id, $useractiveenrolments)) {
-                    unset($this->users[$user->id]);
-                } else {
+                $this->users[$user->id]->suspendedenrolment = false;
+            }
+
+            // If we want to mix both suspended and not suspended users, let's find out who is suspended.
+            if (!$showonlyactiveenrol) {
+                $sql = "SELECT ue.userid
+                          FROM {user_enrolments} ue
+                          JOIN {enrol} e ON e.id = ue.enrolid
+                         WHERE ue.userid $usql
+                               AND ue.status = :uestatus
+                               AND e.status = :estatus
+                               AND e.courseid = :courseid
+                               AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
+                      GROUP BY ue.userid";
+
+                $time = time();
+                $params = array_merge($uparams, array('estatus' => ENROL_INSTANCE_ENABLED, 'uestatus' => ENROL_USER_ACTIVE,
+                        'courseid' => $coursecontext->instanceid, 'now1' => $time, 'now2' => $time));
+                $useractiveenrolments = $DB->get_records_sql($sql, $params);
+
+                foreach ($this->users as $user) {
                     $this->users[$user->id]->suspendedenrolment = !array_key_exists($user->id, $useractiveenrolments);
                 }
             }
@@ -600,12 +605,18 @@ class grade_report_grader extends grade_report {
 
         $levels = count($this->gtree->levels) - 1;
 
-        for ($i = 0; $i < $levels; $i++) {
-            $fillercell = new html_table_cell();
-            $fillercell->attributes['class'] = 'fixedcolumn cell topleft';
-            $fillercell->text = ' ';
-            $fillercell->colspan = $colspan;
-            $row = new html_table_row(array($fillercell));
+        $fillercell = new html_table_cell();
+        $fillercell->header = true;
+        $fillercell->attributes['scope'] = 'col';
+        $fillercell->attributes['class'] = 'cell topleft';
+        $fillercell->text = html_writer::span(get_string('participants'), 'accesshide');
+        $fillercell->colspan = $colspan;
+        $fillercell->rowspan = $levels;
+        $row = new html_table_row(array($fillercell));
+        $rows[] = $row;
+
+        for ($i = 1; $i < $levels; $i++) {
+            $row = new html_table_row();
             $rows[] = $row;
         }
 
@@ -644,13 +655,13 @@ class grade_report_grader extends grade_report {
             $userrow->id = 'fixed_user_'.$userid;
 
             $usercell = new html_table_cell();
-            $usercell->attributes['class'] = 'user';
+            $usercell->attributes['class'] = 'header user';
 
             $usercell->header = true;
             $usercell->scope = 'row';
 
             if ($showuserimage) {
-                $usercell->text = $OUTPUT->user_picture($user);
+                $usercell->text = $OUTPUT->user_picture($user, array('visibletoscreenreaders' => false));
             }
 
             $fullname = fullname($user);
@@ -673,7 +684,7 @@ class grade_report_grader extends grade_report {
 
             $userreportcell = new html_table_cell();
             $userreportcell->attributes['class'] = 'userreport';
-            $userreportcell->header = true;
+            $userreportcell->header = false;
             if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) {
                 $a = new stdClass();
                 $a->user = $fullname;
@@ -694,9 +705,8 @@ class grade_report_grader extends grade_report {
 
             foreach ($extrafields as $field) {
                 $fieldcell = new html_table_cell();
-                $fieldcell->attributes['class'] = 'header userfield user' . $field;
-                $fieldcell->header = true;
-                $fieldcell->scope = 'row';
+                $fieldcell->attributes['class'] = 'userfield user' . $field;
+                $fieldcell->header = false;
                 $fieldcell->text = $user->{$field};
                 $userrow->cells[] = $fieldcell;
             }
@@ -785,8 +795,9 @@ class grade_report_grader extends grade_report {
                     $fillercell->attributes['class'] = $type . ' ' . $catlevel;
                     $fillercell->colspan = $colspan;
                     $fillercell->text = '&nbsp;';
-                    $fillercell->header = true;
-                    $fillercell->scope = 'col';
+
+                    // This is a filler cell; don't use a <th>, it'll confuse screen readers.
+                    $fillercell->header = false;
                     $headingrow->cells[] = $fillercell;
                 } else if ($type == 'category') {
                     // Element is a category
@@ -1155,6 +1166,7 @@ class grade_report_grader extends grade_report {
         $fulltable = new html_table();
         $fulltable->attributes['class'] = 'gradereport-grader-table';
         $fulltable->id = 'user-grades';
+        $fulltable->summary = get_string('summarygrader', 'gradereport_grader');
 
         // Extract rows from each side (left and right) and collate them into one row each
         foreach ($leftrows as $key => $row) {
index e111f47..9236fc8 100644 (file)
@@ -2,6 +2,7 @@ This files describes API changes in /grade/report/*,
 information provided here is intended especially for developers.
 === 2.9 ===
 * Deprecating grade_report_grader:get_collapsing_icon.
+* A new web service function gradereport_user_get_grades_table has been added which will allow external system to retrieve grade information ready to be formatted as a table similar to the gradebook user report one.
 
 === 2.8.2 ===
 * gradereport_singleview::__construct doesn't need groupid parameter anymore, so it was renamed to $unused.
diff --git a/grade/report/user/db/services.php b/grade/report/user/db/services.php
new file mode 100644 (file)
index 0000000..52ed7ef
--- /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/>.
+
+/**
+ * User grade report external functions and service definitions.
+ *
+ * @package    gradereport_user
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$functions = array(
+
+    'gradereport_user_get_grades_table' => array(
+        'classname' => 'gradereport_user_external',
+        'methodname' => 'get_grades_table',
+        'classpath' => 'grade/report/user/externallib.php',
+        'description' => 'Get the user/s report grades table for a course',
+        'type' => 'read',
+        'capabilities' => 'gradereport/user:view'
+    )
+);
diff --git a/grade/report/user/externallib.php b/grade/report/user/externallib.php
new file mode 100644 (file)
index 0000000..3392e22
--- /dev/null
@@ -0,0 +1,256 @@
+<?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/>.
+
+/**
+ * External grade report user API
+ *
+ * @package    gradereport_user
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once("$CFG->libdir/externallib.php");
+
+
+/**
+ * External grade report API implementation
+ *
+ * @package    gradereport_user
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @category   external
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class gradereport_user_external extends external_api {
+
+    /**
+     * Describes the parameters for get_grades_table.
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 2.9
+     */
+    public static function get_grades_table_parameters() {
+        return new external_function_parameters (
+            array(
+                'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED),
+                'userid'   => new external_value(PARAM_INT, 'Return grades only for this user (optional)', VALUE_DEFAULT, 0)
+            )
+        );
+    }
+
+    /**
+     * Returns a list of grades tables for users in a course.
+     *
+     * @param int $courseid Course Id
+     * @param int $userid   Only this user (optional)
+     *
+     * @return array the grades tables
+     * @since Moodle 2.9
+     */
+    public static function get_grades_table($courseid, $userid = 0) {
+        global $CFG, $USER;
+
+        $warnings = array();
+
+        // Validate the parameter.
+        $params = self::validate_parameters(self::get_grades_table_parameters(),
+            array(
+                'courseid' => $courseid,
+                'userid' => $userid)
+            );
+
+        // Compact/extract functions are not recommended.
+        $courseid = $params['courseid'];
+        $userid   = $params['userid'];
+
+        // Function get_course internally throws an exception if the course doesn't exist.
+        $course = get_course($courseid);
+
+        $context = context_course::instance($courseid);
+        self::validate_context($context);
+
+        // Specific capabilities.
+        require_capability('gradereport/user:view', $context);
+
+        $user = null;
+
+        if (empty($userid)) {
+            require_capability('moodle/grade:viewall', $context);
+        } else {
+            $user = core_user::get_user($userid, '*', MUST_EXIST);
+        }
+
+        $access = false;
+
+        if (has_capability('moodle/grade:viewall', $context)) {
+            // Can view all course grades.
+            $access = true;
+        } else if ($userid == $USER->id and has_capability('moodle/grade:view', $context) and $course->showgrades) {
+            // View own grades.
+            $access = true;
+        }
+
+        if (!$access) {
+            throw new moodle_exception('nopermissiontoviewgrades', 'error');
+        }
+
+        // Require files here to save some memory in case validation fails.
+        require_once($CFG->dirroot . '/group/lib.php');
+        require_once($CFG->libdir  . '/gradelib.php');
+        require_once($CFG->dirroot . '/grade/lib.php');
+        require_once($CFG->dirroot . '/grade/report/user/lib.php');
+
+        $gpr = new grade_plugin_return(
+            array(
+                'type' => 'report',
+                'plugin' => 'user',
+                'courseid' => $courseid,
+                'userid' => $userid)
+            );
+
+        $tables = array();
+
+        // Just one user.
+        if ($user) {
+            $report = new grade_report_user($courseid, $gpr, $context, $userid);
+            $report->fill_table();
+
+            $tables[] = array(
+                'courseid'      => $courseid,
+                'userid'        => $user->id,
+                'userfullname'  => fullname($user),
+                'maxdepth'      => $report->maxdepth,
+                'tabledata'     => $report->tabledata
+            );
+
+        } else {
+            $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
+            $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
+            $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context);
+
+            $gui = new graded_users_iterator($course);
+            $gui->require_active_enrolment($showonlyactiveenrol);
+            $gui->init();
+
+            while ($userdata = $gui->next_user()) {
+                $currentuser = $userdata->user;
+                $report = new grade_report_user($courseid, $gpr, $context, $currentuser->id);
+                $report->fill_table();
+
+                $tables[] = array(
+                    'courseid'      => $courseid,
+                    'userid'        => $currentuser->id,
+                    'userfullname'  => fullname($currentuser),
+                    'maxdepth'      => $report->maxdepth,
+                    'tabledata'     => $report->tabledata
+                );
+            }
+            $gui->close();
+        }
+
+        $result = array();
+        $result['tables'] = $tables;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Creates a table column structure
+     *
+     * @return array
+     * @since  Moodle 2.9
+     */
+    private static function grades_table_column() {
+        return array (
+            'class'   => new external_value(PARAM_RAW, 'class'),
+            'content' => new external_value(PARAM_RAW, 'cell content'),
+            'headers' => new external_value(PARAM_RAW, 'headers')
+        );
+    }
+
+    /**
+     * Describes tget_grades_table return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 2.9
+     */
+    public static function get_grades_table_returns() {
+        return new external_single_structure(
+            array(
+                'tables' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'courseid' => new external_value(PARAM_INT, 'course id'),
+                            'userid'   => new external_value(PARAM_INT, 'user id'),
+                            'userfullname' => new external_value(PARAM_TEXT, 'user fullname'),
+                            'maxdepth'   => new external_value(PARAM_INT, 'table max depth (needed for printing it)'),
+                            'tabledata' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'itemname' => new external_single_structure(
+                                            array (
+                                                'class' => new external_value(PARAM_RAW, 'class'),
+                                                'colspan' => new external_value(PARAM_INT, 'col span'),
+                                                'content'  => new external_value(PARAM_RAW, 'cell content'),
+                                                'celltype'  => new external_value(PARAM_RAW, 'cell type'),
+                                                'id'  => new external_value(PARAM_ALPHANUMEXT, 'id')
+                                            ), 'The item returned data', VALUE_OPTIONAL
+                                        ),
+                                        'leader' => new external_single_structure(
+                                            array (
+                                                'class' => new external_value(PARAM_RAW, 'class'),
+                                                'rowspan' => new external_value(PARAM_INT, 'row span')
+                                            ), 'The item returned data', VALUE_OPTIONAL
+                                        ),
+                                        'weight' => new external_single_structure(
+                                            self::grades_table_column(), 'weight column', VALUE_OPTIONAL
+                                        ),
+                                        'grade' => new external_single_structure(
+                                            self::grades_table_column(), 'grade column', VALUE_OPTIONAL
+                                        ),
+                                        'range' => new external_single_structure(
+                                            self::grades_table_column(), 'range column', VALUE_OPTIONAL
+                                        ),
+                                        'percentage' => new external_single_structure(
+                                            self::grades_table_column(), 'percentage column', VALUE_OPTIONAL
+                                        ),
+                                        'lettergrade' => new external_single_structure(
+                                            self::grades_table_column(), 'lettergrade column', VALUE_OPTIONAL
+                                        ),
+                                        'rank' => new external_single_structure(
+                                            self::grades_table_column(), 'rank column', VALUE_OPTIONAL
+                                        ),
+                                        'average' => new external_single_structure(
+                                            self::grades_table_column(), 'average column', VALUE_OPTIONAL
+                                        ),
+                                        'feedback' => new external_single_structure(
+                                            self::grades_table_column(), 'feedback column', VALUE_OPTIONAL
+                                        ),
+                                        'contributiontocoursetotal' => new external_single_structure(
+                                            self::grades_table_column(), 'contributiontocoursetotal column', VALUE_OPTIONAL
+                                        ),
+                                    ), 'table'
+                                )
+                            )
+                        )
+                    )
+                ),
+                'warnings' => new external_warnings()
+            )
+        );
+    }
+}
diff --git a/grade/report/user/tests/externallib_test.php b/grade/report/user/tests/externallib_test.php
new file mode 100644 (file)
index 0000000..b1a3d07
--- /dev/null
@@ -0,0 +1,164 @@
+<?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/>.
+
+/**
+ * User grade report functions unit tests
+ *
+ * @package    gradereport_user
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/grade/report/user/externallib.php');
+
+/**
+ * User grade report functions unit tests
+ *
+ * @package    gradereport_user
+ * @category   external
+ * @copyright  2015 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class gradereport_user_externallib_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Loads some data to be used by the different tests
+     * @param  int $s1grade Student 1 grade
+     * @param  int $s2grade Student 2 grade
+     * @return array          Course and users instances
+     */
+    private function load_data($s1grade, $s2grade) {
+        global $DB;
+
+        $course = $this->getDataGenerator()->create_course();
+
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $student1 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
+
+        $student2 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
+
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $teacher = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
+
+        $assignment = $this->getDataGenerator()->create_module('assign', array('name' => "Test assign", 'course' => $course->id));
+        $modcontext = get_coursemodule_from_instance('assign', $assignment->id, $course->id);
+        $assignment->cmidnumber = $modcontext->id;
+
+        $student1grade = array('userid' => $student1->id, 'rawgrade' => $s1grade);
+        $student2grade = array('userid' => $student2->id, 'rawgrade' => $s2grade);
+        $studentgrades = array($student1->id => $student1grade, $student2->id => $student2grade);
+        assign_grade_item_update($assignment, $studentgrades);
+
+        return array($course, $teacher, $student1, $student2);
+    }
+
+    /**
+     * Test get_grades_table function case teacher
+     */
+    public function test_get_grades_table_teacher() {
+
+        $this->resetAfterTest(true);
+
+        $s1grade = 80;
+        $s2grade = 60;
+
+        list($course, $teacher, $student1, $student2) = $this->load_data($s1grade, $s2grade);
+
+        // A teacher must see all student grades.
+        $this->setUser($teacher);
+
+        $studentgrades = gradereport_user_external::get_grades_table($course->id);
+        $studentgrades = external_api::clean_returnvalue(gradereport_user_external::get_grades_table_returns(), $studentgrades);
+
+        // No warnings returned.
+        $this->assertTrue(count($studentgrades['warnings']) == 0);
+
+        // Check that two grades are returned (each for student).
+        $this->assertTrue(count($studentgrades['tables']) == 2);
+
+        // Read returned grades.
+        $studentreturnedgrades = array();
+        $studentreturnedgrades[$studentgrades['tables'][0]['userid']] =
+            (int) $studentgrades['tables'][0]['tabledata'][1]['grade']['content'];
+
+        $studentreturnedgrades[$studentgrades['tables'][1]['userid']] =
+            (int) $studentgrades['tables'][1]['tabledata'][1]['grade']['content'];
+
+        $this->assertEquals($s1grade, $studentreturnedgrades[$student1->id]);
+        $this->assertEquals($s2grade, $studentreturnedgrades[$student2->id]);
+    }
+
+    /**
+     * Test get_grades_table function case student
+     */
+    public function test_get_grades_table_student() {
+        global $CFG, $DB;
+
+        $this->resetAfterTest(true);
+
+        $s1grade = 80;
+        $s2grade = 60;
+
+        list($course, $teacher, $student1, $student2) = $this->load_data($s1grade, $s2grade);
+
+        // A user can see his own grades.
+        $this->setUser($student1);
+        $studentgrade = gradereport_user_external::get_grades_table($course->id, $student1->id);
+        $studentgrade = external_api::clean_returnvalue(gradereport_user_external::get_grades_table_returns(), $studentgrade);
+
+        // No warnings returned.
+        $this->assertTrue(count($studentgrade['warnings']) == 0);
+
+        $this->assertTrue(count($studentgrade['tables']) == 1);
+        $student1returnedgrade = (int) $studentgrade['tables'][0]['tabledata'][1]['grade']['content'];
+        $this->assertEquals($s1grade, $student1returnedgrade);
+
+    }
+
+    /**
+     * Test get_grades_table function case incorrect permissions
+     */
+    public function test_get_grades_table_permissions() {
+        global $CFG, $DB;
+
+        $this->resetAfterTest(true);
+
+        $s1grade = 80;
+        $s2grade = 60;
+
+        list($course, $teacher, $student1, $student2) = $this->load_data($s1grade, $s2grade);
+
+        $this->setUser($student2);
+
+        try {
+            $studentgrade = gradereport_user_external::get_grades_table($course->id, $student1->id);
+            $this->fail('Exception expected due to not perissions to view other user grades.');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('nopermissiontoviewgrades', $e->errorcode);
+        }
+
+    }
+
+}
index 96b87e2..a0239ff 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014111000;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2014111001;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014110400;        // Requires this Moodle version
 $plugin->component = 'gradereport_user'; // Full name of the plugin (used for diagnostics)
index 7836758..f02b748 100644 (file)
@@ -1205,6 +1205,15 @@ class moodle_group_external extends external_api {
         return core_group_external::create_groups_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function create_groups_is_deprecated() {
+        return true;
+    }
+
     /**
      * Returns description of method parameters
      *
@@ -1242,6 +1251,15 @@ class moodle_group_external extends external_api {
         return core_group_external::get_groups_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_groups_is_deprecated() {
+        return true;
+    }
+
     /**
      * Returns description of method parameters
      *
@@ -1279,6 +1297,15 @@ class moodle_group_external extends external_api {
         return core_group_external::get_course_groups_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_course_groups_is_deprecated() {
+        return true;
+    }
+
     /**
      * Returns description of method parameters
      *
@@ -1288,7 +1315,7 @@ class moodle_group_external extends external_api {
      * @see core_group_external::delete_group_members_parameters()
      */
     public static function delete_groups_parameters() {
-        return core_group_external::delete_group_members_parameters();
+        return core_group_external::delete_groups_parameters();
     }
 
     /**
@@ -1312,9 +1339,17 @@ class moodle_group_external extends external_api {
      * @see core_group_external::delete_group_members_returns()
      */
     public static function delete_groups_returns() {
-        return core_group_external::delete_group_members_returns();
+        return core_group_external::delete_groups_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function delete_groups_is_deprecated() {
+        return true;
+    }
 
     /**
      * Returns description of method parameters
@@ -1353,6 +1388,14 @@ class moodle_group_external extends external_api {
         return core_group_external::get_group_members_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function get_groupmembers_is_deprecated() {
+        return true;
+    }
 
     /**
      * Returns description of method parameters
@@ -1390,6 +1433,14 @@ class moodle_group_external extends external_api {
         return core_group_external::add_group_members_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function add_groupmembers_is_deprecated() {
+        return true;
+    }
 
     /**
      * Returns description of method parameters
@@ -1427,4 +1478,13 @@ class moodle_group_external extends external_api {
         return core_group_external::delete_group_members_returns();
     }
 
+    /**
+     * Marking the method as deprecated.
+     *
+     * @return bool
+     */
+    public static function delete_groupmembers_is_deprecated() {
+        return true;
+    }
+
 }
index 5416beb..d23f400 100644 (file)
@@ -48,6 +48,7 @@ define('AJAX_SCRIPT', false); // prevents some warnings later
 define('CACHE_DISABLE_ALL', true); // Disables caching.. just in case.
 define('PHPUNIT_TEST', false);
 define('IGNORE_COMPONENT_CACHE', true);
+define('MDL_PERF_TEST', false);
 
 // Servers should define a default timezone in php.ini, but if they don't then make sure something is defined.
 // This is a quick hack.  Ideally we should ask the admin for a value.  See MDL-22625 for more on this.
index 4d9f2d5..8588576 100644 (file)
@@ -144,6 +144,7 @@ $string['cannotsetupblock'] = 'Blocks tables could NOT be set up successfully!';
 $string['cannotsetupcapformod'] = 'Could not set up the capabilities for {$a}';
 $string['cannotsetupcapforplugin'] = 'Could not set up the capabilities for {$a}';
 $string['cannotshowhidecoursesincategory'] = 'Cannot show/hide the courses in category {$a}.';
+$string['cannotsignup'] = 'You cannot create a new account because you are already logged in as {$a}.';
 $string['cannotunassigncap'] = 'Could not unassign deprecated capability {$a->cap} from role {$a->role}';
 $string['cannotunassignrolefrom'] = 'Cannot unassign this user from role id: {$a}';
 $string['cannotunzipfile'] = 'Cannot unzip file';
@@ -229,6 +230,7 @@ $string['errorcreatingfile'] = 'Error creating file "{$a}"';
 $string['errorcreatingrole'] = 'Error creating role';
 $string['errorfetchingrssfeed'] = 'Error fetching RSS feed.';
 $string['erroronline'] = 'Error on line {$a}';
+$string['erroroutput'] = 'Error output, so disabling automatic redirect.';
 $string['errorparsingxml'] = 'Error parsing XML: {$a->errorstring} at line {$a->errorline}, char {$a->errorchar}';
 $string['errorreadingfile'] = 'Error reading file "{$a}"';
 $string['errorsavingrequest'] = 'An error occurred when trying to save your request.';
index cb1464b..e80d92e 100644 (file)
@@ -305,6 +305,7 @@ $string['gradepointmax_validateerror'] = 'This setting must be an integer betwee
 $string['gradepreferences'] = 'Grade preferences';
 $string['gradepreferenceshelp'] = 'Grade preferences Help';
 $string['gradepublishing'] = 'Enable publishing';
+$string['gradepublishinglink'] = 'Download: {$a}';
 $string['gradepublishing_help'] = 'Enable publishing in exports and imports: Exported grades can be accessed by accessing a URL, without having to log on to a Moodle site. Grades can be imported by accessing such a URL (which means that a Moodle site can import grades published by another site). By default only administrators may use this feature, please educate users before adding required capabilities to other roles (dangers of bookmark sharing and download accelerators, IP restrictions, etc.).';
 $string['gradereport'] = 'Grade report';
 $string['graderreport'] = 'Grader report';
index 1bb70c9..b9a5d7b 100644 (file)
@@ -270,6 +270,7 @@ $string['complete'] = 'Complete';
 $string['completereport'] = 'Complete report';
 $string['configuration'] = 'Configuration';
 $string['confirm'] = 'Confirm';
+$string['confirmdeletesection'] = 'Are you absolutely sure you want to delete "{$a}"? All activities will be also deleted';
 $string['confirmed'] = 'Your registration has been confirmed';
 $string['confirmednot'] = 'Your registration has not yet been confirmed!';
 $string['confirmcheckfull'] = 'Are you absolutely sure you want to confirm {$a} ?';
@@ -480,6 +481,7 @@ $string['deletechecktypename'] = 'Are you sure that you want to delete the {$a->
 $string['deletecheckfiles'] = 'Are you absolutely sure you want to delete these files?';
 $string['deletecheckfull'] = 'Are you absolutely sure you want to completely delete {$a} ?';
 $string['deletecheckwarning'] = 'You are about to delete these files';
+$string['deletesection'] = 'Delete section';
 $string['deleteselected'] = 'Delete selected';
 $string['deleteselectedkey'] = 'Delete selected key';
 $string['deletingcourse'] = 'Deleting {$a}';
index 192b66b..37da3db 100644 (file)
@@ -7847,11 +7847,11 @@ class admin_setting_webservicesoverview extends admin_setting {
                 get_string('enablemobilewebservice', 'admin'),
                 get_string('configenablemobilewebservice',
                         'admin', ''), 0); //we don't want to display it but to know the ws mobile status
-        $manageserviceurl = new moodle_url("/admin/settings.php?section=externalservices");
+        $manageserviceurl = new moodle_url("/admin/settings.php?section=mobile");
         $wsmobileparam = new stdClass();
         $wsmobileparam->enablemobileservice = get_string('enablemobilewebservice', 'admin');
         $wsmobileparam->manageservicelink = html_writer::link($manageserviceurl,
-                get_string('externalservices', 'webservice'));
+                get_string('mobile', 'admin'));
         $mobilestatus = $enablemobile->get_setting()?get_string('mobilewsenabled', 'webservice'):get_string('mobilewsdisabled', 'webservice');
         $wsmobileparam->wsmobilestatus = html_writer::tag('strong', $mobilestatus);
         $return .= $OUTPUT->heading(get_string('enablemobilewebservice', 'admin'), 3, 'main');
index e3729a2..80b57ce 100644 (file)
@@ -1126,7 +1126,8 @@ function badges_download($userid) {
     if ($zipper->archive_to_pathname($filelist, $tempzip)) {
         send_temp_file($tempzip, 'badges.zip');
     } else {
-        debugging("Problems with archiving the files.");
+        debugging("Problems with archiving the files.", DEBUG_DEVELOPER);
+        die;
     }
 }
 
index 613b7a9..cd518cf 100644 (file)
@@ -41,6 +41,8 @@ class behat_selectors {
     protected static $allowedtextselectors = array(
         'dialogue' => 'dialogue',
         'block' => 'block',
+        'section' => 'section',
+        'activity' => 'activity',
         'region' => 'region',
         'table_row' => 'table_row',
         'list_item' => 'list_item',
@@ -56,6 +58,8 @@ class behat_selectors {
     protected static $allowedselectors = array(
         'dialogue' => 'dialogue',
         'block' => 'block',
+        'section' => 'section',
+        'activity' => 'activity',
         'region' => 'region',
         'table_row' => 'table_row',
         'list_item' => 'list_item',
@@ -102,6 +106,16 @@ XPATH
     (contains(concat(' ', normalize-space(@class), ' '), concat(' ', %locator%, ' ')) or
      descendant::h2[normalize-space(.) = %locator%] or
      @aria-label = %locator%)]
+XPATH
+        , 'section' => <<<XPATH
+//li[contains(concat(' ', normalize-space(@class), ' '), ' section ')][./descendant::*[self::h3]
+    [normalize-space(.) = %locator%][contains(concat(' ', normalize-space(@class), ' '), ' sectionname ') or
+    contains(concat(' ', normalize-space(@class), ' '), ' section-title ')]] |
+//div[contains(concat(' ', normalize-space(@class), ' '), ' sitetopic ')]
+    [./descendant::*[self::h2][normalize-space(.) = %locator%] or %locator% = 'frontpage']
+XPATH
+        , 'activity' => <<<XPATH
+//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][normalize-space(.) = %locator% ]
 XPATH
         , 'region' => <<<XPATH
 //*[self::div | self::section | self::aside | self::header | self::footer][./@id = %locator%]
index 76936c6..630d592 100644 (file)
@@ -498,7 +498,7 @@ class block_manager {
             return false;
         }
         foreach ($this->visibleblockcontent[$region] as $instance) {
-            if (!empty($instance->content) && !get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
+            if (!get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
                 return false;
             }
         }
index c016bd5..234cc93 100644 (file)
@@ -50,13 +50,19 @@ class manager {
      *
      * NOTE: to be used from message_send() only.
      *
-     * @param \stdClass $eventdata fully prepared event data for processors
+     * @param \stdClass|\core\message\message $eventdata fully prepared event data for processors
      * @param \stdClass $savemessage the message saved in 'message' table
      * @param array $processorlist list of processors for target user
      * @return int $messageid the id from 'message' or 'message_read' table (false is not returned)
      */
-    public static function send_message(\stdClass $eventdata, \stdClass $savemessage, array $processorlist) {
+    public static function send_message($eventdata, \stdClass $savemessage, array $processorlist) {
         global $CFG;
+
+        if (!($eventdata instanceof \stdClass) && !($eventdata instanceof message)) {
+            // Not a valid object.
+            throw new \coding_exception('Message should be of type stdClass or \core\message\message');
+        }
+
         require_once($CFG->dirroot.'/message/lib.php'); // This is most probably already included from messagelib.php file.
 
         if (empty($processorlist)) {
@@ -85,12 +91,13 @@ class manager {
     /**
      * Send message to message processors.
      *
-     * @param \stdClass $eventdata
+     * @param \stdClass|\core\message\message $eventdata
      * @param \stdClass $savemessage
      * @param array $processorlist
      * @return int $messageid
      */
-    protected static function send_message_to_processors(\stdClass $eventdata, \stdClass $savemessage, array $processorlist) {
+    protected static function send_message_to_processors($eventdata, \stdClass $savemessage, array
+    $processorlist) {
         global $CFG, $DB;
 
         // We cannot communicate with external systems in DB transactions,
@@ -114,7 +121,9 @@ class manager {
 
         $failed = false;
         foreach ($processorlist as $procname) {
-            if (!$processors[$procname]->object->send_message($eventdata)) {
+            // Let new messaging class add custom content based on the processor.
+            $proceventdata = ($eventdata instanceof message) ? $eventdata->get_eventobject_for_processor($procname) : $eventdata;
+            if (!$processors[$procname]->object->send_message($proceventdata)) {
                 debugging('Error calling message processor ' . $procname);
                 $failed = true;
                 // Previously the $messageid = false here was overridden
diff --git a/lib/classes/message/message.php b/lib/classes/message/message.php
new file mode 100644 (file)
index 0000000..393d0e7
--- /dev/null
@@ -0,0 +1,252 @@
+<?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/>.
+
+/**
+ * New messaging class.
+ *
+ * @package   core_message
+ * @since     Moodle 2.9
+ * @copyright 2015 onwards Ankit Agarwal
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\message;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * New messaging class.
+ *
+ * Required parameters of the $eventdata object:
+ *  component string Component name. must exist in message_providers
+ *  name string Message type name. must exist in message_providers
+ *  userfrom object|int The user sending the message
+ *  userto object|int The message recipient
+ *  subject string The message subject
+ *  fullmessage string The full message in a given format
+ *  fullmessageformat int The format if the full message (FORMAT_MOODLE, FORMAT_HTML, ..)
+ *  fullmessagehtml string The full version (the message processor will choose with one to use)
+ *  smallmessage string The small version of the message
+ *
+ * Optional parameters of the $eventdata object:
+ *  notification bool Should the message be considered as a notification rather than a personal message
+ *  contexturl string If this is a notification then you can specify a url to view the event.
+ *                    For example the forum post the user is being notified of.
+ *  contexturlname string The display text for contexturl.
+ *  replyto string An email address which can be used to send an reply.
+ *  attachment stored_file File instance that needs to be sent as attachment.
+ *  attachname string Name of the attachment.
+ *
+ * @package   core_message
+ * @since     Moodle 2.9
+ * @copyright 2015 onwards Ankit Agarwal
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class message {
+    /** @var string Component name. */
+    private $component;
+
+    /** @var string Name. */
+    private $name;
+
+    /** @var object|int The user who is sending this message. */
+    private $userfrom;
+
+    /** @var object|int The user who is receiving from which is sending this message. */
+    private $userto;
+
+    /** @var string Subject of the message. */
+    private $subject;
+
+    /** @var string Complete message. */
+    private $fullmessage;
+
+    /** @var int Message format. */
+    private $fullmessageformat;
+
+    /** @var string Complete message in html format. */
+    private $fullmessagehtml;
+
+    /** @var  string Smaller version of the message. */
+    private $smallmessage;
+
+    /** @var  int Is it a notification? */
+    private $notification;
+
+    /** @var  string context url. */
+    private $contexturl;
+
+    /** @var  string context name. */
+    private $contexturlname;
+
+    /** @var  string An email address which can be used to send an reply. */
+    private $replyto;
+
+    /** @var  int Used internally to store the id of the row representing this message in DB. */
+    private $savedmessageid;
+
+    /** @var  \stored_file  File to be attached to the message. Note:- not all processors support this.*/
+    private $attachment;
+
+    /** @var  string Name of the attachment. Note:- not all processors support this.*/
+    private $attachname;
+
+    /** @var array a list of properties that is allowed for each message. */
+    private $properties = array('component', 'name', 'userfrom', 'userto', 'subject', 'fullmessage', 'fullmessageformat',
+                                'fullmessagehtml', 'smallmessage', 'notification', 'contexturl', 'contexturlname', 'savedmessageid',
+                                'replyto', 'attachment', 'attachname');
+
+    /** @var array property to store any additional message processor specific content */
+    private $additionalcontent = array();
+
+    /**
+     * Fullmessagehtml content including any processor specific content.
+     *
+     * @param string $processorname Name of the processor.
+     *
+     * @return mixed|string
+     */
+    protected function get_fullmessagehtml($processorname = '') {
+        if (!empty($processorname) && isset($this->additionalcontent[$processorname])) {
+            return $this->get_message_with_additional_content($processorname, 'fullmessagehtml');
+        } else {
+            return $this->fullmessagehtml;
+        }
+    }
+
+    /**
+     * Fullmessage content including any processor specific content.
+     *
+     * @param string $processorname Name of the processor.
+     *
+     * @return mixed|string
+     */
+    protected function get_fullmessage($processorname = '') {
+        if (!empty($processorname) && isset($this->additionalcontent[$processorname])) {
+            return $this->get_message_with_additional_content($processorname, 'fullmessage');
+        } else {
+            return $this->fullmessage;
+        }
+    }
+
+    /**
+     * Smallmessage content including any processor specific content.
+     *
+     * @param string $processorname Name of the processor.
+     *
+     * @return mixed|string
+     */
+    protected function get_smallmessage($processorname = '') {
+        if (!empty($processorname) && isset($this->additionalcontent[$processorname])) {
+            return $this->get_message_with_additional_content($processorname, 'smallmessage');
+        } else {
+            return $this->smallmessage;
+        }
+    }
+
+    /**
+     * Helper method used to get message content added with processor specific content.
+     *
+     * @param string $processorname Name of the processor.
+     * @param string $messagetype one of 'fullmessagehtml', 'fullmessage', 'smallmessage'.
+     *
+     * @return mixed|string
+     */
+    protected function get_message_with_additional_content($processorname, $messagetype) {
+        $message = $this->$messagetype;
+        if (isset($this->additionalcontent[$processorname]['*'])) {
+            // Content that needs to be added to all format.
+            $pattern = $this->additionalcontent[$processorname]['*'];
+            $message = empty($pattern['header']) ? $message : $pattern['header'] . $message;
+            $message = empty($pattern['footer']) ? $message : $message . $pattern['footer'];
+        }
+
+        if (isset($this->additionalcontent[$processorname][$messagetype])) {
+            // Content that needs to be added to the specific given format.
+            $pattern = $this->additionalcontent[$processorname][$messagetype];
+            $message = empty($pattern['header']) ? $message : $pattern['header'] . $message;
+            $message = empty($pattern['footer']) ? $message : $message . $pattern['footer'];
+        }
+
+        return $message;
+    }
+
+    /**
+     * Magic getter method.
+     *
+     * @param string $prop name of property to get.
+     *
+     * @return mixed
+     * @throws \coding_exception
+     */
+    public function __get($prop) {
+        if (in_array($prop, $this->properties)) {
+            return $this->$prop;
+        }
+        throw new \coding_exception("Invalid property $prop specified");
+    }
+
+    /**
+     * Magic setter method.
+     *
+     * @param string $prop name of property to set.
+     * @param mixed $value value to assign to the property.
+     *
+     * @return mixed
+     * @throws \coding_exception
+     */
+    public function __set($prop, $value) {
+        if (in_array($prop, $this->properties)) {
+            return $this->$prop = $value;
+        }
+        throw new \coding_exception("Invalid property $prop specified");
+    }
+
+    /**
+     * This method lets you define content that would be added to the message only for specific message processors.
+     *
+     * Example of $content:-
+     * array('fullmessagehtml' => array('header' => 'header content', 'footer' => 'footer content'),
+     *       'smallmessage' => array('header' => 'header content for small message', 'footer' => 'footer content'),
+     *       '*' => array('header' => 'header content for all types', 'footer' => 'footer content')
+     * )
+     *
+     * @param string $processorname name of the processor.
+     * @param array $content content to add in the above defined format.
+     */
+    public function set_additional_content($processorname, $content) {
+        $this->additionalcontent[$processorname] = $content;
+    }
+
+    /**
+     * Get a event object for a specific processor in stdClass format.
+     *
+     * @param string $processorname Name of the processor.
+     *
+     * @return \stdClass event object in stdClass format.
+     */
+    public function get_eventobject_for_processor($processorname) {
+        // This is done for Backwards compatibility. We should consider throwing notices here in future versions and requesting
+        // them to use proper api.
+
+        $eventdata = new \stdClass();
+        foreach ($this->properties as $prop) {
+            $func = "get_$prop";
+            $eventdata->$prop = method_exists($this, $func) ? $this->$func($processorname) : $this->$prop;
+        }
+        return $eventdata;
+    }
+}
\ No newline at end of file
index 84bf5f0..9aa27c1 100644 (file)
@@ -122,10 +122,10 @@ $functions = array(
     // === group related functions ===
 
     'moodle_group_create_groups' => array(
-        'classname'   => 'core_group_external',
+        'classname'   => 'moodle_group_external',
         'methodname'  => 'create_groups',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_create_groups(). ',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_create_groups(). ',
         'type'        => 'write',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -140,10 +140,10 @@ $functions = array(
     ),
 
     'moodle_group_get_groups' => array(
-        'classname'   => 'core_group_external',
+        'classname'   => 'moodle_group_external',
         'methodname'  => 'get_groups',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_get_groups()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_get_groups()',
         'type'        => 'read',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -158,10 +158,10 @@ $functions = array(
     ),
 
     'moodle_group_get_course_groups' => array(
-        'classname'   => 'core_group_external',
+        'classname'   => 'moodle_group_external',
         'methodname'  => 'get_course_groups',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_get_course_groups()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_get_course_groups()',
         'type'        => 'read',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -176,10 +176,10 @@ $functions = array(
     ),
 
     'moodle_group_delete_groups' => array(
-        'classname'   => 'core_group_external',
+        'classname'   => 'moodle_group_external',
         'methodname'  => 'delete_groups',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_delete_groups()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_delete_groups()',
         'type'        => 'delete',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -194,10 +194,10 @@ $functions = array(
     ),
 
     'moodle_group_get_groupmembers' => array(
-        'classname'   => 'core_group_external',
-        'methodname'  => 'get_group_members',
+        'classname'   => 'moodle_group_external',
+        'methodname'  => 'get_groupmembers',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_get_group_members()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_get_group_members()',
         'type'        => 'read',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -212,10 +212,10 @@ $functions = array(
     ),
 
     'moodle_group_add_groupmembers' => array(
-        'classname'   => 'core_group_external',
-        'methodname'  => 'add_group_members',
+        'classname'   => 'moodle_group_external',
+        'methodname'  => 'add_groupmembers',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_add_group_members()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_add_group_members()',
         'type'        => 'write',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -230,10 +230,10 @@ $functions = array(
     ),
 
     'moodle_group_delete_groupmembers' => array(
-        'classname'   => 'core_group_external',
-        'methodname'  => 'delete_group_members',
+        'classname'   => 'moodle_group_external',
+        'methodname'  => 'delete_groupmembers',
         'classpath'   => 'group/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_group_delete_group_members()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_group_delete_group_members()',
         'type'        => 'delete',
         'capabilities'=> 'moodle/course:managegroups',
     ),
@@ -306,9 +306,9 @@ $functions = array(
     // === file related functions ===
 
     'moodle_file_get_files' => array(
-        'classname'   => 'core_files_external',
+        'classname'   => 'moodle_file_external',
         'methodname'  => 'get_files',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_files_get_files()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_files_get_files()',
         'type'        => 'read',
         'classpath'   => 'files/externallib.php',
     ),
@@ -322,9 +322,9 @@ $functions = array(
     ),
 
     'moodle_file_upload' => array(
-        'classname'   => 'core_files_external',
+        'classname'   => 'moodle_file_external',
         'methodname'  => 'upload',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_files_upload()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_files_upload()',
         'type'        => 'write',
         'classpath'   => 'files/externallib.php',
     ),
@@ -340,10 +340,10 @@ $functions = array(
     // === user related functions ===
 
     'moodle_user_create_users' => array(
-        'classname'   => 'core_user_external',
+        'classname'   => 'moodle_user_external',
         'methodname'  => 'create_users',
         'classpath'   => 'user/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_user_create_users()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_user_create_users()',
         'type'        => 'write',
         'capabilities'=> 'moodle/user:create',
     ),
@@ -367,10 +367,10 @@ $functions = array(
     ),
 
     'moodle_user_get_users_by_id' => array(
-        'classname'   => 'core_user_external',
+        'classname'   => 'moodle_user_external',
         'methodname'  => 'get_users_by_id',
         'classpath'   => 'user/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_user_get_users_by_id()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. Use core_user_get_users_by_field service instead',
         'type'        => 'read',
         'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update',
     ),
@@ -394,19 +394,19 @@ $functions = array(
     ),
 
     'moodle_user_get_users_by_courseid' => array(
-        'classname'   => 'core_enrol_external',
-        'methodname'  => 'get_enrolled_users',
-        'classpath'   => 'enrol/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_enrol_get_enrolled_users()',
+        'classname'   => 'moodle_user_external',
+        'methodname'  => 'get_users_by_courseid',
+        'classpath'   => 'user/externallib.php',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_enrol_get_enrolled_users()',
         'type'        => 'read',
         'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update, moodle/site:accessallgroups',
     ),
 
     'moodle_user_get_course_participants_by_id' => array(
-        'classname'   => 'core_user_external',
-        'methodname'  => 'get_course_user_profiles',
+        'classname'   => 'moodle_user_external',
+        'methodname'  => 'get_course_participants_by_id',
         'classpath'   => 'user/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_user_get_course_user_profiles()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_user_get_course_user_profiles()',
         'type'        => 'read',
         'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update, moodle/site:accessallgroups',
     ),
@@ -421,10 +421,10 @@ $functions = array(
     ),
 
     'moodle_user_delete_users' => array(
-        'classname'   => 'core_user_external',
+        'classname'   => 'moodle_user_external',
         'methodname'  => 'delete_users',
         'classpath'   => 'user/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_user_delete_users()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_user_delete_users()',
         'type'        => 'write',
         'capabilities'=> 'moodle/user:delete',
     ),
@@ -439,10 +439,10 @@ $functions = array(
     ),
 
     'moodle_user_update_users' => array(
-        'classname'   => 'core_user_external',
+        'classname'   => 'moodle_user_external',
         'methodname'  => 'update_users',
         'classpath'   => 'user/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_user_update_users()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_user_update_users()',
         'type'        => 'write',
         'capabilities'=> 'moodle/user:update',
     ),
@@ -496,10 +496,10 @@ $functions = array(
     ),
 
     'moodle_enrol_get_users_courses' => array(
-        'classname'   => 'core_enrol_external',
+        'classname'   => 'moodle_enrol_external',
         'methodname'  => 'get_users_courses',
         'classpath'   => 'enrol/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_enrol_get_users_courses()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_enrol_get_users_courses()',
         'type'        => 'read',
         'capabilities'=> 'moodle/course:viewparticipants',
     ),
@@ -524,10 +524,10 @@ $functions = array(
     // === Role related functions ===
 
     'moodle_role_assign' => array(
-        'classname'   => 'core_role_external',
-        'methodname'  => 'assign_roles',
+        'classname'   => 'moodle_enrol_external',
+        'methodname'  => 'role_assign',
         'classpath'   => 'enrol/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_role_assign_role()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_role_assign_role()',
         'type'        => 'write',
         'capabilities'=> 'moodle/role:assign',
     ),
@@ -542,10 +542,10 @@ $functions = array(
     ),
 
     'moodle_role_unassign' => array(
-        'classname'   => 'core_role_external',
-        'methodname'  => 'unassign_roles',
+        'classname'   => 'moodle_enrol_external',
+        'methodname'  => 'role_unassign',
         'classpath'   => 'enrol/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_role_unassign_role()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_role_unassign_role()',
         'type'        => 'write',
         'capabilities'=> 'moodle/role:assign',
     ),
@@ -571,10 +571,10 @@ $functions = array(
     ),
 
     'moodle_course_get_courses' => array(
-        'classname'   => 'core_course_external',
+        'classname'   => 'moodle_course_external',
         'methodname'  => 'get_courses',
         'classpath'   => 'course/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_course_get_courses()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_course_get_courses()',
         'type'        => 'read',
         'capabilities'=> 'moodle/course:view,moodle/course:update,moodle/course:viewhiddencourses',
     ),
@@ -589,10 +589,10 @@ $functions = array(
     ),
 
     'moodle_course_create_courses' => array(
-        'classname'   => 'core_course_external',
+        'classname'   => 'moodle_course_external',
         'methodname'  => 'create_courses',
         'classpath'   => 'course/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_course_create_courses()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_course_create_courses()',
         'type'        => 'write',
         'capabilities'=> 'moodle/course:create,moodle/course:visibility',
     ),
@@ -692,10 +692,10 @@ $functions = array(
     // === message related functions ===
 
     'moodle_message_send_instantmessages' => array(
-        'classname'   => 'core_message_external',
-        'methodname'  => 'send_instant_messages',
+        'classname'   => 'moodle_message_external',
+        'methodname'  => 'send_instantmessages',
         'classpath'   => 'message/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_message_send_instant_messages()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_message_send_instant_messages()',
         'type'        => 'write',
         'capabilities'=> 'moodle/site:sendmessage',
     ),
@@ -784,10 +784,10 @@ $functions = array(
     // === notes related functions ===
 
     'moodle_notes_create_notes' => array(
-        'classname'   => 'core_notes_external',
+        'classname'   => 'moodle_notes_external',
         'methodname'  => 'create_notes',
         'classpath'   => 'notes/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_notes_create_notes()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_notes_create_notes()',
         'type'        => 'write',
         'capabilities'=> 'moodle/notes:manage',
     ),
@@ -862,10 +862,10 @@ $functions = array(
     // === webservice related functions ===
 
     'moodle_webservice_get_siteinfo' => array(
-        'classname'   => 'core_webservice_external',
-        'methodname'  => 'get_site_info',
+        'classname'   => 'moodle_webservice_external',
+        'methodname'  => 'get_siteinfo',
         'classpath'   => 'webservice/externallib.php',
-        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has be renamed as core_webservice_get_site_info()',
+        'description' => 'DEPRECATED: this deprecated function will be removed in a future version. This function has been renamed as core_webservice_get_site_info()',
         'type'        => 'read',
     ),
 
@@ -984,7 +984,8 @@ $services = array(
             'core_message_unblock_contacts',
             'core_message_get_contacts',
             'core_message_search_contacts',
-            'core_message_get_blocked_users'
+            'core_message_get_blocked_users',
+            'gradereport_user_get_grades_table'
             ),
         'enabled' => 0,
         'restrictedusers' => 0,
index 56144ef..a9ed4bf 100644 (file)
@@ -58,6 +58,7 @@ function external_function_info($function, $strictness=MUST_EXIST) {
 
     $function->parameters_method = $function->methodname.'_parameters';
     $function->returns_method    = $function->methodname.'_returns';
+    $function->deprecated_method = $function->methodname.'_is_deprecated';
 
     // make sure the implementaion class is ok
     if (!method_exists($function->classname, $function->methodname)) {
@@ -69,6 +70,11 @@ function external_function_info($function, $strictness=MUST_EXIST) {
     if (!method_exists($function->classname, $function->returns_method)) {
         throw new coding_exception('Missing returned values description');
     }
+    if (method_exists($function->classname, $function->deprecated_method)) {
+        if (call_user_func(array($function->classname, $function->deprecated_method)) === true) {
+            $function->deprecated = true;
+        }
+    }
 
     // fetch the parameters description
     $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method));
index 8ef41f6..7b9f46f 100644 (file)
@@ -105,6 +105,7 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil
     }
 
     $imageinfo = getimagesize($originalfile);
+    $imagefnc = '';
 
     if (empty($imageinfo)) {
         return false;
@@ -137,6 +138,13 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil
                 debugging('JPEG not supported on this server');
                 return false;
             }
+            // If the user uploads a jpeg them we should process as a jpeg if possible.
+            if (function_exists('imagejpeg')) {
+                $imagefnc = 'imagejpeg';
+                $imageext = '.jpg';
+                $filters = null; // Not used.
+                $quality = 90;
+            }
             break;
         case IMAGETYPE_PNG:
             if (function_exists('imagecreatefrompng')) {
@@ -150,19 +158,22 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil
             return false;
     }
 
-    if (function_exists('imagepng')) {
-        $imagefnc = 'imagepng';
-        $imageext = '.png';
-        $filters = PNG_NO_FILTER;
-        $quality = 1;
-    } else if (function_exists('imagejpeg')) {
-        $imagefnc = 'imagejpeg';
-        $imageext = '.jpg';
-        $filters = null; // not used
-        $quality = 90;
-    } else {
-        debugging('Jpeg and png not supported on this server, please fix server configuration');
-        return false;
+    // The conversion has not been decided yet, let's apply defaults (png with fallback to jpg).
+    if (empty($imagefnc)) {
+        if (function_exists('imagepng')) {
+            $imagefnc = 'imagepng';
+            $imageext = '.png';
+            $filters = PNG_NO_FILTER;
+            $quality = 1;
+        } else if (function_exists('imagejpeg')) {
+            $imagefnc = 'imagejpeg';
+            $imageext = '.jpg';
+            $filters = null; // Not used.
+            $quality = 90;
+        } else {
+            debugging('Jpeg and png not supported on this server, please fix server configuration');
+            return false;
+        }
     }
 
     if (function_exists('imagecreatetruecolor')) {
index 8f9ee6a..d700bca 100644 (file)
@@ -51,7 +51,7 @@ require_once(dirname(dirname(__FILE__)) . '/message/lib.php');
  *       earlier versions did not do it consistently either.
  *
  * @category message
- * @param stdClass $eventdata information about the message (component, userfrom, userto, ...)
+ * @param stdClass|\core\message\message $eventdata information about the message (component, userfrom, userto, ...)
  * @return mixed the integer ID of the new message or false if there was a problem with submitted data
  */
 function message_send($eventdata) {
index 6326937..7b64921 100644 (file)
@@ -9051,11 +9051,25 @@ function get_performance_info() {
         $hits = 0;
         $misses = 0;
         $sets = 0;
-        foreach ($stats as $definition => $stores) {
-            $html .= '<span class="cache-definition-stats">';
-            $html .= '<span class="cache-definition-stats-heading">'.$definition.'</span>';
+        foreach ($stats as $definition => $details) {
+            switch ($details['mode']) {
+                case cache_store::MODE_APPLICATION:
+                    $modeclass = 'application';
+                    $mode = ' <span title="application cache">[a]</span>';
+                    break;
+                case cache_store::MODE_SESSION:
+                    $modeclass = 'session';
+                    $mode = ' <span title="session cache">[s]</span>';
+                    break;
+                case cache_store::MODE_REQUEST:
+                    $modeclass = 'request';
+                    $mode = ' <span title="request cache">[r]</span>';
+                    break;
+            }
+            $html .= '<span class="cache-definition-stats cache-mode-'.$modeclass.'">';
+            $html .= '<span class="cache-definition-stats-heading">'.$definition.$mode.'</span>';
             $text .= "$definition {";
-            foreach ($stores as $store => $data) {
+            foreach ($details['stores'] as $store => $data) {
                 $hits += $data['hits'];
                 $misses += $data['misses'];
                 $sets += $data['sets'];
index ffa111f..b87a1b7 100644 (file)
@@ -3231,22 +3231,28 @@ class navbar extends navigation_node {
      */
     private function get_course_categories() {
         global $CFG;
-
         require_once($CFG->dirroot.'/course/lib.php');
+        require_once($CFG->libdir.'/coursecatlib.php');
+
         $categories = array();
         $cap = 'moodle/category:viewhiddencategories';
-        foreach ($this->page->categories as $category) {
-            if (!$category->visible && !has_capability($cap, get_category_or_system_context($category->parent))) {
-                continue;
-            }
-            $url = new moodle_url('/course/index.php', array('categoryid' => $category->id));
-            $name = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
-            $categorynode = navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id);
-            if (!$category->visible) {
-                $categorynode->hidden = true;
+        $showcategories = coursecat::count_all() > 1;
+
+        if ($showcategories) {
+            foreach ($this->page->categories as $category) {
+                if (!$category->visible && !has_capability($cap, get_category_or_system_context($category->parent))) {
+                    continue;
+                }
+                $url = new moodle_url('/course/index.php', array('categoryid' => $category->id));
+                $name = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
+                $categorynode = navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id);
+                if (!$category->visible) {
+                    $categorynode->hidden = true;
+                }
+                $categories[] = $categorynode;
             }
-            $categories[] = $categorynode;
         }
+
         if (is_enrolled(context_course::instance($this->page->course->id))) {
             $courses = $this->page->navigation->get('mycourses');
         } else {
index 04aeba4..38b4e94 100644 (file)
@@ -1513,6 +1513,19 @@ class html_writer {
 
         $countcols = 0;
 
+        // Output a caption if present.
+        if (!empty($table->caption)) {
+            $captionattributes = array();
+            if ($table->captionhide) {
+                $captionattributes['class'] = 'accesshide';
+            }
+            $output .= html_writer::tag(
+                'caption',
+                $table->caption,
+                $captionattributes
+            );
+        }
+
         if (!empty($table->head)) {
             $countcols = count($table->head);
 
@@ -2095,6 +2108,23 @@ class html_table {
      */
     public $summary;
 
+    /**
+     * @var string Caption for the table, typically a title.
+     *
+     * Example of usage:
+     * $t->caption = "TV Guide";
+     */
+    public $caption;
+
+    /**
+     * @var bool Whether to hide the table's caption from sighted users.
+     *
+     * Example of usage:
+     * $t->caption = "TV Guide";
+     * $t->captionhide = true;
+     */
+    public $captionhide = false;
+
     /**
      * Constructor
      */
index 155154b..23b0b4b 100644 (file)
@@ -819,7 +819,7 @@ class core_renderer extends renderer_base {
         $output .= $this->notification($message, 'redirectmessage');
         $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
         if ($debugdisableredirect) {
-            $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
+            $output .= '<p><strong>'.get_string('erroroutput', 'error').'</strong></p>';
         }
         $output .= $this->footer();
         return $output;
@@ -3399,7 +3399,7 @@ EOD;
         $linkurl = new moodle_url('/theme/switchdevice.php', array('url' => $this->page->url, 'device' => $devicetype, 'sesskey' => sesskey()));
 
         $content  = html_writer::start_tag('div', array('id' => 'theme_switch_link'));
-        $content .= html_writer::link($linkurl, $linktext);
+        $content .= html_writer::link($linkurl, $linktext, array('rel' => 'nofollow'));
         $content .= html_writer::end_tag('div');
 
         return $content;
index 13c8466..c089c6a 100644 (file)
@@ -190,7 +190,7 @@ function testing_update_composer_dependencies() {
     }
 
     // Update composer dependencies.
-    passthru("php composer.phar update --dev", $code);
+    passthru("php composer.phar update", $code);
     if ($code != 0) {
         exit($code);
     }
index 43f1ece..7e70965 100644 (file)
@@ -202,16 +202,53 @@ class core_html_writer_testcase extends basic_testcase {
         // The attribute will get overwritten by the ID above.
         $table->data[] = $row;
 
+        // Specify a caption to be output.
+        $table->caption = "A table of meaningless data.";
+
         $output = html_writer::table($table);
 
         $expected = <<<EOF
 <table class="generaltable" id="Jeffrey" data-name="Colin">
-<tbody><tr class="lastrow" id="Bob" data-name="Fred">
+<caption>A table of meaningless data.</caption><tbody><tr class="lastrow" id="Bob" data-name="Fred">
 <td class="cell c0 lastcol" id="Jeremy" data-name="John" style=""></td>
 </tr>
 </tbody>
 </table>
 
+EOF;
+        $this->assertSame($expected, $output);
+    }
+
+    public function test_table_hidden_caption() {
+
+        $table = new html_table();
+        $table->id = "whodat";
+        $table->data = array(
+            array('fred', 'MDK'),
+            array('bob',  'Burgers'),
+            array('dave', 'Competitiveness')
+        );
+        $table->caption = "Who even knows?";
+        $table->captionhide = true;
+
+        $output = html_writer::table($table);
+        $expected = <<<EOF
+<table class="generaltable" id="whodat">
+<caption class="accesshide">Who even knows?</caption><tbody><tr class="">
+<td class="cell c0" style="">fred</td>
+<td class="cell c1 lastcol" style="">MDK</td>
+</tr>
+<tr class="">
+<td class="cell c0" style="">bob</td>
+<td class="cell c1 lastcol" style="">Burgers</td>
+</tr>
+<tr class="lastrow">
+<td class="cell c0" style="">dave</td>
+<td class="cell c1 lastcol" style="">Competitiveness</td>
+</tr>
+</tbody>
+</table>
+
 EOF;
         $this->assertSame($expected, $output);
     }
diff --git a/lib/tests/message_test.php b/lib/tests/message_test.php
new file mode 100644 (file)
index 0000000..3713aac
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Test classes for \core\message\message.
+ *
+ * @package core_message
+ * @category test
+ * @copyright 2015 onwards Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+/**
+ * Test script for message class.
+ *
+ * @package core_message
+ * @category test
+ * @copyright 2015 onwards Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_message_testcase extends advanced_testcase {
+
+    /**
+     * Test the method get_eventobject_for_processor().
+     */
+    public function test_get_eventobject_for_processor() {
+        global $USER;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $user = $this->getDataGenerator()->create_user();
+
+        $message = new \core\message\message();
+        $message->component = 'moodle';
+        $message->name = 'instantmessage';
+        $message->userfrom = $USER;
+        $message->userto = $user;
+        $message->subject = 'message subject 1';
+        $message->fullmessage = 'message body';
+        $message->fullmessageformat = FORMAT_MARKDOWN;
+        $message->fullmessagehtml = '<p>message body</p>';
+        $message->smallmessage = 'small message';
+        $message->notification = '0';
+        $message->contexturl = 'http://GalaxyFarFarAway.com';
+        $message->contexturlname = 'Context name';
+        $message->replyto = "random@random.com";
+        $message->attachname = 'attachment';
+        $content = array('*' => array('header' => ' test ', 'footer' => ' test ')); // Extra content for all types of messages.
+        $message->set_additional_content('test', $content);
+
+        // Create a file instance.
+        $usercontext = context_user::instance($user->id);
+        $file = new stdClass;
+        $file->contextid = $usercontext->id;
+        $file->component = 'user';
+        $file->filearea  = 'private';
+        $file->itemid    = 0;
+        $file->filepath  = '/';
+        $file->filename  = '1.txt';
+        $file->source    = 'test';
+
+        $fs = get_file_storage();
+        $file = $fs->create_file_from_string($file, 'file1 content');
+        $message->attachment = $file;
+
+        $stdclass = $message->get_eventobject_for_processor('test');
+
+        $this->assertSame($message->component, $stdclass->component);
+        $this->assertSame($message->name, $stdclass->name);
+        $this->assertSame($message->userfrom, $stdclass->userfrom);
+        $this->assertSame($message->userto, $stdclass->userto);
+        $this->assertSame($message->subject, $stdclass->subject);
+        $this->assertSame(' test ' . $message->fullmessage . ' test ', $stdclass->fullmessage);
+        $this->assertSame(' test ' . $message->fullmessagehtml . ' test ', $stdclass->fullmessagehtml);
+        $this->assertSame(' test ' . $message->smallmessage . ' test ', $stdclass->smallmessage);
+        $this->assertSame($message->notification, $stdclass->notification);
+        $this->assertSame($message->contexturl, $stdclass->contexturl);
+        $this->assertSame($message->contexturlname, $stdclass->contexturlname);
+        $this->assertSame($message->replyto, $stdclass->replyto);
+        $this->assertSame($message->attachname, $stdclass->attachname);
+
+        // Extra content for fullmessage only.
+        $content = array('fullmessage' => array('header' => ' test ', 'footer' => ' test '));
+        $message->set_additional_content('test', $content);
+        $stdclass = $message->get_eventobject_for_processor('test');
+        $this->assertSame(' test ' . $message->fullmessage . ' test ', $stdclass->fullmessage);
+        $this->assertSame($message->fullmessagehtml, $stdclass->fullmessagehtml);
+        $this->assertSame($message->smallmessage, $stdclass->smallmessage);
+
+        // Extra content for fullmessagehtml and smallmessage only.
+        $content = array('fullmessagehtml' => array('header' => ' test ', 'footer' => ' test '),
+                         'smallmessage' => array('header' => ' testsmall ', 'footer' => ' testsmall '));
+        $message->set_additional_content('test', $content);
+        $stdclass = $message->get_eventobject_for_processor('test');
+        $this->assertSame($message->fullmessage, $stdclass->fullmessage);
+        $this->assertSame(' test ' . $message->fullmessagehtml . ' test ', $stdclass->fullmessagehtml);
+        $this->assertSame(' testsmall ' . $message->smallmessage . ' testsmall ', $stdclass->smallmessage);
+
+        // Extra content for * and smallmessage.
+        $content = array('*' => array('header' => ' test ', 'footer' => ' test '),
+                         'smallmessage' => array('header' => ' testsmall ', 'footer' => ' testsmall '));
+        $message->set_additional_content('test', $content);
+        $stdclass = $message->get_eventobject_for_processor('test');
+        $this->assertSame(' test ' . $message->fullmessage . ' test ', $stdclass->fullmessage);
+        $this->assertSame(' test ' . $message->fullmessagehtml . ' test ', $stdclass->fullmessagehtml);
+        $this->assertSame(' testsmall ' . ' test ' .  $message->smallmessage . ' test ' . ' testsmall ', $stdclass->smallmessage);
+    }
+
+    /**
+     * Test sending messages as email works with the new class.
+     */
+    public function test_send_message() {
+        global $DB, $CFG;
+        $this->preventResetByRollback();
+        $this->resetAfterTest();
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // Test basic email processor.
+        $this->assertFileExists("$CFG->dirroot/message/output/email/version.php");
+        $this->assertFileExists("$CFG->dirroot/message/output/popup/version.php");
+
+        $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
+        set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
+
+        // Extra content for all types of messages.
+        $message = new \core\message\message();
+        $message->component         = 'moodle';
+        $message->name              = 'instantmessage';
+        $message->userfrom          = $user1;
+        $message->userto            = $user2;
+        $message->subject           = 'message subject 1';
+        $message->fullmessage       = 'message body';
+        $message->fullmessageformat = FORMAT_MARKDOWN;
+        $message->fullmessagehtml   = '<p>message body</p>';
+        $message->smallmessage      = 'small message';
+        $message->notification      = '0';
+        $content = array('*' => array('header' => ' test ', 'footer' => ' test '));
+        $message->set_additional_content('email', $content);
+
+        $sink = $this->redirectEmails();
+        $messageid = message_send($message);
+        $emails = $sink->get_messages();
+        $this->assertCount(1, $emails);
+        $email = reset($emails);
+        $recordexists = $DB->record_exists('message_read', array('id' => $messageid));
+        $this->assertSame(true, $recordexists);
+        $this->assertSame($user1->email, $email->from);
+        $this->assertSame($user2->email, $email->to);
+        $this->assertSame($message->subject, $email->subject);
+        $this->assertNotEmpty($email->header);
+        $this->assertNotEmpty($email->body);
+        $this->assertRegExp('/test message body test/', $email->body);
+        $sink->clear();
+
+        // Extra content for small message only. Shouldn't show up in emails as we sent fullmessage and fullmessagehtml only in
+        // the emails.
+        $message = new \core\message\message();
+        $message->component         = 'moodle';
+        $message->name              = 'instantmessage';
+        $message->userfrom          = $user1;
+        $message->userto            = $user2;
+        $message->subject           = 'message subject 1';
+        $message->fullmessage       = 'message body';
+        $message->fullmessageformat = FORMAT_MARKDOWN;
+        $message->fullmessagehtml   = '<p>message body</p>';
+        $message->smallmessage      = 'small message';
+        $message->notification      = '0';
+        $content = array('smallmessage' => array('header' => ' test ', 'footer' => ' test '));
+        $message->set_additional_content('email', $content);
+
+        $messageid = message_send($message);
+        $emails = $sink->get_messages();
+        $this->assertCount(1, $emails);
+        $email = reset($emails);
+        $recordexists = $DB->record_exists('message_read', array('id' => $messageid));
+        $this->assertSame(true, $recordexists);
+        $this->assertSame($user1->email, $email->from);
+        $this->assertSame($user2->email, $email->to);
+        $this->assertSame($message->subject, $email->subject);
+        $this->assertNotEmpty($email->header);
+        $this->assertNotEmpty($email->body);
+        $this->assertNotRegExp('/test message body test/', $email->body);
+        $sink->close();
+    }
+}
\ No newline at end of file
index 77ab467..2eabcb2 100644 (file)
@@ -10,2288 +10,2315 @@ id,name,year,tzrule,gmtoff,dstoff,dst_month,dst_startday,dst_weekday,dst_skipwee
 9,Atlantic/Cape_Verde,1975,,-60,0,0,0,0,0,00:00,0,0,0,0,00:00
 10,Africa/Ndjamena,1970,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
 11,Africa/Ndjamena,1979,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-12,Indian/Comoro,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-13,Africa/Abidjan,1970,,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-14,Africa/Djibouti,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-15,Africa/Cairo,1970,Egypt,120,60,5,1,-1,0,03:00,10,1,-1,0,04:00
-16,Africa/Cairo,1982,Egypt,120,60,7,25,-1,0,03:00,10,1,-1,0,04:00
-17,Africa/Cairo,1983,Egypt,120,60,7,12,-1,0,03:00,10,1,-1,0,04:00
-18,Africa/Cairo,1984,Egypt,120,60,5,1,-1,0,03:00,10,1,-1,0,04:00
-19,Africa/Cairo,1989,Egypt,120,60,5,6,-1,0,03:00,10,1,-1,0,04:00
-20,Africa/Cairo,1990,Egypt,120,60,5,1,-1,0,03:00,10,1,-1,0,04:00
-21,Africa/Cairo,1995,Egypt,120,60,4,-1,5,0,02:00,9,-1,4,0,25:00
-22,Africa/Cairo,2006,Egypt,120,60,4,-1,5,0,02:00,9,21,-1,0,25:00
-23,Africa/Cairo,2007,Egypt,120,60,4,-1,5,0,02:00,9,1,4,0,25:00
-24,Africa/Cairo,2008,Egypt,120,60,4,-1,5,0,02:00,8,-1,4,0,25:00
-25,Africa/Cairo,2009,Egypt,120,60,4,-1,5,0,02:00,8,20,-1,0,25:00
-26,Africa/Cairo,2010,Egypt,120,60,9,9,-1,0,26:00,9,-1,4,0,25:00
-27,Africa/Cairo,2014,Egypt,120,60,7,31,-1,0,26:00,9,-1,4,0,25:00
-28,Africa/Cairo,2015,Egypt,120,60,7,23,-1,0,26:00,6,11,-1,0,25:00
-29,Africa/Cairo,2016,Egypt,120,60,7,7,-1,0,26:00,6,2,-1,0,25:00
-30,Africa/Cairo,2017,Egypt,120,60,6,29,-1,0,26:00,5,25,-1,0,25:00
-31,Africa/Cairo,2018,Egypt,120,60,6,14,-1,0,26:00,5,10,-1,0,25:00
-32,Africa/Cairo,2019,Egypt,120,60,6,6,-1,0,26:00,5,2,-1,0,25:00
-33,Africa/Cairo,2020,Egypt,120,60,5,28,-1,0,26:00,9,-1,4,0,25:00
-34,Africa/Cairo,2021,Egypt,120,60,5,13,-1,0,26:00,9,-1,4,0,25:00
-35,Africa/Cairo,2022,Egypt,120,60,5,5,-1,0,26:00,9,-1,4,0,25:00
-36,Africa/Cairo,2023,Egypt,120,60,4,-1,5,0,02:00,9,-1,4,0,25:00
-37,Africa/Asmara,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-38,Africa/Addis_Ababa,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-39,Africa/Accra,1970,Ghana,0,0,9,1,-1,0,00:00,12,31,-1,0,-1:00
-40,Africa/Bissau,1970,,-60,0,0,0,0,0,00:00,0,0,0,0,00:00
-41,Africa/Bissau,1975,,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-42,Africa/Nairobi,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-43,Africa/Monrovia,1970,,-44,0,0,0,0,0,00:00,0,0,0,0,00:00
-44,Africa/Monrovia,1972,,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-45,Africa/Tripoli,1970,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-46,Africa/Tripoli,1982,Libya,60,60,4,1,-1,0,01:00,10,1,-1,0,00:00
-47,Africa/Tripoli,1985,Libya,60,60,4,6,-1,0,01:00,10,1,-1,0,00:00
-48,Africa/Tripoli,1986,Libya,60,60,4,4,-1,0,01:00,10,3,-1,0,00:00
-49,Africa/Tripoli,1987,Libya,60,60,4,1,-1,0,01:00,10,1,-1,0,00:00
-50,Africa/Tripoli,1990,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-51,Africa/Tripoli,1996,Libya,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-52,Africa/Tripoli,1997,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-53,Africa/Tripoli,2012,Libya,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-54,Africa/Tripoli,2013,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-55,Indian/Antananarivo,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-56,Indian/Mauritius,1970,Mauritius,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-57,Indian/Mauritius,1982,Mauritius,240,60,10,10,-1,0,04:00,1,1,-1,0,15:00
-58,Indian/Mauritius,1983,Mauritius,240,0,12,31,-1,0,16:00,3,21,-1,0,03:00
-59,Indian/Mauritius,2008,Mauritius,240,60,10,-1,0,0,06:00,1,1,-1,0,15:00
-60,Indian/Mauritius,2009,Mauritius,240,0,12,31,-1,0,16:00,3,-1,0,0,05:00
-61,Indian/Mayotte,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-62,Africa/Casablanca,1970,Morocco,0,60,6,3,-1,0,12:00,10,1,-1,0,-1:00
-63,Africa/Casablanca,1974,Morocco,0,60,6,24,-1,0,00:00,9,1,-1,0,-1:00
-64,Africa/Casablanca,1976,Morocco,0,60,5,1,-1,0,00:00,8,1,-1,0,-1:00
-65,Africa/Casablanca,1977,Morocco,0,60,5,1,-1,0,00:00,9,28,-1,0,-1:00
-66,Africa/Casablanca,1978,Morocco,0,60,6,1,-1,0,00:00,8,4,-1,0,-1:00
-67,Africa/Casablanca,1984,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-68,Africa/Casablanca,1986,Morocco,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-69,Africa/Casablanca,2008,Morocco,0,60,6,1,-1,0,00:00,9,1,-1,0,-1:00
-70,Africa/Casablanca,2009,Morocco,0,60,6,1,-1,0,00:00,8,21,-1,0,-1:00
-71,Africa/Casablanca,2010,Morocco,0,60,5,2,-1,0,00:00,8,8,-1,0,-1:00
-72,Africa/Casablanca,2011,Morocco,0,60,4,3,-1,0,00:00,7,31,-1,0,-1:00
-73,Africa/Casablanca,2012,Morocco,0,60,8,20,-1,0,02:00,7,20,-1,0,02:00
-74,Africa/Casablanca,2013,Morocco,0,60,8,10,-1,0,02:00,10,-1,0,0,02:00
-75,Africa/Casablanca,2014,Morocco,0,60,8,2,-1,0,02:00,6,28,-1,0,02:00
-76,Africa/Casablanca,2015,Morocco,0,60,7,18,-1,0,02:00,6,13,-1,0,02:00
-77,Africa/Casablanca,2016,Morocco,0,60,7,9,-1,0,02:00,6,4,-1,0,02:00
-78,Africa/Casablanca,2017,Morocco,0,60,7,1,-1,0,02:00,5,20,-1,0,02:00
-79,Africa/Casablanca,2018,Morocco,0,60,6,16,-1,0,02:00,5,12,-1,0,02:00
-80,Africa/Casablanca,2019,Morocco,0,60,6,8,-1,0,02:00,5,4,-1,0,02:00
-81,Africa/Casablanca,2020,Morocco,0,60,5,30,-1,0,02:00,4,18,-1,0,02:00
-82,Africa/Casablanca,2021,Morocco,0,60,5,15,-1,0,02:00,4,10,-1,0,02:00
-83,Africa/Casablanca,2022,Morocco,0,60,5,7,-1,0,02:00,4,2,-1,0,02:00
-84,Africa/Casablanca,2023,Morocco,0,60,4,22,-1,0,02:00,10,-1,0,0,02:00
-85,Africa/El_Aaiun,1970,,-60,0,0,0,0,0,00:00,0,0,0,0,00:00
-86,Africa/El_Aaiun,1976,Morocco,0,60,5,1,-1,0,00:00,8,1,-1,0,-1:00
-87,Africa/El_Aaiun,1977,Morocco,0,60,5,1,-1,0,00:00,9,28,-1,0,-1:00
-88,Africa/El_Aaiun,1978,Morocco,0,60,6,1,-1,0,00:00,8,4,-1,0,-1:00
-89,Africa/El_Aaiun,2008,Morocco,0,60,6,1,-1,0,00:00,9,1,-1,0,-1:00
-90,Africa/El_Aaiun,2009,Morocco,0,60,6,1,-1,0,00:00,8,21,-1,0,-1:00
-91,Africa/El_Aaiun,2010,Morocco,0,60,5,2,-1,0,00:00,8,8,-1,0,-1:00
-92,Africa/El_Aaiun,2011,Morocco,0,60,4,3,-1,0,00:00,7,31,-1,0,-1:00
-93,Africa/El_Aaiun,2012,Morocco,0,60,8,20,-1,0,02:00,7,20,-1,0,02:00
-94,Africa/El_Aaiun,2013,Morocco,0,60,8,10,-1,0,02:00,10,-1,0,0,02:00
-95,Africa/El_Aaiun,2014,Morocco,0,60,8,2,-1,0,02:00,6,28,-1,0,02:00
-96,Africa/El_Aaiun,2015,Morocco,0,60,7,18,-1,0,02:00,6,13,-1,0,02:00
-97,Africa/El_Aaiun,2016,Morocco,0,60,7,9,-1,0,02:00,6,4,-1,0,02:00
-98,Africa/El_Aaiun,2017,Morocco,0,60,7,1,-1,0,02:00,5,20,-1,0,02:00
-99,Africa/El_Aaiun,2018,Morocco,0,60,6,16,-1,0,02:00,5,12,-1,0,02:00
-100,Africa/El_Aaiun,2019,Morocco,0,60,6,8,-1,0,02:00,5,4,-1,0,02:00
-101,Africa/El_Aaiun,2020,Morocco,0,60,5,30,-1,0,02:00,4,18,-1,0,02:00
-102,Africa/El_Aaiun,2021,Morocco,0,60,5,15,-1,0,02:00,4,10,-1,0,02:00
-103,Africa/El_Aaiun,2022,Morocco,0,60,5,7,-1,0,02:00,4,2,-1,0,02:00
-104,Africa/El_Aaiun,2023,Morocco,0,60,4,22,-1,0,02:00,10,-1,0,0,02:00
-105,Africa/Maputo,1970,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-106,Africa/Windhoek,1970,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-107,Africa/Windhoek,1990,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-108,Africa/Windhoek,1994,Namibia,60,60,9,1,0,0,03:00,1,1,-1,0,12:00
-109,Africa/Windhoek,1995,Namibia,60,60,9,1,0,0,03:00,4,1,0,0,02:00
-110,Africa/Lagos,1970,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-111,Indian/Reunion,1970,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-112,Indian/Mahe,1970,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-113,Africa/Mogadishu,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-114,Africa/Johannesburg,1970,SA,120,0,12,31,-1,0,14:00,3,15,0,0,03:00
-115,Africa/Khartoum,1970,Sudan,120,60,5,1,-1,0,02:00,10,15,-1,0,01:00
-116,Africa/Khartoum,1971,Sudan,120,60,4,30,-1,0,02:00,10,15,-1,0,01:00
-117,Africa/Khartoum,1972,Sudan,120,60,4,-1,0,0,02:00,10,15,-1,0,01:00
-118,Africa/Khartoum,1985,Sudan,120,0,4,-1,0,0,02:00,10,15,-1,0,01:00
-119,Africa/Khartoum,2000,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-120,Africa/Dar_es_Salaam,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-121,Africa/Tunis,1970,Tunisia,60,60,4,1,1,0,03:00,9,16,-1,0,00:00
-122,Africa/Tunis,1977,Tunisia,60,60,4,30,-1,0,01:00,9,24,-1,0,01:00
-123,Africa/Tunis,1978,Tunisia,60,60,5,1,-1,0,01:00,10,1,-1,0,01:00
-124,Africa/Tunis,1988,Tunisia,60,60,6,1,-1,0,01:00,9,-1,0,0,01:00
-125,Africa/Tunis,1989,Tunisia,60,60,3,26,-1,0,01:00,9,-1,0,0,01:00
-126,Africa/Tunis,1990,Tunisia,60,60,5,1,-1,0,01:00,9,-1,0,0,01:00
-127,Africa/Tunis,2005,Tunisia,60,60,5,1,-1,0,01:00,9,30,-1,0,02:00
-128,Africa/Tunis,2006,Tunisia,60,60,3,-1,0,0,03:00,10,-1,0,0,03:00
-129,Africa/Tunis,2008,Tunisia,60,0,3,-1,0,0,03:00,10,-1,0,0,03:00
-130,Africa/Kampala,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-131,Antarctica/Casey,1970,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-132,Antarctica/Casey,2009,,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-133,Antarctica/Casey,2010,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-134,Antarctica/Casey,2011,,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-135,Antarctica/Casey,2012,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-136,Antarctica/Davis,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-137,Antarctica/Davis,2009,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-138,Antarctica/Davis,2010,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-139,Antarctica/Davis,2011,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-140,Antarctica/Davis,2012,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-141,Antarctica/Mawson,1970,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-142,Antarctica/Mawson,2009,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-143,Indian/Kerguelen,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-144,Antarctica/DumontDUrville,1970,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-145,Antarctica/Syowa,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-146,Antarctica/Troll,1970,,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-147,Antarctica/Troll,2005,Troll,0,0,10,-1,0,0,1:00,1,1,-1,0,11:00
-148,Antarctica/Vostok,1970,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-149,Antarctica/Rothera,1970,,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-150,Antarctica/Rothera,1976,,-180,0,0,0,0,0,00:00,0,0,0,0,00:00
-151,Antarctica/Palmer,1970,ArgAQ,-180,0,12,31,-1,0,09:00,4,1,0,0,-4:00
-152,Antarctica/Palmer,1974,ArgAQ,-180,60,1,23,-1,0,-3:00,5,1,-1,0,-4:00
-153,Antarctica/Palmer,1975,ArgAQ,-180,0,1,23,-1,0,-3:00,5,1,-1,0,-4:00
-154,Antarctica/Palmer,1982,ChileAQ,-240,60,10,9,0,0,4:00,3,9,0,0,3:00
-155,Antarctica/Palmer,1987,ChileAQ,-240,60,10,9,0,0,4:00,4,12,-1,0,3:00
-156,Antarctica/Palmer,1988,ChileAQ,-240,60,10,1,0,0,4:00,3,9,0,0,3:00
-157,Antarctica/Palmer,1989,ChileAQ,-240,60,10,9,0,0,4:00,3,9,0,0,3:00
-158,Antarctica/Palmer,1990,ChileAQ,-240,60,9,16,-1,0,4:00,3,18,-1,0,3:00
-159,Antarctica/Palmer,1991,ChileAQ,-240,60,10,9,0,0,4:00,3,9,0,0,3:00
-160,Antarctica/Palmer,1997,ChileAQ,-240,60,10,9,0,0,4:00,3,30,-1,0,3:00
-161,Antarctica/Palmer,1998,ChileAQ,-240,60,9,27,-1,0,4:00,3,9,0,0,3:00
-162,Antarctica/Palmer,1999,ChileAQ,-240,60,10,9,0,0,4:00,4,4,-1,0,3:00
-163,Antarctica/Palmer,2000,ChileAQ,-240,60,10,9,0,0,4:00,3,9,0,0,3:00
-164,Antarctica/Palmer,2008,ChileAQ,-240,60,10,9,0,0,4:00,3,30,-1,0,3:00
-165,Antarctica/Palmer,2009,ChileAQ,-240,60,10,9,0,0,4:00,3,9,0,0,3:00
-166,Antarctica/Palmer,2010,ChileAQ,-240,60,10,9,0,0,4:00,4,1,0,0,3:00
-167,Antarctica/Palmer,2011,ChileAQ,-240,60,8,16,0,0,4:00,5,2,0,0,3:00
-168,Antarctica/Palmer,2012,ChileAQ,-240,60,9,2,0,0,4:00,4,23,0,0,3:00
-169,Asia/Kabul,1970,,270,0,0,0,0,0,00:00,0,0,0,0,00:00
-170,Asia/Yerevan,1970,RussiaAsia,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-171,Asia/Yerevan,1981,RussiaAsia,240,60,4,1,-1,0,04:00,10,1,-1,0,03:00
-172,Asia/Yerevan,1984,RussiaAsia,240,60,4,1,-1,0,04:00,9,-1,0,0,06:00
-173,Asia/Yerevan,1985,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-174,Asia/Yerevan,1991,RussiaAsia,180,60,3,-1,0,0,05:00,9,-1,0,0,05:00
-175,Asia/Yerevan,1992,RussiaAsia,180,60,3,-1,6,0,26:00,9,-1,6,0,25:00
-176,Asia/Yerevan,1993,RussiaAsia,180,60,3,-1,0,0,05:00,9,-1,0,0,05:00
-177,Asia/Yerevan,1995,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-178,Asia/Yerevan,1997,RussiaAsia,240,60,3,-1,0,0,06:00,10,-1,0,0,06:00
-179,Asia/Yerevan,2012,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-180,Asia/Baku,1970,RussiaAsia,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-181,Asia/Baku,1981,RussiaAsia,240,60,4,1,-1,0,04:00,10,1,-1,0,03:00
-182,Asia/Baku,1984,RussiaAsia,240,60,4,1,-1,0,04:00,9,-1,0,0,06:00
-183,Asia/Baku,1985,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-184,Asia/Baku,1991,RussiaAsia,180,60,3,-1,0,0,05:00,9,-1,0,0,05:00
-185,Asia/Baku,1992,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-186,Asia/Baku,1996,EUAsia,240,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-187,Asia/Baku,1997,Azer,240,60,3,-1,0,0,08:00,10,-1,0,0,08:00
-188,Asia/Bahrain,1970,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-189,Asia/Bahrain,1972,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-190,Asia/Dhaka,1970,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-191,Asia/Dhaka,1971,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-192,Asia/Dhaka,2009,Dhaka,360,60,6,19,-1,0,29:00,12,31,-1,0,29:00
-193,Asia/Dhaka,2010,Dhaka,360,0,6,19,-1,0,29:00,12,31,-1,0,29:00
-194,Asia/Thimphu,1970,,330,0,0,0,0,0,00:00,0,0,0,0,00:00
-195,Asia/Thimphu,1987,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-196,Indian/Chagos,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-197,Indian/Chagos,1996,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-198,Asia/Brunei,1970,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-199,Asia/Rangoon,1970,,390,0,0,0,0,0,00:00,0,0,0,0,00:00
-200,Asia/Phnom_Penh,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-201,Asia/Shanghai,1970,PRC,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-202,Asia/Shanghai,1986,PRC,480,60,5,4,-1,0,08:00,9,11,0,0,07:00
-203,Asia/Shanghai,1987,PRC,480,60,4,10,0,0,08:00,9,11,0,0,07:00
-204,Asia/Shanghai,1991,PRC,480,0,4,10,0,0,08:00,9,11,0,0,07:00
-205,Asia/Urumqi,1970,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-206,Asia/Hong_Kong,1970,HK,480,60,4,16,0,0,11:30,10,16,0,0,10:30
-207,Asia/Hong_Kong,1973,HK,480,60,12,30,-1,0,11:30,10,16,0,0,10:30
-208,Asia/Hong_Kong,1974,HK,480,60,4,16,0,0,11:30,10,16,0,0,10:30
-209,Asia/Hong_Kong,1979,HK,480,60,5,8,0,0,11:30,10,16,0,0,10:30
-210,Asia/Hong_Kong,1980,HK,480,0,5,8,0,0,11:30,10,16,0,0,10:30
-211,Asia/Taipei,1970,Taiwan,480,60,6,1,-1,0,08:00,10,1,-1,0,07:00
-212,Asia/Taipei,1974,Taiwan,480,60,4,1,-1,0,08:00,10,1,-1,0,07:00
-213,Asia/Taipei,1979,Taiwan,480,60,7,1,-1,0,08:00,10,1,-1,0,07:00
-214,Asia/Taipei,1980,Taiwan,480,0,7,1,-1,0,08:00,10,1,-1,0,07:00
-215,Asia/Macau,1970,Macau,480,60,4,16,0,0,11:30,10,16,0,0,10:30
-216,Asia/Macau,1972,Macau,480,60,4,15,0,0,08:00,10,15,0,0,07:00
-217,Asia/Macau,1974,Macau,480,60,4,15,0,0,08:00,10,15,0,0,10:30
-218,Asia/Macau,1975,Macau,480,60,4,15,0,0,11:30,10,15,0,0,10:30
-219,Asia/Macau,1978,Macau,480,60,4,15,0,0,08:00,10,15,0,0,07:00
-220,Asia/Macau,1980,Macau,480,0,4,15,0,0,08:00,10,15,0,0,07:00
-221,Asia/Macau,1999,PRC,480,0,4,15,0,0,08:00,10,15,0,0,07:00
-222,Asia/Nicosia,1970,Cyprus,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-223,Asia/Nicosia,1975,Cyprus,120,60,4,13,-1,0,02:00,10,12,-1,0,01:00
-224,Asia/Nicosia,1976,Cyprus,120,60,5,15,-1,0,02:00,10,11,-1,0,01:00
-225,Asia/Nicosia,1977,Cyprus,120,60,4,1,0,0,02:00,9,25,-1,0,01:00
-226,Asia/Nicosia,1978,Cyprus,120,60,4,1,0,0,02:00,10,2,-1,0,01:00
-227,Asia/Nicosia,1979,Cyprus,120,60,4,1,0,0,02:00,9,-1,0,0,01:00
-228,Asia/Nicosia,1981,Cyprus,120,60,3,-1,0,0,02:00,9,-1,0,0,01:00
-229,Asia/Nicosia,1998,EUAsia,120,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-230,Asia/Tbilisi,1970,RussiaAsia,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-231,Asia/Tbilisi,1981,RussiaAsia,240,60,4,1,-1,0,04:00,10,1,-1,0,03:00
-232,Asia/Tbilisi,1984,RussiaAsia,240,60,4,1,-1,0,04:00,9,-1,0,0,06:00
-233,Asia/Tbilisi,1985,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-234,Asia/Tbilisi,1991,RussiaAsia,180,60,3,-1,0,0,05:00,9,-1,0,0,05:00
-235,Asia/Tbilisi,1992,E-EurAsia,180,60,3,-1,0,0,03:00,9,-1,0,0,02:00
-236,Asia/Tbilisi,1994,E-EurAsia,240,60,3,-1,0,0,04:00,9,-1,0,0,03:00
-237,Asia/Tbilisi,1995,E-EurAsia,240,0,12,31,-1,0,16:00,9,-1,0,0,03:00
-238,Asia/Tbilisi,1996,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-239,Asia/Tbilisi,1997,E-EurAsia,240,60,3,-1,0,0,04:00,10,-1,0,0,03:00
-240,Asia/Tbilisi,2004,RussiaAsia,180,60,3,-1,0,0,05:00,10,-1,0,0,05:00
-241,Asia/Tbilisi,2005,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-242,Asia/Dili,1970,,540,0,0,0,0,0,00:00,0,0,0,0,00:00
-243,Asia/Dili,1976,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-244,Asia/Dili,2000,,540,0,0,0,0,0,00:00,0,0,0,0,00:00
-245,Asia/Kolkata,1970,,330,0,0,0,0,0,00:00,0,0,0,0,00:00
-246,Asia/Jakarta,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-247,Asia/Pontianak,1970,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-248,Asia/Pontianak,1988,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-249,Asia/Makassar,1970,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-250,Asia/Jayapura,1970,,540,0,0,0,0,0,00:00,0,0,0,0,00:00
-251,Asia/Tehran,1970,,210,0,0,0,0,0,00:00,0,0,0,0,00:00
-252,Asia/Tehran,1977,Iran,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-253,Asia/Tehran,1978,Iran,240,60,3,21,-1,0,04:00,10,21,-1,0,03:00
-254,Asia/Tehran,1979,Iran,210,60,3,21,-1,0,03:30,9,19,-1,0,02:30
-255,Asia/Tehran,1980,Iran,210,60,3,21,-1,0,03:30,9,23,-1,0,02:30
-256,Asia/Tehran,1991,Iran,210,60,5,3,-1,0,03:30,9,22,-1,0,02:30
-257,Asia/Tehran,1992,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-258,Asia/Tehran,1996,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-259,Asia/Tehran,1997,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-260,Asia/Tehran,2000,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-261,Asia/Tehran,2001,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-262,Asia/Tehran,2004,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-263,Asia/Tehran,2005,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-264,Asia/Tehran,2008,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-265,Asia/Tehran,2009,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-266,Asia/Tehran,2012,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-267,Asia/Tehran,2013,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-268,Asia/Tehran,2016,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-269,Asia/Tehran,2017,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-270,Asia/Tehran,2020,Iran,210,60,3,21,-1,0,03:30,9,21,-1,0,02:30
-271,Asia/Tehran,2021,Iran,210,60,3,22,-1,0,03:30,9,22,-1,0,02:30
-272,Asia/Baghdad,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-273,Asia/Baghdad,1982,Iraq,180,60,5,1,-1,0,03:00,10,1,-1,0,02:00
-274,Asia/Baghdad,1983,Iraq,180,60,3,31,-1,0,03:00,10,1,-1,0,02:00
-275,Asia/Baghdad,1984,Iraq,180,60,4,1,-1,0,03:00,10,1,-1,0,02:00
-276,Asia/Baghdad,1985,Iraq,180,60,4,1,-1,0,03:00,9,-1,0,0,04:00
-277,Asia/Baghdad,1986,Iraq,180,60,3,-1,0,0,04:00,9,-1,0,0,04:00
-278,Asia/Baghdad,1991,Iraq,180,60,4,1,-1,0,06:00,10,1,-1,0,06:00
-279,Asia/Baghdad,2007,Iraq,180,0,4,1,-1,0,06:00,10,1,-1,0,06:00
-280,Asia/Jerusalem,1970,Zion,120,60,4,29,-1,0,04:00,9,22,-1,0,01:00
-281,Asia/Jerusalem,1974,Zion,120,60,7,7,-1,0,02:00,10,13,-1,0,01:00
-282,Asia/Jerusalem,1975,Zion,120,60,4,20,-1,0,02:00,8,31,-1,0,01:00
-283,Asia/Jerusalem,1985,Zion,120,60,4,14,-1,0,02:00,9,15,-1,0,01:00
-284,Asia/Jerusalem,1986,Zion,120,60,5,18,-1,0,02:00,9,7,-1,0,01:00
-285,Asia/Jerusalem,1987,Zion,120,60,4,15,-1,0,02:00,9,13,-1,0,01:00
-286,Asia/Jerusalem,1988,Zion,120,60,4,10,-1,0,02:00,9,4,-1,0,01:00
-287,Asia/Jerusalem,1989,Zion,120,60,4,30,-1,0,02:00,9,3,-1,0,01:00
-288,Asia/Jerusalem,1990,Zion,120,60,3,25,-1,0,02:00,8,26,-1,0,01:00
-289,Asia/Jerusalem,1991,Zion,120,60,3,24,-1,0,02:00,9,1,-1,0,01:00
-290,Asia/Jerusalem,1992,Zion,120,60,3,29,-1,0,02:00,9,6,-1,0,01:00
-291,Asia/Jerusalem,1993,Zion,120,60,4,2,-1,0,02:00,9,5,-1,0,01:00
-292,Asia/Jerusalem,1994,Zion,120,60,4,1,-1,0,02:00,8,28,-1,0,01:00
-293,Asia/Jerusalem,1995,Zion,120,60,3,31,-1,0,02:00,9,3,-1,0,01:00
-294,Asia/Jerusalem,1996,Zion,120,60,3,15,-1,0,02:00,9,16,-1,0,01:00
-295,Asia/Jerusalem,1997,Zion,120,60,3,21,-1,0,02:00,9,14,-1,0,01:00
-296,Asia/Jerusalem,1998,Zion,120,60,3,20,-1,0,02:00,9,6,-1,0,01:00
-297,Asia/Jerusalem,1999,Zion,120,60,4,2,-1,0,04:00,9,3,-1,0,03:00
-298,Asia/Jerusalem,2000,Zion,120,60,4,14,-1,0,04:00,10,6,-1,0,02:00
-299,Asia/Jerusalem,2001,Zion,120,60,4,9,-1,0,03:00,9,24,-1,0,02:00
-300,Asia/Jerusalem,2002,Zion,120,60,3,29,-1,0,03:00,10,7,-1,0,02:00
-301,Asia/Jerusalem,2003,Zion,120,60,3,28,-1,0,03:00,10,3,-1,0,02:00
-302,Asia/Jerusalem,2004,Zion,120,60,4,7,-1,0,03:00,9,22,-1,0,02:00
-303,Asia/Jerusalem,2005,Zion,120,60,4,1,-1,0,04:00,10,9,-1,0,03:00
-304,Asia/Jerusalem,2006,Zion,120,60,3,26,5,0,04:00,10,1,-1,0,03:00
-305,Asia/Jerusalem,2007,Zion,120,60,3,26,5,0,04:00,9,16,-1,0,03:00
-306,Asia/Jerusalem,2008,Zion,120,60,3,26,5,0,04:00,10,5,-1,0,03:00
-307,Asia/Jerusalem,2009,Zion,120,60,3,26,5,0,04:00,9,27,-1,0,03:00
-308,Asia/Jerusalem,2010,Zion,120,60,3,26,5,0,04:00,9,12,-1,0,03:00
-309,Asia/Jerusalem,2011,Zion,120,60,4,1,-1,0,04:00,10,2,-1,0,03:00
-310,Asia/Jerusalem,2012,Zion,120,60,3,26,5,0,04:00,9,23,-1,0,03:00
-311,Asia/Jerusalem,2013,Zion,120,60,3,23,5,0,04:00,10,-1,0,0,03:00
-312,Asia/Tokyo,1970,Japan,540,0,5,1,0,0,11:00,9,8,6,0,10:00
-313,Asia/Amman,1970,Jordan,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-314,Asia/Amman,1973,Jordan,120,60,6,6,-1,0,02:00,10,1,-1,0,01:00
-315,Asia/Amman,1974,Jordan,120,60,5,1,-1,0,02:00,10,1,-1,0,01:00
-316,Asia/Amman,1976,Jordan,120,60,5,1,-1,0,02:00,11,1,-1,0,01:00
-317,Asia/Amman,1977,Jordan,120,60,5,1,-1,0,02:00,10,1,-1,0,01:00
-318,Asia/Amman,1978,Jordan,120,60,4,30,-1,0,02:00,9,30,-1,0,01:00
-319,Asia/Amman,1985,Jordan,120,60,4,1,-1,0,02:00,10,1,-1,0,01:00
-320,Asia/Amman,1986,Jordan,120,60,4,1,5,0,02:00,10,1,5,0,01:00
-321,Asia/Amman,1989,Jordan,120,60,5,8,-1,0,02:00,10,1,5,0,01:00
-322,Asia/Amman,1990,Jordan,120,60,4,27,-1,0,02:00,10,1,5,0,01:00
-323,Asia/Amman,1991,Jordan,120,60,4,17,-1,0,02:00,9,27,-1,0,01:00
-324,Asia/Amman,1992,Jordan,120,60,4,10,-1,0,02:00,10,1,5,0,01:00
-325,Asia/Amman,1993,Jordan,120,60,4,1,5,0,02:00,10,1,5,0,01:00
-326,Asia/Amman,1994,Jordan,120,60,4,1,5,0,02:00,9,15,5,0,01:00
-327,Asia/Amman,1995,Jordan,120,60,4,1,5,0,02:00,9,15,5,0,02:00
-328,Asia/Amman,1999,Jordan,120,60,7,1,-1,0,02:00,9,-1,5,0,02:00
-329,Asia/Amman,2000,Jordan,120,60,3,-1,4,0,02:00,9,-1,5,0,02:00
-330,Asia/Amman,2002,Jordan,120,60,3,-1,4,0,26:00,9,-1,5,0,02:00
-331,Asia/Amman,2003,Jordan,120,60,3,-1,4,0,26:00,10,24,-1,0,02:00
-332,Asia/Amman,2004,Jordan,120,60,3,-1,4,0,26:00,10,15,-1,0,02:00
-333,Asia/Amman,2005,Jordan,120,60,3,-1,4,0,26:00,9,-1,5,0,02:00
-334,Asia/Amman,2006,Jordan,120,60,3,-1,4,0,26:00,10,-1,5,0,02:00
-335,Asia/Amman,2012,Jordan,120,60,3,-1,4,0,26:00,1,1,-1,0,13:00
-336,Asia/Amman,2013,Jordan,120,0,12,31,-1,0,14:00,12,20,-1,0,01:00
-337,Asia/Amman,2014,Jordan,120,60,3,-1,4,0,26:00,10,-1,5,0,02:00
-338,Asia/Almaty,1970,RussiaAsia,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-339,Asia/Almaty,1981,RussiaAsia,360,60,4,1,-1,0,06:00,10,1,-1,0,05:00
-340,Asia/Almaty,1984,RussiaAsia,360,60,4,1,-1,0,06:00,9,-1,0,0,08:00
-341,Asia/Almaty,1985,RussiaAsia,360,60,3,-1,0,0,08:00,9,-1,0,0,08:00
-342,Asia/Almaty,1991,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-343,Asia/Almaty,1992,RussiaAsia,360,60,3,-1,6,0,29:00,9,-1,6,0,28:00
-344,Asia/Almaty,1993,RussiaAsia,360,60,3,-1,0,0,08:00,9,-1,0,0,08:00
-345,Asia/Almaty,1996,RussiaAsia,360,60,3,-1,0,0,08:00,10,-1,0,0,08:00
-346,Asia/Almaty,2005,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-347,Asia/Qyzylorda,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-348,Asia/Qyzylorda,1981,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-349,Asia/Qyzylorda,1982,RussiaAsia,300,60,4,1,-1,0,05:00,10,1,-1,0,04:00
-350,Asia/Qyzylorda,1984,RussiaAsia,300,60,4,1,-1,0,05:00,9,-1,0,0,07:00
-351,Asia/Qyzylorda,1985,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-352,Asia/Qyzylorda,1991,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-353,Asia/Qyzylorda,1992,RussiaAsia,360,60,3,-1,6,0,29:00,9,-1,6,0,28:00
-354,Asia/Qyzylorda,1993,RussiaAsia,360,60,3,-1,0,0,08:00,9,-1,0,0,08:00
-355,Asia/Qyzylorda,1996,RussiaAsia,360,60,3,-1,0,0,08:00,10,-1,0,0,08:00
-356,Asia/Qyzylorda,2005,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-357,Asia/Aqtobe,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-358,Asia/Aqtobe,1981,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-359,Asia/Aqtobe,1982,RussiaAsia,300,60,4,1,-1,0,05:00,10,1,-1,0,04:00
-360,Asia/Aqtobe,1984,RussiaAsia,300,60,4,1,-1,0,05:00,9,-1,0,0,07:00
-361,Asia/Aqtobe,1985,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-362,Asia/Aqtobe,1992,RussiaAsia,300,60,3,-1,6,0,28:00,9,-1,6,0,27:00
-363,Asia/Aqtobe,1993,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-364,Asia/Aqtobe,1996,RussiaAsia,300,60,3,-1,0,0,07:00,10,-1,0,0,07:00
-365,Asia/Aqtobe,2005,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-366,Asia/Aqtau,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-367,Asia/Aqtau,1981,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-368,Asia/Aqtau,1982,RussiaAsia,300,60,4,1,-1,0,05:00,10,1,-1,0,04:00
-369,Asia/Aqtau,1984,RussiaAsia,300,60,4,1,-1,0,05:00,9,-1,0,0,07:00
-370,Asia/Aqtau,1985,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-371,Asia/Aqtau,1992,RussiaAsia,300,60,3,-1,6,0,28:00,9,-1,6,0,27:00
-372,Asia/Aqtau,1993,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-373,Asia/Aqtau,1995,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-374,Asia/Aqtau,1996,RussiaAsia,240,60,3,-1,0,0,06:00,10,-1,0,0,06:00
-375,Asia/Aqtau,2005,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-376,Asia/Oral,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-377,Asia/Oral,1981,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-378,Asia/Oral,1982,RussiaAsia,300,60,4,1,-1,0,05:00,10,1,-1,0,04:00
-379,Asia/Oral,1984,RussiaAsia,300,60,4,1,-1,0,05:00,9,-1,0,0,07:00
-380,Asia/Oral,1985,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-381,Asia/Oral,1989,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-382,Asia/Oral,1992,RussiaAsia,240,60,3,-1,6,0,27:00,9,-1,6,0,26:00
-383,Asia/Oral,1993,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-384,Asia/Oral,1996,RussiaAsia,240,60,3,-1,0,0,06:00,10,-1,0,0,06:00
-385,Asia/Oral,2005,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-386,Asia/Bishkek,1970,RussiaAsia,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-387,Asia/Bishkek,1981,RussiaAsia,360,60,4,1,-1,0,06:00,10,1,-1,0,05:00
-388,Asia/Bishkek,1984,RussiaAsia,360,60,4,1,-1,0,06:00,9,-1,0,0,08:00
-389,Asia/Bishkek,1985,RussiaAsia,360,60,3,-1,0,0,08:00,9,-1,0,0,08:00
-390,Asia/Bishkek,1991,Kyrgyz,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-391,Asia/Bishkek,1992,Kyrgyz,300,60,4,7,0,0,05:00,9,-1,0,0,04:00
-392,Asia/Bishkek,1997,Kyrgyz,300,60,3,-1,0,0,07:30,10,-1,0,0,06:30
-393,Asia/Bishkek,2005,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-394,Asia/Seoul,1970,ROK,540,60,5,15,-1,0,09:00,9,13,-1,0,08:00
-395,Asia/Seoul,1987,ROK,540,60,5,8,0,0,09:00,10,8,0,0,08:00
-396,Asia/Seoul,1988,ROK,540,0,5,8,0,0,09:00,10,8,0,0,08:00
-397,Asia/Pyongyang,1970,,540,0,0,0,0,0,00:00,0,0,0,0,00:00
-398,Asia/Kuwait,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-399,Asia/Vientiane,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-400,Asia/Beirut,1970,Lebanon,120,60,5,1,-1,0,02:00,10,1,-1,0,01:00
-401,Asia/Beirut,1972,Lebanon,120,60,6,22,-1,0,02:00,10,1,-1,0,01:00
-402,Asia/Beirut,1973,Lebanon,120,60,5,1,-1,0,02:00,10,1,-1,0,01:00
-403,Asia/Beirut,1978,Lebanon,120,60,4,30,-1,0,02:00,9,30,-1,0,01:00
-404,Asia/Beirut,1984,Lebanon,120,60,5,1,-1,0,02:00,10,16,-1,0,01:00
-405,Asia/Beirut,1988,Lebanon,120,60,6,1,-1,0,02:00,10,16,-1,0,01:00
-406,Asia/Beirut,1989,Lebanon,120,60,5,10,-1,0,02:00,10,16,-1,0,01:00
-407,Asia/Beirut,1990,Lebanon,120,60,5,1,-1,0,02:00,10,16,-1,0,01:00
-408,Asia/Beirut,1992,Lebanon,120,60,5,1,-1,0,02:00,10,4,-1,0,01:00
-409,Asia/Beirut,1993,Lebanon,120,60,3,-1,0,0,02:00,9,-1,0,0,01:00
-410,Asia/Beirut,1999,Lebanon,120,60,3,-1,0,0,02:00,10,-1,0,0,01:00
-411,Asia/Kuala_Lumpur,1970,,450,0,0,0,0,0,00:00,0,0,0,0,00:00
-412,Asia/Kuala_Lumpur,1982,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-413,Asia/Kuching,1970,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-414,Asia/Kuching,1982,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-415,Indian/Maldives,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-416,Asia/Hovd,1970,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-417,Asia/Hovd,1978,Mongol,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-418,Asia/Hovd,1983,Mongol,420,60,4,1,-1,0,07:00,10,1,-1,0,06:00
-419,Asia/Hovd,1984,Mongol,420,60,4,1,-1,0,07:00,9,-1,0,0,06:00
-420,Asia/Hovd,1985,Mongol,420,60,3,-1,0,0,07:00,9,-1,0,0,06:00
-421,Asia/Hovd,2001,Mongol,420,60,4,-1,6,0,09:00,9,-1,6,0,08:00
-422,Asia/Hovd,2002,Mongol,420,60,3,-1,6,0,09:00,9,-1,6,0,08:00
-423,Asia/Hovd,2006,Mongol,420,0,3,-1,6,0,09:00,9,-1,6,0,08:00
-424,Asia/Ulaanbaatar,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-425,Asia/Ulaanbaatar,1978,Mongol,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-426,Asia/Ulaanbaatar,1983,Mongol,480,60,4,1,-1,0,08:00,10,1,-1,0,07:00
-427,Asia/Ulaanbaatar,1984,Mongol,480,60,4,1,-1,0,08:00,9,-1,0,0,07:00
-428,Asia/Ulaanbaatar,1985,Mongol,480,60,3,-1,0,0,08:00,9,-1,0,0,07:00
-429,Asia/Ulaanbaatar,2001,Mongol,480,60,4,-1,6,0,10:00,9,-1,6,0,09:00
-430,Asia/Ulaanbaatar,2002,Mongol,480,60,3,-1,6,0,10:00,9,-1,6,0,09:00
-431,Asia/Ulaanbaatar,2006,Mongol,480,0,3,-1,6,0,10:00,9,-1,6,0,09:00
-432,Asia/Choibalsan,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-433,Asia/Choibalsan,1978,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-434,Asia/Choibalsan,1983,Mongol,540,60,4,1,-1,0,09:00,10,1,-1,0,08:00
-435,Asia/Choibalsan,1984,Mongol,540,60,4,1,-1,0,09:00,9,-1,0,0,08:00
-436,Asia/Choibalsan,1985,Mongol,540,60,3,-1,0,0,09:00,9,-1,0,0,08:00
-437,Asia/Choibalsan,2001,Mongol,540,60,4,-1,6,0,11:00,9,-1,6,0,10:00
-438,Asia/Choibalsan,2002,Mongol,540,60,3,-1,6,0,11:00,9,-1,6,0,10:00
-439,Asia/Choibalsan,2006,Mongol,540,0,3,-1,6,0,11:00,9,-1,6,0,10:00
-440,Asia/Choibalsan,2008,Mongol,480,0,3,-1,6,0,10:00,9,-1,6,0,09:00
-441,Asia/Kathmandu,1970,,330,0,0,0,0,0,00:00,0,0,0,0,00:00
-442,Asia/Kathmandu,1986,,345,0,0,0,0,0,00:00,0,0,0,0,00:00
-443,Asia/Muscat,1970,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-444,Asia/Karachi,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-445,Asia/Karachi,1971,Pakistan,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-446,Asia/Karachi,2002,Pakistan,300,60,4,2,0,0,05:01,10,2,0,0,04:01
-447,Asia/Karachi,2008,Pakistan,300,60,6,1,-1,0,05:00,11,1,-1,0,04:00
-448,Asia/Karachi,2009,Pakistan,300,0,4,15,-1,0,05:00,11,1,-1,0,04:00
-449,Asia/Gaza,1970,Zion,120,60,4,29,-1,0,04:00,9,22,-1,0,01:00
-450,Asia/Gaza,1974,Zion,120,60,7,7,-1,0,02:00,10,13,-1,0,01:00
-451,Asia/Gaza,1975,Zion,120,60,4,20,-1,0,02:00,8,31,-1,0,01:00
-452,Asia/Gaza,1985,Zion,120,60,4,14,-1,0,02:00,9,15,-1,0,01:00
-453,Asia/Gaza,1986,Zion,120,60,5,18,-1,0,02:00,9,7,-1,0,01:00
-454,Asia/Gaza,1987,Zion,120,60,4,15,-1,0,02:00,9,13,-1,0,01:00
-455,Asia/Gaza,1988,Zion,120,60,4,10,-1,0,02:00,9,4,-1,0,01:00
-456,Asia/Gaza,1989,Zion,120,60,4,30,-1,0,02:00,9,3,-1,0,01:00
-457,Asia/Gaza,1990,Zion,120,60,3,25,-1,0,02:00,8,26,-1,0,01:00
-458,Asia/Gaza,1991,Zion,120,60,3,24,-1,0,02:00,9,1,-1,0,01:00
-459,Asia/Gaza,1992,Zion,120,60,3,29,-1,0,02:00,9,6,-1,0,01:00
-460,Asia/Gaza,1993,Zion,120,60,4,2,-1,0,02:00,9,5,-1,0,01:00
-461,Asia/Gaza,1994,Zion,120,60,4,1,-1,0,02:00,8,28,-1,0,01:00
-462,Asia/Gaza,1995,Zion,120,60,3,31,-1,0,02:00,9,3,-1,0,01:00
-463,Asia/Gaza,1996,Jordan,120,60,4,1,5,0,02:00,9,15,5,0,02:00
-464,Asia/Gaza,1999,Palestine,120,60,4,15,5,0,02:00,10,15,5,0,01:00
-465,Asia/Gaza,2004,Palestine,120,60,4,15,5,0,02:00,10,1,-1,0,02:00
-466,Asia/Gaza,2005,Palestine,120,60,4,15,5,0,02:00,10,4,-1,0,03:00
-467,Asia/Gaza,2006,Palestine,120,60,4,1,-1,0,02:00,9,22,-1,0,01:00
-468,Asia/Gaza,2007,Palestine,120,60,4,1,-1,0,02:00,9,8,4,0,03:00
-469,Asia/Gaza,2008,Palestine,120,60,3,-1,5,0,02:00,9,1,-1,0,01:00
-470,Asia/Gaza,2009,Palestine,120,60,3,-1,5,0,02:00,9,1,5,0,02:00
-471,Asia/Gaza,2010,Palestine,120,60,3,26,-1,0,02:00,8,11,-1,0,01:00
-472,Asia/Gaza,2011,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-473,Asia/Gaza,2012,Palestine,120,60,3,-1,4,0,26:00,9,21,-1,0,02:00
-474,Asia/Gaza,2013,Palestine,120,60,3,-1,4,0,26:00,9,21,5,0,01:00
-475,Asia/Hebron,1970,Zion,120,60,4,29,-1,0,04:00,9,22,-1,0,01:00
-476,Asia/Hebron,1974,Zion,120,60,7,7,-1,0,02:00,10,13,-1,0,01:00
-477,Asia/Hebron,1975,Zion,120,60,4,20,-1,0,02:00,8,31,-1,0,01:00
-478,Asia/Hebron,1985,Zion,120,60,4,14,-1,0,02:00,9,15,-1,0,01:00
-479,Asia/Hebron,1986,Zion,120,60,5,18,-1,0,02:00,9,7,-1,0,01:00
-480,Asia/Hebron,1987,Zion,120,60,4,15,-1,0,02:00,9,13,-1,0,01:00
-481,Asia/Hebron,1988,Zion,120,60,4,10,-1,0,02:00,9,4,-1,0,01:00
-482,Asia/Hebron,1989,Zion,120,60,4,30,-1,0,02:00,9,3,-1,0,01:00
-483,Asia/Hebron,1990,Zion,120,60,3,25,-1,0,02:00,8,26,-1,0,01:00
-484,Asia/Hebron,1991,Zion,120,60,3,24,-1,0,02:00,9,1,-1,0,01:00
-485,Asia/Hebron,1992,Zion,120,60,3,29,-1,0,02:00,9,6,-1,0,01:00
-486,Asia/Hebron,1993,Zion,120,60,4,2,-1,0,02:00,9,5,-1,0,01:00
-487,Asia/Hebron,1994,Zion,120,60,4,1,-1,0,02:00,8,28,-1,0,01:00
-488,Asia/Hebron,1995,Zion,120,60,3,31,-1,0,02:00,9,3,-1,0,01:00
-489,Asia/Hebron,1996,Jordan,120,60,4,1,5,0,02:00,9,15,5,0,02:00
-490,Asia/Hebron,1999,Palestine,120,60,4,15,5,0,02:00,10,15,5,0,01:00
-491,Asia/Hebron,2004,Palestine,120,60,4,15,5,0,02:00,10,1,-1,0,02:00
-492,Asia/Hebron,2005,Palestine,120,60,4,15,5,0,02:00,10,4,-1,0,03:00
-493,Asia/Hebron,2006,Palestine,120,60,4,1,-1,0,02:00,9,22,-1,0,01:00
-494,Asia/Hebron,2007,Palestine,120,60,4,1,-1,0,02:00,9,8,4,0,03:00
-495,Asia/Hebron,2008,Palestine,120,60,3,-1,5,0,02:00,9,1,-1,0,01:00
-496,Asia/Hebron,2009,Palestine,120,60,3,-1,5,0,02:00,9,1,5,0,02:00
-497,Asia/Hebron,2010,Palestine,120,60,3,26,-1,0,02:00,8,11,-1,0,01:00
-498,Asia/Hebron,2011,Palestine,120,60,8,30,-1,0,02:00,9,30,-1,0,01:00
-499,Asia/Hebron,2012,Palestine,120,60,3,-1,4,0,26:00,9,21,-1,0,02:00
-500,Asia/Hebron,2013,Palestine,120,60,3,-1,4,0,26:00,9,21,5,0,01:00
-501,Asia/Manila,1970,Phil,480,60,4,12,-1,0,08:00,7,1,-1,0,07:00
-502,Asia/Manila,1978,Phil,480,60,3,22,-1,0,08:00,9,21,-1,0,07:00
-503,Asia/Manila,1979,Phil,480,0,3,22,-1,0,08:00,9,21,-1,0,07:00
-504,Asia/Qatar,1970,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-505,Asia/Qatar,1972,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-506,Asia/Riyadh,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-507,Asia/Singapore,1970,,450,0,0,0,0,0,00:00,0,0,0,0,00:00
-508,Asia/Singapore,1982,,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-509,Asia/Colombo,1970,,330,0,0,0,0,0,00:00,0,0,0,0,00:00
-510,Asia/Colombo,1996,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-511,Asia/Colombo,2006,,330,0,0,0,0,0,00:00,0,0,0,0,00:00
-512,Asia/Damascus,1970,Syria,120,60,5,1,-1,0,04:00,10,1,-1,0,03:00
-513,Asia/Damascus,1977,Syria,120,60,5,1,-1,0,04:00,9,1,-1,0,03:00
-514,Asia/Damascus,1983,Syria,120,60,4,9,-1,0,04:00,10,1,-1,0,03:00
-515,Asia/Damascus,1986,Syria,120,60,2,16,-1,0,04:00,10,9,-1,0,03:00
-516,Asia/Damascus,1987,Syria,120,60,3,1,-1,0,04:00,10,31,-1,0,03:00
-517,Asia/Damascus,1988,Syria,120,60,3,15,-1,0,04:00,10,31,-1,0,03:00
-518,Asia/Damascus,1989,Syria,120,60,3,31,-1,0,04:00,10,1,-1,0,03:00
-519,Asia/Damascus,1990,Syria,120,60,4,1,-1,0,04:00,9,30,-1,0,03:00
-520,Asia/Damascus,1991,Syria,120,60,4,1,-1,0,02:00,10,1,-1,0,01:00
-521,Asia/Damascus,1992,Syria,120,60,4,8,-1,0,02:00,10,1,-1,0,01:00
-522,Asia/Damascus,1993,Syria,120,60,3,26,-1,0,02:00,9,25,-1,0,01:00
-523,Asia/Damascus,1994,Syria,120,60,4,1,-1,0,02:00,10,1,-1,0,01:00
-524,Asia/Damascus,1997,Syria,120,60,3,-1,1,0,02:00,10,1,-1,0,01:00
-525,Asia/Damascus,1999,Syria,120,60,4,1,-1,0,02:00,10,1,-1,0,01:00
-526,Asia/Damascus,2006,Syria,120,60,4,1,-1,0,02:00,9,22,-1,0,01:00
-527,Asia/Damascus,2007,Syria,120,60,3,-1,5,0,02:00,11,1,5,0,01:00
-528,Asia/Damascus,2008,Syria,120,60,4,1,5,0,02:00,11,1,-1,0,01:00
-529,Asia/Damascus,2009,Syria,120,60,3,-1,5,0,02:00,10,-1,5,0,01:00
-530,Asia/Damascus,2010,Syria,120,60,4,1,5,0,02:00,10,-1,5,0,01:00
-531,Asia/Damascus,2012,Syria,120,60,3,-1,5,0,02:00,10,-1,5,0,01:00
-532,Asia/Dushanbe,1970,RussiaAsia,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-533,Asia/Dushanbe,1981,RussiaAsia,360,60,4,1,-1,0,06:00,10,1,-1,0,05:00
-534,Asia/Dushanbe,1984,RussiaAsia,360,60,4,1,-1,0,06:00,9,-1,0,0,08:00
-535,Asia/Dushanbe,1985,RussiaAsia,360,60,3,-1,0,0,08:00,9,-1,0,0,08:00
-536,Asia/Dushanbe,1991,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-537,Asia/Bangkok,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-538,Asia/Ashgabat,1970,RussiaAsia,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-539,Asia/Ashgabat,1981,RussiaAsia,300,60,4,1,-1,0,05:00,10,1,-1,0,04:00
-540,Asia/Ashgabat,1984,RussiaAsia,300,60,4,1,-1,0,05:00,9,-1,0,0,07:00
-541,Asia/Ashgabat,1985,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-542,Asia/Ashgabat,1991,RussiaAsia,240,60,3,-1,0,0,06:00,9,-1,0,0,06:00
-543,Asia/Ashgabat,1992,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-544,Asia/Dubai,1970,,240,0,0,0,0,0,00:00,0,0,0,0,00:00
-545,Asia/Samarkand,1970,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-546,Asia/Samarkand,1981,,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-547,Asia/Samarkand,1982,RussiaAsia,300,60,4,1,-1,0,05:00,10,1,-1,0,04:00
-548,Asia/Samarkand,1984,RussiaAsia,300,60,4,1,-1,0,05:00,9,-1,0,0,07:00
-549,Asia/Samarkand,1985,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-550,Asia/Samarkand,1992,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-551,Asia/Tashkent,1970,RussiaAsia,360,0,0,0,0,0,00:00,0,0,0,0,00:00
-552,Asia/Tashkent,1981,RussiaAsia,360,60,4,1,-1,0,06:00,10,1,-1,0,05:00
-553,Asia/Tashkent,1984,RussiaAsia,360,60,4,1,-1,0,06:00,9,-1,0,0,08:00
-554,Asia/Tashkent,1985,RussiaAsia,360,60,3,-1,0,0,08:00,9,-1,0,0,08:00
-555,Asia/Tashkent,1991,RussiaAsia,300,60,3,-1,0,0,07:00,9,-1,0,0,07:00
-556,Asia/Tashkent,1992,,300,0,0,0,0,0,00:00,0,0,0,0,00:00
-557,Asia/Ho_Chi_Minh,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-558,Asia/Aden,1970,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-559,Australia/Darwin,1970,Aus,570,0,12,31,-1,0,21:30,3,-1,0,0,10:30
-560,Australia/Perth,1970,AW,480,0,0,0,0,0,00:00,0,0,0,0,00:00
-561,Australia/Perth,1974,AW,480,60,10,-1,0,0,10:00,1,1,-1,0,19:00
-562,Australia/Perth,1975,AW,480,0,12,31,-1,0,20:00,3,1,0,0,10:00
-563,Australia/Perth,1983,AW,480,60,10,-1,0,0,10:00,1,1,-1,0,19:00
-564,Australia/Perth,1984,AW,480,0,12,31,-1,0,20:00,3,1,0,0,10:00
-565,Australia/Perth,1991,AW,480,60,11,17,-1,0,10:00,1,1,-1,0,19:00
-566,Australia/Perth,1992,AW,480,0,12,31,-1,0,20:00,3,1,0,0,10:00
-567,Australia/Perth,2006,AW,480,60,12,3,-1,0,10:00,1,1,-1,0,19:00
-568,Australia/Perth,2007,AW,480,60,10,-1,0,0,10:00,3,-1,0,0,10:00
-569,Australia/Perth,2009,AW,480,0,12,31,-1,0,20:00,3,-1,0,0,10:00
-570,Australia/Eucla,1970,AW,525,0,0,0,0,0,00:00,0,0,0,0,00:00
-571,Australia/Eucla,1974,AW,525,60,10,-1,0,0,10:45,1,1,-1,0,19:45
-572,Australia/Eucla,1975,AW,525,0,12,31,-1,0,20:45,3,1,0,0,10:45
-573,Australia/Eucla,1983,AW,525,60,10,-1,0,0,10:45,1,1,-1,0,19:45
-574,Australia/Eucla,1984,AW,525,0,12,31,-1,0,20:45,3,1,0,0,10:45
-575,Australia/Eucla,1991,AW,525,60,11,17,-1,0,10:45,1,1,-1,0,19:45
-576,Australia/Eucla,1992,AW,525,0,12,31,-1,0,20:45,3,1,0,0,10:45
-577,Australia/Eucla,2006,AW,525,60,12,3,-1,0,10:45,1,1,-1,0,19:45
-578,Australia/Eucla,2007,AW,525,60,10,-1,0,0,10:45,3,-1,0,0,10:45
-579,Australia/Eucla,2009,AW,525,0,12,31,-1,0,20:45,3,-1,0,0,10:45
-580,Australia/Brisbane,1970,Aus,600,0,12,31,-1,0,22:00,3,-1,0,0,11:00
-581,Australia/Brisbane,1971,AQ,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-582,Australia/Brisbane,1972,AQ,600,0,12,31,-1,0,22:00,2,-1,0,0,12:00
-583,Australia/Brisbane,1989,AQ,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-584,Australia/Brisbane,1990,AQ,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-585,Australia/Brisbane,1992,AQ,600,0,12,31,-1,0,22:00,3,1,0,0,12:00
-586,Australia/Lindeman,1970,Aus,600,0,12,31,-1,0,22:00,3,-1,0,0,11:00
-587,Australia/Lindeman,1971,AQ,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-588,Australia/Lindeman,1972,AQ,600,0,12,31,-1,0,22:00,2,-1,0,0,12:00
-589,Australia/Lindeman,1989,AQ,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-590,Australia/Lindeman,1990,AQ,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-591,Australia/Lindeman,1992,Holiday,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-592,Australia/Lindeman,1993,Holiday,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-593,Australia/Lindeman,1994,Holiday,600,0,12,31,-1,0,22:00,3,1,0,0,12:00
-594,Australia/Adelaide,1970,Aus,570,0,12,31,-1,0,21:30,3,-1,0,0,10:30
-595,Australia/Adelaide,1971,AS,570,60,10,-1,0,0,11:30,1,1,-1,0,20:30
-596,Australia/Adelaide,1972,AS,570,60,10,-1,0,0,11:30,2,27,-1,0,11:30
-597,Australia/Adelaide,1973,AS,570,60,10,-1,0,0,11:30,3,1,0,0,11:30
-598,Australia/Adelaide,1986,AS,570,60,10,19,-1,0,11:30,3,15,0,0,11:30
-599,Australia/Adelaide,1987,AS,570,60,10,-1,0,0,11:30,3,15,0,0,11:30
-600,Australia/Adelaide,1991,AS,570,60,10,-1,0,0,11:30,3,3,-1,0,11:30
-601,Australia/Adelaide,1992,AS,570,60,10,-1,0,0,11:30,3,22,-1,0,11:30
-602,Australia/Adelaide,1993,AS,570,60,10,-1,0,0,11:30,3,7,-1,0,11:30
-603,Australia/Adelaide,1994,AS,570,60,10,-1,0,0,11:30,3,20,-1,0,11:30
-604,Australia/Adelaide,1995,AS,570,60,10,-1,0,0,11:30,3,-1,0,0,11:30
-605,Australia/Adelaide,2006,AS,570,60,10,-1,0,0,11:30,4,2,-1,0,11:30
-606,Australia/Adelaide,2007,AS,570,60,10,-1,0,0,11:30,3,-1,0,0,11:30
-607,Australia/Adelaide,2008,AS,570,60,10,1,0,0,11:30,4,1,0,0,11:30
-608,Australia/Hobart,1970,AT,600,60,10,-1,0,0,12:00,3,8,0,0,12:00
-609,Australia/Hobart,1972,AT,600,60,10,-1,0,0,12:00,2,-1,0,0,12:00
-610,Australia/Hobart,1973,AT,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-611,Australia/Hobart,1982,AT,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-612,Australia/Hobart,1984,AT,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-613,Australia/Hobart,1986,AT,600,60,10,15,0,0,12:00,3,1,0,0,12:00
-614,Australia/Hobart,1987,AT,600,60,10,22,0,0,12:00,3,15,0,0,12:00
-615,Australia/Hobart,1988,AT,600,60,10,-1,0,0,12:00,3,15,0,0,12:00
-616,Australia/Hobart,1991,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-617,Australia/Hobart,2000,AT,600,60,8,-1,0,0,12:00,3,-1,0,0,12:00
-618,Australia/Hobart,2001,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-619,Australia/Hobart,2006,AT,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-620,Australia/Hobart,2007,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-621,Australia/Hobart,2008,AT,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-622,Australia/Currie,1970,Aus,600,0,12,31,-1,0,22:00,3,-1,0,0,11:00
-623,Australia/Currie,1971,AT,600,60,10,-1,0,0,12:00,3,8,0,0,12:00
-624,Australia/Currie,1972,AT,600,60,10,-1,0,0,12:00,2,-1,0,0,12:00
-625,Australia/Currie,1973,AT,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-626,Australia/Currie,1982,AT,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-627,Australia/Currie,1984,AT,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-628,Australia/Currie,1986,AT,600,60,10,15,0,0,12:00,3,1,0,0,12:00
-629,Australia/Currie,1987,AT,600,60,10,22,0,0,12:00,3,15,0,0,12:00
-630,Australia/Currie,1988,AT,600,60,10,-1,0,0,12:00,3,15,0,0,12:00
-631,Australia/Currie,1991,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-632,Australia/Currie,2000,AT,600,60,8,-1,0,0,12:00,3,-1,0,0,12:00
-633,Australia/Currie,2001,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-634,Australia/Currie,2006,AT,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-635,Australia/Currie,2007,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-636,Australia/Currie,2008,AT,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-637,Australia/Melbourne,1970,Aus,600,0,12,31,-1,0,22:00,3,-1,0,0,11:00
-638,Australia/Melbourne,1971,AV,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-639,Australia/Melbourne,1972,AV,600,60,10,-1,0,0,12:00,2,-1,0,0,12:00
-640,Australia/Melbourne,1973,AV,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-641,Australia/Melbourne,1986,AV,600,60,10,15,0,0,12:00,3,15,0,0,12:00
-642,Australia/Melbourne,1988,AV,600,60,10,-1,0,0,12:00,3,15,0,0,12:00
-643,Australia/Melbourne,1991,AV,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-644,Australia/Melbourne,1995,AV,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-645,Australia/Melbourne,2000,AV,600,60,8,-1,0,0,12:00,3,-1,0,0,12:00
-646,Australia/Melbourne,2001,AV,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-647,Australia/Melbourne,2006,AV,600,60,10,-1,0,0,12:00,4,1,0,0,12:00
-648,Australia/Melbourne,2007,AV,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-649,Australia/Melbourne,2008,AV,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-650,Australia/Sydney,1970,Aus,600,0,12,31,-1,0,22:00,3,-1,0,0,11:00
-651,Australia/Sydney,1971,AN,600,60,10,-1,0,0,12:00,1,1,-1,0,21:00
-652,Australia/Sydney,1972,AN,600,60,10,-1,0,0,12:00,2,27,-1,0,12:00
-653,Australia/Sydney,1973,AN,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-654,Australia/Sydney,1982,AN,600,60,10,-1,0,0,12:00,4,1,0,0,12:00
-655,Australia/Sydney,1983,AN,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-656,Australia/Sydney,1986,AN,600,60,10,19,-1,0,12:00,3,15,0,0,12:00
-657,Australia/Sydney,1987,AN,600,60,10,-1,0,0,12:00,3,15,0,0,12:00
-658,Australia/Sydney,1990,AN,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-659,Australia/Sydney,1996,AN,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-660,Australia/Sydney,2000,AN,600,60,8,-1,0,0,12:00,3,-1,0,0,12:00
-661,Australia/Sydney,2001,AN,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-662,Australia/Sydney,2006,AN,600,60,10,-1,0,0,12:00,4,1,0,0,12:00
-663,Australia/Sydney,2007,AN,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-664,Australia/Sydney,2008,AN,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-665,Australia/Broken_Hill,1970,Aus,570,0,12,31,-1,0,21:30,3,-1,0,0,10:30
-666,Australia/Broken_Hill,1971,AN,570,60,10,-1,0,0,11:30,1,1,-1,0,20:30
-667,Australia/Broken_Hill,1972,AN,570,60,10,-1,0,0,11:30,2,27,-1,0,11:30
-668,Australia/Broken_Hill,1973,AN,570,60,10,-1,0,0,11:30,3,1,0,0,11:30
-669,Australia/Broken_Hill,1982,AN,570,60,10,-1,0,0,11:30,4,1,0,0,11:30
-670,Australia/Broken_Hill,1983,AN,570,60,10,-1,0,0,11:30,3,1,0,0,11:30
-671,Australia/Broken_Hill,1986,AN,570,60,10,19,-1,0,11:30,3,15,0,0,11:30
-672,Australia/Broken_Hill,1987,AN,570,60,10,-1,0,0,11:30,3,15,0,0,11:30
-673,Australia/Broken_Hill,1990,AN,570,60,10,-1,0,0,11:30,3,1,0,0,11:30
-674,Australia/Broken_Hill,1996,AN,570,60,10,-1,0,0,11:30,3,-1,0,0,11:30
-675,Australia/Broken_Hill,2000,AS,570,60,10,-1,0,0,11:30,3,-1,0,0,11:30
-676,Australia/Broken_Hill,2006,AS,570,60,10,-1,0,0,11:30,4,2,-1,0,11:30
-677,Australia/Broken_Hill,2007,AS,570,60,10,-1,0,0,11:30,3,-1,0,0,11:30
-678,Australia/Broken_Hill,2008,AS,570,60,10,1,0,0,11:30,4,1,0,0,11:30
-679,Australia/Lord_Howe,1970,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-680,Australia/Lord_Howe,1981,LH,630,60,10,-1,0,0,12:30,1,1,-1,0,21:30
-681,Australia/Lord_Howe,1982,LH,630,60,10,-1,0,0,12:30,3,1,0,0,11:30
-682,Australia/Lord_Howe,1985,LH,630,30,10,-1,0,0,12:30,3,1,0,0,11:30
-683,Australia/Lord_Howe,1986,LH,630,30,10,19,-1,0,12:30,3,15,0,0,11:30
-684,Australia/Lord_Howe,1987,LH,630,30,10,-1,0,0,12:30,3,15,0,0,11:30
-685,Australia/Lord_Howe,1990,LH,630,30,10,-1,0,0,12:30,3,1,0,0,11:30
-686,Australia/Lord_Howe,1996,LH,630,30,10,-1,0,0,12:30,3,-1,0,0,11:30
-687,Australia/Lord_Howe,2000,LH,630,30,8,-1,0,0,12:30,3,-1,0,0,11:30
-688,Australia/Lord_Howe,2001,LH,630,30,10,-1,0,0,12:30,3,-1,0,0,11:30
-689,Australia/Lord_Howe,2006,LH,630,30,10,-1,0,0,12:30,4,1,0,0,11:30
-690,Australia/Lord_Howe,2007,LH,630,30,10,-1,0,0,12:30,3,-1,0,0,11:30
-691,Australia/Lord_Howe,2008,LH,630,30,10,1,0,0,12:30,4,1,0,0,11:30
-692,Antarctica/Macquarie,1970,AT,600,60,10,-1,0,0,12:00,3,8,0,0,12:00
-693,Antarctica/Macquarie,1972,AT,600,60,10,-1,0,0,12:00,2,-1,0,0,12:00
-694,Antarctica/Macquarie,1973,AT,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-695,Antarctica/Macquarie,1982,AT,600,60,10,-1,0,0,12:00,3,-1,0,0,12:00
-696,Antarctica/Macquarie,1984,AT,600,60,10,-1,0,0,12:00,3,1,0,0,12:00
-697,Antarctica/Macquarie,1986,AT,600,60,10,15,0,0,12:00,3,1,0,0,12:00
-698,Antarctica/Macquarie,1987,AT,600,60,10,22,0,0,12:00,3,15,0,0,12:00
-699,Antarctica/Macquarie,1988,AT,600,60,10,-1,0,0,12:00,3,15,0,0,12:00
-700,Antarctica/Macquarie,1991,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-701,Antarctica/Macquarie,2000,AT,600,60,8,-1,0,0,12:00,3,-1,0,0,12:00
-702,Antarctica/Macquarie,2001,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-703,Antarctica/Macquarie,2006,AT,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-704,Antarctica/Macquarie,2007,AT,600,60,10,1,0,0,12:00,3,-1,0,0,12:00
-705,Antarctica/Macquarie,2008,AT,600,60,10,1,0,0,12:00,4,1,0,0,12:00
-706,Antarctica/Macquarie,2010,,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-707,Indian/Christmas,1970,,420,0,0,0,0,0,00:00,0,0,0,0,00:00
-708,Indian/Cocos,1970,,390,0,0,0,0,0,00:00,0,0,0,0,00:00
-709,Pacific/Fiji,1970,Fiji,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-710,Pacific/Fiji,1998,Fiji,720,60,11,1,0,0,14:00,1,1,-1,0,23:00
-711,Pacific/Fiji,1999,Fiji,720,60,11,1,0,0,14:00,2,-1,0,0,14:00
-712,Pacific/Fiji,2000,Fiji,720,0,12,31,-1,0,24:00,2,-1,0,0,14:00
-713,Pacific/Fiji,2009,Fiji,720,60,11,29,-1,0,14:00,1,1,-1,0,23:00
-714,Pacific/Fiji,2010,Fiji,720,60,10,21,0,0,14:00,3,-1,0,0,14:00
-715,Pacific/Fiji,2011,Fiji,720,60,10,21,0,0,14:00,3,1,0,0,14:00
-716,Pacific/Fiji,2012,Fiji,720,60,10,21,0,0,14:00,1,18,0,0,14:00
-717,Pacific/Fiji,2014,Fiji,720,60,10,21,0,0,14:00,1,18,0,0,13:00
-718,Pacific/Gambier,1970,,-540,0,0,0,0,0,00:00,0,0,0,0,00:00
-719,Pacific/Marquesas,1970,,-570,0,0,0,0,0,00:00,0,0,0,0,00:00
-720,Pacific/Tahiti,1970,,-600,0,0,0,0,0,00:00,0,0,0,0,00:00
-721,Pacific/Guam,1970,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-722,Pacific/Guam,2000,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-723,Pacific/Tarawa,1970,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-724,Pacific/Enderbury,1970,,-720,0,0,0,0,0,00:00,0,0,0,0,00:00
-725,Pacific/Enderbury,1979,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-726,Pacific/Enderbury,1995,,780,0,0,0,0,0,00:00,0,0,0,0,00:00
-727,Pacific/Kiritimati,1970,,-640,0,0,0,0,0,00:00,0,0,0,0,00:00
-728,Pacific/Kiritimati,1979,,-600,0,0,0,0,0,00:00,0,0,0,0,00:00
-729,Pacific/Kiritimati,1995,,840,0,0,0,0,0,00:00,0,0,0,0,00:00
-730,Pacific/Saipan,1970,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-731,Pacific/Saipan,2000,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-732,Pacific/Majuro,1970,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-733,Pacific/Kwajalein,1970,,-720,0,0,0,0,0,00:00,0,0,0,0,00:00
-734,Pacific/Kwajalein,1993,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-735,Pacific/Chuuk,1970,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-736,Pacific/Pohnpei,1970,,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-737,Pacific/Kosrae,1970,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-738,Pacific/Kosrae,1999,,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-739,Pacific/Nauru,1970,,690,0,0,0,0,0,00:00,0,0,0,0,00:00
-740,Pacific/Nauru,1979,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-741,Pacific/Noumea,1970,NC,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-742,Pacific/Noumea,1977,NC,660,60,12,1,0,0,11:00,1,1,-1,0,22:00
-743,Pacific/Noumea,1978,NC,660,60,12,1,0,0,11:00,2,27,-1,0,10:00
-744,Pacific/Noumea,1979,NC,660,0,12,31,-1,0,23:00,2,27,-1,0,10:00
-745,Pacific/Noumea,1996,NC,660,60,12,1,-1,0,13:00,1,1,-1,0,22:00
-746,Pacific/Noumea,1997,NC,660,0,12,31,-1,0,23:00,3,2,-1,0,13:00
-747,Pacific/Auckland,1970,NZ,720,0,12,31,-1,0,24:00,1,1,-1,0,11:00
-748,Pacific/Auckland,1974,NZ,720,60,11,1,0,0,14:00,1,1,-1,0,23:00
-749,Pacific/Auckland,1975,NZ,720,60,10,-1,0,0,14:00,2,-1,0,0,14:00
-750,Pacific/Auckland,1976,NZ,720,60,10,-1,0,0,14:00,3,1,0,0,14:00
-751,Pacific/Auckland,1989,NZ,720,60,10,8,0,0,14:00,3,1,0,0,14:00
-752,Pacific/Auckland,1990,NZ,720,60,10,1,0,0,14:00,3,15,0,0,14:00
-753,Pacific/Auckland,2007,NZ,720,60,9,-1,0,0,14:00,3,15,0,0,14:00
-754,Pacific/Auckland,2008,NZ,720,60,9,-1,0,0,14:00,4,1,0,0,14:00
-755,Pacific/Chatham,1970,Chatham,765,0,0,0,0,0,00:00,0,0,0,0,00:00
-756,Pacific/Chatham,1974,Chatham,765,60,11,1,0,0,15:30,1,1,-1,0,23:45
-757,Pacific/Chatham,1975,Chatham,765,60,10,-1,0,0,15:30,2,-1,0,0,15:30
-758,Pacific/Chatham,1976,Chatham,765,60,10,-1,0,0,15:30,3,1,0,0,15:30
-759,Pacific/Chatham,1989,Chatham,765,60,10,8,0,0,15:30,3,1,0,0,15:30
-760,Pacific/Chatham,1990,Chatham,765,60,10,1,0,0,15:30,3,15,0,0,15:30
-761,Pacific/Chatham,2007,Chatham,765,60,9,-1,0,0,15:30,3,15,0,0,15:30
-762,Pacific/Chatham,2008,Chatham,765,60,9,-1,0,0,15:30,4,1,0,0,15:30
-763,Pacific/Rarotonga,1970,,-630,0,0,0,0,0,00:00,0,0,0,0,00:00
-764,Pacific/Rarotonga,1978,Cook,-600,30,11,12,-1,0,-10:00,1,1,-1,0,01:00
-765,Pacific/Rarotonga,1979,Cook,-600,30,10,-1,0,0,-10:00,3,1,0,0,-11:00
-766,Pacific/Rarotonga,1991,Cook,-600,0,12,31,-1,0,02:00,3,1,0,0,-11:00
-767,Pacific/Niue,1970,,-690,0,0,0,0,0,00:00,0,0,0,0,00:00
-768,Pacific/Niue,1978,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-769,Pacific/Norfolk,1970,,690,0,0,0,0,0,00:00,0,0,0,0,00:00
-770,Pacific/Palau,1970,,540,0,0,0,0,0,00:00,0,0,0,0,00:00
-771,Pacific/Port_Moresby,1970,,600,0,0,0,0,0,00:00,0,0,0,0,00:00
-772,Pacific/Pitcairn,1970,,-510,0,0,0,0,0,00:00,0,0,0,0,00:00
-773,Pacific/Pitcairn,1998,,-480,0,0,0,0,0,00:00,0,0,0,0,00:00
-774,Pacific/Pago_Pago,1970,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-775,Pacific/Pago_Pago,1983,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-776,Pacific/Apia,1970,WS,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-777,Pacific/Apia,2010,WS,-660,1,9,-1,0,0,-11:00,1,1,-1,0,00:00
-778,Pacific/Apia,2011,WS,780,1,9,-1,6,0,16:00,4,1,6,0,16:00
-779,Pacific/Apia,2012,WS,780,1,9,-1,0,0,16:00,4,1,0,0,16:00
-780,Pacific/Guadalcanal,1970,,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-781,Pacific/Fakaofo,1970,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-782,Pacific/Fakaofo,2011,,780,0,0,0,0,0,00:00,0,0,0,0,00:00
-783,Pacific/Tongatapu,1970,,780,0,0,0,0,0,00:00,0,0,0,0,00:00
-784,Pacific/Tongatapu,1999,Tonga,780,60,10,7,-1,0,15:00,1,1,-1,0,24:00
-785,Pacific/Tongatapu,2000,Tonga,780,60,11,1,0,0,15:00,3,19,-1,0,15:00
-786,Pacific/Tongatapu,2001,Tonga,780,60,11,1,0,0,15:00,1,-1,0,0,14:00
-787,Pacific/Tongatapu,2002,Tonga,780,0,12,31,-1,0,25:00,1,-1,0,0,14:00
-788,Pacific/Funafuti,1970,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-789,Pacific/Midway,1970,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-790,Pacific/Midway,1983,,-660,0,0,0,0,0,00:00,0,0,0,0,00:00
-791,Pacific/Wake,1970,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-792,Pacific/Efate,1970,Vanuatu,660,0,0,0,0,0,00:00,0,0,0,0,00:00
-793,Pacific/Efate,1983,Vanuatu,660,60,9,25,-1,0,11:00,1,1,-1,0,22:00
-794,Pacific/Efate,1984,Vanuatu,660,60,10,23,-1,0,11:00,3,23,0,0,10:00
-795,Pacific/Efate,1985,Vanuatu,660,60,9,23,0,0,11:00,3,23,0,0,10:00
-796,Pacific/Efate,1992,Vanuatu,660,60,10,23,0,0,11:00,1,23,0,0,10:00
-797,Pacific/Efate,1993,Vanuatu,660,0,12,31,-1,0,23:00,1,23,0,0,10:00
-798,Pacific/Wallis,1970,,720,0,0,0,0,0,00:00,0,0,0,0,00:00
-799,Europe/London,1970,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-800,Europe/London,1971,GB-Eire,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-801,Europe/London,1972,GB-Eire,0,60,3,16,0,0,02:00,10,23,0,0,02:00
-802,Europe/London,1981,GB-Eire,0,60,3,-1,0,0,1:00,10,23,0,0,1:00
-803,Europe/London,1990,GB-Eire,0,60,3,-1,0,0,1:00,10,22,0,0,1:00
-804,Europe/London,1995,GB-Eire,0,0,3,-1,0,0,1:00,10,22,0,0,1:00
-805,Europe/London,1996,EU,0,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-806,Europe/Dublin,1970,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-807,Europe/Dublin,1971,GB-Eire,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-808,Europe/Dublin,1972,GB-Eire,0,60,3,16,0,0,02:00,10,23,0,0,02:00
-809,Europe/Dublin,1981,GB-Eire,0,60,3,-1,0,0,1:00,10,23,0,0,1:00
-810,Europe/Dublin,1990,GB-Eire,0,60,3,-1,0,0,1:00,10,22,0,0,1:00
-811,Europe/Dublin,1995,GB-Eire,0,0,3,-1,0,0,1:00,10,22,0,0,1:00
-812,Europe/Dublin,1996,EU,0,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-813,WET,1970,EU,0,0,0,0,0,0,00:00,0,0,0,0,00:00
-814,WET,1977,EU,0,60,4,1,0,0,1:00,9,-1,0,0,1:00
-815,WET,1978,EU,0,60,4,1,0,0,1:00,10,1,-1,0,1:00
-816,WET,1979,EU,0,60,4,1,0,0,1:00,9,-1,0,0,1:00
-817,WET,1981,EU,0,60,3,-1,0,0,1:00,9,-1,0,0,1:00
-818,WET,1996,EU,0,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-819,CET,1970,C-Eur,60,60,4,1,1,0,03:00,9,16,-1,0,03:00
-820,CET,1977,C-Eur,60,60,4,1,0,0,03:00,9,-1,0,0,03:00
-821,CET,1978,C-Eur,60,60,4,1,0,0,03:00,10,1,-1,0,03:00
-822,CET,1979,C-Eur,60,60,4,1,0,0,03:00,9,-1,0,0,03:00
-823,CET,1981,C-Eur,60,60,3,-1,0,0,03:00,9,-1,0,0,03:00
-824,CET,1996,C-Eur,60,60,3,-1,0,0,03:00,10,-1,0,0,03:00
-825,MET,1970,C-Eur,60,60,4,1,1,0,03:00,9,16,-1,0,03:00
-826,MET,1977,C-Eur,60,60,4,1,0,0,03:00,9,-1,0,0,03:00
-827,MET,1978,C-Eur,60,60,4,1,0,0,03:00,10,1,-1,0,03:00
-828,MET,1979,C-Eur,60,60,4,1,0,0,03:00,9,-1,0,0,03:00
-829,MET,1981,C-Eur,60,60,3,-1,0,0,03:00,9,-1,0,0,03:00
-830,MET,1996,C-Eur,60,60,3,-1,0,0,03:00,10,-1,0,0,03:00
-831,EET,1970,EU,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-832,EET,1977,EU,120,60,4,1,0,0,1:00,9,-1,0,0,1:00
-833,EET,1978,EU,120,60,4,1,0,0,1:00,10,1,-1,0,1:00
-834,EET,1979,EU,120,60,4,1,0,0,1:00,9,-1,0,0,1:00
-835,EET,1981,EU,120,60,3,-1,0,0,1:00,9,-1,0,0,1:00
-836,EET,1996,EU,120,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-837,Europe/Tirane,1970,Albania,60,60,3,29,-1,0,03:00,4,10,-1,0,03:00
-838,Europe/Tirane,1974,Albania,60,60,5,4,-1,0,01:00,10,2,-1,0,00:00
-839,Europe/Tirane,1975,Albania,60,60,5,1,-1,0,01:00,10,2,-1,0,00:00
-840,Europe/Tirane,1976,Albania,60,60,5,2,-1,0,01:00,10,3,-1,0,00:00
-841,Europe/Tirane,1977,Albania,60,60,5,8,-1,0,01:00,10,2,-1,0,00:00
-842,Europe/Tirane,1978,Albania,60,60,5,6,-1,0,01:00,10,1,-1,0,00:00
-843,Europe/Tirane,1979,Albania,60,60,5,5,-1,0,01:00,9,30,-1,0,00:00
-844,Europe/Tirane,1980,Albania,60,60,5,3,-1,0,01:00,10,4,-1,0,00:00
-845,Europe/Tirane,1981,Albania,60,60,4,26,-1,0,01:00,9,27,-1,0,00:00
-846,Europe/Tirane,1982,Albania,60,60,5,2,-1,0,01:00,10,3,-1,0,00:00
-847,Europe/Tirane,1983,Albania,60,60,4,18,-1,0,01:00,10,1,-1,0,00:00
-848,Europe/Tirane,1984,EU,60,60,3,-1,0,0,1:00,9,-1,0,0,1:00
-849,Europe/Tirane,1996,EU,60,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-850,Europe/Andorra,1970,,60,0,0,0,0,0,00:00,0,0,0,0,00:00
-851,Europe/Andorra,1985,EU,60,60,3,-1,0,0,1:00,9,-1,0,0,1:00
-852,Europe/Andorra,1996,EU,60,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-853,Europe/Vienna,1970,Austria,60,60,4,18,-1,0,03:00,10,1,0,0,03:00
-854,Europe/Vienna,1980,Austria,60,60,4,6,-1,0,01:00,9,28,-1,0,00:00
-855,Europe/Vienna,1981,EU,60,60,3,-1,0,0,1:00,9,-1,0,0,1:00
-856,Europe/Vienna,1996,EU,60,60,3,-1,0,0,1:00,10,-1,0,0,1:00
-857,Europe/Minsk,1970,Russia,180,60,9,1,-1,0,03:00,10,1,-1,0,02:00
-858,Europe/Minsk,1981,Russia,180,60,4,1,-1,0,03:00,10,1,-1,0,02:00
-859,Europe/Minsk,1984,Russia,180,60,4,1,-1,0,03:00,9,-1,0,0,05:00
-860,Europe/Minsk,1985,Russia,180,60,3,-1,0,0,05:00,9,-1,0,0,05:00
-861,Europe/Minsk,1990,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-862,Europe/Minsk,1991,,120,0,0,0,0,0,00:00,0,0,0,0,00:00
-863,Europe/Minsk,1992,Russia,120,60,3,-1,6,0,25:00,9,-1,6,0,24:00
-864,Europe/Minsk,1993,Russia,120,60,3,-1,0,0,04:00,9,-1,0,0,04:00
-865,Europe/Minsk,1996,Russia,120,60,3,-1,0,0,04:00,10,-1,0,0,04:00
-866,Europe/Minsk,2010,Russia,120,0,3,-1,0,0,04:00,10,-1,0,0,04:00
-867,Europe/Minsk,2011,,180,0,0,0,0,0,00:00,0,0,0,0,00:00
-868,Europe/Brussels,1970,Belgium,60,0,5,19,-1,0,03:00,10,7,-1,0,03:00
-869,Europe/Brussels,1977,EU,60,60,4,1,0,0,1:00,9,-1,0,0,1:00
-870,Europe/Brussels,1978,EU,60,60,4,1,0,0,1:00,10,1,-1,0,1:00
-871,Europe/Brussels,1979,EU,60,60,4,1,0,0,1:00,9,-1,0,0,1:00
-872,Europe/Brussels,1981,EU,60,60,3,-1,0,0,1:00,9,-1,0,0,1:00
-873,Europe/Brussels,1996,E