Merge branch 'MDL-68200-master-take2' of git://github.com/rezaies/moodle
authorSara Arjona <sara@moodle.com>
Thu, 14 May 2020 11:10:01 +0000 (13:10 +0200)
committerSara Arjona <sara@moodle.com>
Thu, 14 May 2020 11:10:01 +0000 (13:10 +0200)
16 files changed:
lib/amd/build/user_date.min.js
lib/amd/build/user_date.min.js.map
lib/amd/src/user_date.js
lib/external/externallib.php
lib/external/tests/external_test.php
lib/templates/time_element.mustache [new file with mode: 0644]
lib/upgrade.txt
mod/forum/classes/local/exporters/post.php
mod/forum/templates/discussion_list.mustache
mod/forum/templates/forum_discussion_nested_v2_first_post.mustache
mod/forum/templates/forum_discussion_post.mustache
mod/forum/templates/forum_discussion_threaded_post.mustache
mod/forum/tests/generator/lib.php
mod/forum/view.php
theme/boost/scss/moodle/undo.scss
theme/boost/style/moodle.css

index 6f512ef..c221d1a 100644 (file)
Binary files a/lib/amd/build/user_date.min.js and b/lib/amd/build/user_date.min.js differ
index 898b39a..87973d0 100644 (file)
Binary files a/lib/amd/build/user_date.min.js.map and b/lib/amd/build/user_date.min.js.map differ
index 20ea44c..95efe3d 100644 (file)
@@ -107,9 +107,14 @@ define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
      */
     var loadDatesFromServer = function(dates) {
         var args = dates.map(function(data) {
+            var fixDay = data.hasOwnProperty('fixday') ? data.fixday : 1;
+            var fixHour = data.hasOwnProperty('fixhour') ? data.fixhour : 1;
             return {
                 timestamp: data.timestamp,
-                format: data.format
+                format: data.format,
+                type: data.type || '',
+                fixday: fixDay,
+                fixhour: fixHour
             };
         });
 
@@ -155,7 +160,8 @@ define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
      * Only dates not found in either cache will be sent to the server
      * for transforming.
      *
-     * A request object must have a timestamp key and a format key.
+     * A request object must have a timestamp key and a format key and
+     * optionally may have a type key.
      *
      * E.g.
      * var request = [
@@ -165,7 +171,10 @@ define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
      *     },
      *     {
      *         timestamp: 1293876000,
-     *         format: '%A, %d %B %Y, %I:%M %p'
+     *         format: '%A, %d %B %Y, %I:%M %p',
+     *         type: 'gregorian',
+     *         fixday: false,
+     *         fixhour: false
      *     }
      * ];
      *
index 6721d2a..c8e08ab 100644 (file)
@@ -120,7 +120,7 @@ class core_external extends external_api {
     /**
      * Returns description of get_string() result value
      *
-     * @return string
+     * @return external_description
      * @since Moodle 2.4
      */
     public static function get_string_returns() {
@@ -189,7 +189,7 @@ class core_external extends external_api {
     /**
      * Returns description of get_string() result value
      *
-     * @return array
+     * @return external_description
      * @since Moodle 2.4
      */
     public static function get_strings_returns() {
@@ -233,6 +233,9 @@ class core_external extends external_api {
                         [
                             'timestamp' => new external_value(PARAM_INT, 'unix timestamp'),
                             'format' => new external_value(PARAM_TEXT, 'format string'),
+                            'type' => new external_value(PARAM_PLUGIN, 'The calendar type', VALUE_DEFAULT),
+                            'fixday' => new external_value(PARAM_INT, 'Remove leading zero for day', VALUE_DEFAULT, 1),
+                            'fixhour' => new external_value(PARAM_INT, 'Remove leading zero for hour', VALUE_DEFAULT, 1),
                         ]
                     )
                 )
@@ -264,7 +267,12 @@ class core_external extends external_api {
         self::validate_context($context);
 
         $formatteddates = array_map(function($timestamp) {
-            return userdate($timestamp['timestamp'], $timestamp['format']);
+
+            $calendartype = $timestamp['type'];
+            $fixday = !empty($timestamp['fixday']);
+            $fixhour = !empty($timestamp['fixhour']);
+            $calendar  = \core_calendar\type_factory::get_calendar_instance($calendartype);
+            return $calendar->timestamp_to_date_string($timestamp['timestamp'], $timestamp['format'], 99, $fixday, $fixhour);
         }, $params['timestamps']);
 
         return ['dates' => $formatteddates];
@@ -273,7 +281,7 @@ class core_external extends external_api {
     /**
      * Returns description of get_user_dates() result value
      *
-     * @return array
+     * @return external_description
      */
     public static function get_user_dates_returns() {
         return new external_single_structure(
@@ -333,7 +341,7 @@ class core_external extends external_api {
     /**
      * Returns description of get_component_strings() result value
      *
-     * @return array
+     * @return external_description
      * @since Moodle 2.4
      */
     public static function get_component_strings_returns() {
@@ -421,7 +429,7 @@ class core_external extends external_api {
     /**
      * Returns description of get_fragment() result value
      *
-     * @return array
+     * @return external_description
      * @since Moodle 3.1
      */
     public static function get_fragment_returns() {
index 6277bc7..bf43f49 100644 (file)
@@ -203,7 +203,6 @@ class core_external_testcase extends externallib_advanced_testcase {
     }
 
     public function test_get_user_dates() {
-        global $USER, $CFG, $DB;
         $this->resetAfterTest();
 
         $this->setAdminUser();
@@ -222,6 +221,11 @@ class core_external_testcase extends externallib_advanced_testcase {
                 'timestamp' => 1293876000,
                 'format' => '%d %m %Y'
             ],
+            [
+                'timestamp' => 1293876000,
+                'format' => '%d %m %Y',
+                'type' => 'gregorian'
+            ],
             [
                 'timestamp' => 1293876000,
                 'format' => 'some invalid format'
@@ -233,6 +237,7 @@ class core_external_testcase extends externallib_advanced_testcase {
 
         $this->assertEquals('Saturday, 1 January 2011, 6:00', $result['dates'][0]);
         $this->assertEquals('1 01 2011', $result['dates'][1]);
-        $this->assertEquals('some invalid format', $result['dates'][2]);
+        $this->assertEquals('1 01 2011', $result['dates'][2]);
+        $this->assertEquals('some invalid format', $result['dates'][3]);
     }
 }
diff --git a/lib/templates/time_element.mustache b/lib/templates/time_element.mustache
new file mode 100644 (file)
index 0000000..f225448
--- /dev/null
@@ -0,0 +1,92 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core/time_element
+
+    Template to display an HTML time element.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * data-timestamp Number - The timestamp for the element.
+    * data-datetimeformat String - A valid format for the datetime attribute.
+
+    Context variables required for this template:
+    * timestamp Number - The timestamp for the element.
+    * userdateformat String - The user-facing date format
+    * datetimeformat String - A valid format for the datetime attribute. Defaults to the ISO-8601 format of '%Y-%m-%dT%H:%M%z'.
+    Example context (json):
+    {
+        "timestamp": 0,
+        "userdateformat": "%d %b %Y",
+        "datetimeformat": "%Y-%m-%dT%H:%M%z"
+    }
+}}
+<time id="time-{{$elementid}}{{uniqid}}{{/elementid}}" class="{{$elementclass}}{{timeclass}}{{/elementclass}}" datetime="{{$datetimeval}}{{datetime}}{{/datetimeval}}"
+      data-timestamp="{{$timestampval}}{{timestamp}}{{/timestampval}}"
+      data-datetimeformat="{{$datetimeformatval}}{{#datetimeformat}}{{.}}{{/datetimeformat}}{{^datetimeformat}}%Y-%m-%dT%H:%M%z{{/datetimeformat}}{{/datetimeformatval}}">
+    {{$datedisplay}}
+        {{#userdate}} {{$timestampval}}{{timestamp}}{{/timestampval}}, {{$userdateformatval}}{{userdateformat}}{{/userdateformatval}} {{/userdate}}
+    {{/datedisplay}}
+</time>
+{{#js}}
+    /** Fetches the formatted date/time for the time element's datetime attribute. */
+    require(['core/user_date'], function(UserDate) {
+        var root = document.getElementById('time-{{$elementid}}{{uniqid}}{{/elementid}}');
+        // Fetch value for the datetime attribute using core/user_date, if it's not available.
+        if (!root.getAttribute('datetime')) {
+            var dateTimeFormat = root.getAttribute('data-datetimeformat');
+            var timestamp = root.getAttribute('data-timestamp');
+
+            if (!dateTimeFormat.match(/%(?![YmdHMSzZ])./g)) {
+                var zeroPad = function(nNum, nPad) {
+                    return ((Math.pow(10, nPad) + nNum) + '').slice(1);
+                };
+
+                var date = new Date(timestamp * 1000);
+
+                var datetime = dateTimeFormat.replace(/%./g, function(sMatch) {
+                    return (({
+                        '%Y': date.getFullYear(),
+                        '%m': zeroPad(date.getMonth() + 1, 2),
+                        '%d': zeroPad(date.getDate(), 2),
+                        '%H': zeroPad(date.getHours(), 2),
+                        '%M': zeroPad(date.getMinutes(), 2),
+                        '%S': zeroPad(date.getSeconds(), 2),
+                        '%z': date.toTimeString().replace(/.+GMT([+-]\d+).+/, '$1'),
+                        '%Z': date.toTimeString().replace(/.+\((.+?)\)$/, '$1')
+                    }[sMatch] || '') + '') || sMatch;
+                });
+                root.setAttribute('datetime', datetime);
+            }  else {
+                // Otherwise, use core/user_date.
+                var timestamps = [{
+                    timestamp: timestamp,
+                    format: dateTimeFormat,
+                    type: 'gregorian',
+                    fixday: 0,
+                    fixhour: 0
+                }];
+                UserDate.get(timestamps).done(function(dates) {
+                    var datetime = dates.pop();
+                    root.setAttribute('datetime', datetime);
+                });
+            }
+        }
+    });
+{{/js}}
index 9cc65ff..db9c6a0 100644 (file)
@@ -63,6 +63,10 @@ information provided here is intended especially for developers.
 * The following functions have been updated to support passing in an array of group IDs (but still support passing in a single ID):
   * groups_get_members_join()
   * groups_get_members_ids_sql()
+* Additional parameters were added to core_get_user_dates:
+    - type: specifies the calendar type. Optional, defaults to Gregorian.
+    - fixday: Whether to remove leading zero for day. Optional, defaults to 1.
+    - fixhour: Whether to remove leading zero for hour. Optional, defaults to 1.
 
 === 3.8 ===
 * Add CLI option to notify all cron tasks to stop: admin/cli/cron.php --stop
index 8faf0f9..a1919f6 100644 (file)
@@ -638,9 +638,8 @@ class post extends exporter {
     private function get_author_subheading_html(stdClass $exportedauthor, int $timecreated) : string {
         $fullname = $exportedauthor->fullname;
         $profileurl = $exportedauthor->urls['profile'] ?? null;
-        $formatteddate = userdate($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
         $name = $profileurl ? "<a href=\"{$profileurl}\">{$fullname}</a>" : $fullname;
-        $date = "<time>{$formatteddate}</time>";
+        $date = userdate_htmltime($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
         return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]);
     }
 }
index 50cc04d..da67f20 100644 (file)
                                         <div class="author-info align-middle">
                                             <div class="mb-1 line-height-3 text-truncate">{{fullname}}</div>
                                             <div class="line-height-3">
-                                                {{#userdate}}{{discussion.times.created}}, {{#str}}strftimedatemonthabbr, langconfig{{/str}}{{/userdate}}
+                                                {{< core/time_element }}
+                                                    {{$elementid}}created-{{discussion.id}}{{/elementid}}
+                                                    {{$timestampval}}{{discussion.times.created}}{{/timestampval}}
+                                                    {{$userdateformatval}}{{#str}}strftimedatemonthabbr, langconfig{{/str}}{{/userdateformatval}}
+                                                {{/core/time_element}}
                                             </div>
                                         </div>
                                     </div>
                                             <div class="line-height-3">
                                                 {{#latestpostid}}
                                                     <a href="{{{discussion.urls.viewlatest}}}" title="{{#userdate}}{{discussion.times.modified}},{{#str}}strftimerecentfull{{/str}}{{/userdate}}">
-                                                        {{#userdate}}{{discussion.times.modified}}, {{#str}}strftimedatemonthabbr, langconfig{{/str}}{{/userdate}}
+                                                        {{< core/time_element }}
+                                                            {{$elementid}}modified-{{discussion.id}}{{/elementid}}
+                                                            {{$timestampval}}{{discussion.times.modified}}{{/timestampval}}
+                                                            {{$userdateformatval}}{{#str}}strftimedatemonthabbr, langconfig{{/str}}{{/userdateformatval}}
+                                                        {{/ core/time_element }}
                                                     </a>
                                                 {{/latestpostid}}
                                             </div>
index 32b44d0..f138cd2 100644 (file)
@@ -77,7 +77,7 @@
             <header id="post-header-{{id}}-{{uniqid}}">
                 {{^isdeleted}}
                     <div class="d-flex flex-wrap align-items-center mb-1">
-                        <address class="mb-0 mr-2" tabindex="-1">
+                        <div class="mr-2" tabindex="-1">
                             {{#author}}
                                 <h4 class="h6 d-lg-inline-block mb-0 author-header mr-1">
                                     {{#parentauthorname}}
                                     {{/parentauthorname}}
                                 </h4>
                             {{/author}}
-                            <time class="text-muted">
-                                {{#userdate}} {{timecreated}}, {{#str}} strftimerecentfull, core_langconfig {{/str}} {{/userdate}}
-                            </time>
-                        </address>
+                            {{< core/time_element }}
+                                {{$elementid}}created-{{id}}-{{uniqid}}{{/elementid}}
+                                {{$elementclass}}text-muted{{/elementclass}}
+                                {{$timestampval}}{{timecreated}}{{/timestampval}}
+                                {{$userdateformatval}}{{#str}} strftimerecentfull, core_langconfig {{/str}}{{/userdateformatval}}
+                            {{/core/time_element}}
+                        </div>
 
                         <div class="d-flex align-items-center ml-auto">
                             {{#author.groups}}
index dad785c..868c1e8 100644 (file)
                             }}>{{$subject}}{{{subject}}}{{/subject}}</h3>
                     {{/subjectheading}}
                     {{^isdeleted}}
-                        <address tabindex="-1">
+                        <div class="mb-3" tabindex="-1">
                             {{#html.authorsubheading}}{{{.}}}{{/html.authorsubheading}}
                             {{^html.authorsubheading}}
-                                <time>
-                                    {{#userdate}} {{timecreated}}, {{#str}} strftimedaydatetime, core_langconfig {{/str}} {{/userdate}}
-                                </time>
+                                {{< core/time_element }}
+                                    {{$elementid}}created-{{id}}-{{uniqid}}{{/elementid}}
+                                    {{$timestampval}}{{timecreated}}{{/timestampval}}
+                                    {{$userdateformatval}}{{#str}} strftimedaydatetime, core_langconfig {{/str}}{{/userdateformatval}}
+                                {{/core/time_element}}
                             {{/html.authorsubheading}}
-                        </address>
+                        </div>
                     {{/isdeleted}}
                     {{#isprivatereply}}
                         <div class="privatereplyinfo">
index fd4c034..a4819dd 100644 (file)
@@ -38,9 +38,7 @@
 >
     <a href="{{{urls.viewisolated}}}">{{subject}}</a>
     {{^isdeleted}}
-        <address class="d-inline-block mb-0">
-            {{{html.authorsubheading}}}
-        </address>
+        {{{html.authorsubheading}}}
     {{/isdeleted}}
 
     <div data-region="replies-container">
index 303c195..bb071a0 100644 (file)
@@ -371,9 +371,8 @@ class mod_forum_generator extends testing_module_generator {
     public function get_author_subheading_html(stdClass $exportedauthor, int $timecreated) : string {
         $fullname = $exportedauthor->fullname;
         $profileurl = $exportedauthor->urls['profile'] ?? null;
-        $formatteddate = userdate($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
         $name = $profileurl ? "<a href=\"{$profileurl}\">{$fullname}</a>" : $fullname;
-        $date = "<time>{$formatteddate}</time>";
+        $date = userdate_htmltime($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
         return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]);
     }
 }
index 5c9f43e..9514e74 100644 (file)
@@ -108,7 +108,7 @@ $PAGE->set_heading($course->fullname);
 $PAGE->set_button(forum_search_form($course, $search));
 
 if ($istypesingle && $displaymode == FORUM_MODE_NESTED_V2) {
-    $PAGE->add_body_class('reset-style');
+    $PAGE->add_body_class('nested-v2-display-mode reset-style');
     $settingstrigger = $OUTPUT->render_from_template('mod_forum/settings_drawer_trigger', null);
     $PAGE->add_header_action($settingstrigger);
 }
index 5e6e4b3..0d2408f 100644 (file)
@@ -236,7 +236,7 @@ $allow-reset-style: true !default;
                     vertical-align: top;
 
                     div[role="main"] {
-                        flex: 1;
+                        flex: 1 0 auto;
                     }
 
                     .activity-navigation {
index 48c6ea9..aa6c0db 100644 (file)
@@ -17737,7 +17737,7 @@ body.reset-style #page-content {
     padding-right: 1.25rem;
     vertical-align: top; }
     body.reset-style #page-content #region-main-box #region-main div[role="main"] {
-      flex: 1; }
+      flex: 1 0 auto; }
     body.reset-style #page-content #region-main-box #region-main .activity-navigation {
       overflow: hidden; }
     body.reset-style #page-content #region-main-box #region-main.has-blocks {