Merge branch 'MDL-59753-master' of git://github.com/lameze/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 5 Sep 2017 11:28:53 +0000 (13:28 +0200)
committerDavid Monllao <davidm@moodle.com>
Thu, 7 Sep 2017 08:53:32 +0000 (10:53 +0200)
20 files changed:
calendar/amd/build/summary_modal.min.js
calendar/amd/src/summary_modal.js
calendar/tests/behat/calendar.feature
lib/amd/build/modal_confirm.min.js
lib/amd/build/modal_events.min.js
lib/amd/build/modal_factory.min.js
lib/amd/src/modal_confirm.js
lib/amd/src/modal_events.js
lib/amd/src/modal_factory.js
lib/ddl/tests/ddl_test.php
lib/dml/moodle_database.php
lib/dml/tests/dml_test.php
lib/templates/modal_confirm.mustache [deleted file]
lib/upgrade.txt
message/amd/build/message_area_messages.min.js
message/amd/src/message_area_messages.js
message/tests/behat/delete_all_messages.feature
user/amd/build/status_field.min.js
user/amd/src/status_field.js
user/tests/behat/edit_user_enrolment.feature

index 358bad0..0a9f53a 100644 (file)
Binary files a/calendar/amd/build/summary_modal.min.js and b/calendar/amd/build/summary_modal.min.js differ
index 4a668be..da138c3 100644 (file)
@@ -118,33 +118,11 @@ define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_even
         // Apply parent event listeners.
         Modal.prototype.registerEventListeners.call(this);
 
-        var confirmPromise = ModalFactory.create(
-            {
-                type: ModalFactory.types.CONFIRM
-            },
-            this.getDeleteButton()
-        ).then(function(modal) {
-            modal.getRoot().on(ModalEvents.yes, function() {
-                var eventId = this.getEventId();
-
-                CalendarRepository.deleteEvent(eventId)
-                    .then(function() {
-                        $('body').trigger(CalendarEvents.deleted, [eventId]);
-                        this.hide();
-                    }.bind(this))
-                    .catch(Notification.exception);
-            }.bind(this));
-
-            return modal;
-        }.bind(this));
-
         // We have to wait for the modal to finish rendering in order to ensure that
         // the data-event-title property is available to use as the modal title.
         this.getRoot().on(ModalEvents.bodyRendered, function() {
             var eventTitle = this.getBody().find(SELECTORS.ROOT).attr('data-event-title');
-            confirmPromise.then(function(modal) {
-                modal.setBody(Str.get_string('confirmeventdelete', 'core_calendar', eventTitle));
-            });
+            prepareDeleteAction(this, eventTitle);
         }.bind(this));
 
         CustomEvents.define(this.getEditButton(), [
@@ -173,6 +151,48 @@ define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_even
         }.bind(this));
     };
 
+    /**
+     * Prepares the action for the summary modal's delete action.
+     *
+     * @param {ModalEventSummary} summaryModal The summary modal instance.
+     * @param {string} eventTitle The event title.
+     */
+    function prepareDeleteAction(summaryModal, eventTitle) {
+        var deleteStrings = [
+            {
+                key: 'deleteevent',
+                component: 'calendar'
+            },
+            {
+                key: 'confirmeventdelete',
+                component: 'calendar',
+                param: eventTitle
+            }
+        ];
+        var eventId = summaryModal.getEventId();
+        var stringsPromise = Str.get_strings(deleteStrings);
+        var deletePromise = ModalFactory.create(
+            {
+                type: ModalFactory.types.SAVE_CANCEL
+            },
+            summaryModal.getDeleteButton()
+        );
+
+        $.when(stringsPromise, deletePromise).then(function(strings, deleteModal) {
+            deleteModal.setTitle(strings[0]);
+            deleteModal.setBody(strings[1]);
+            deleteModal.setSaveButtonText(strings[0]);
+            deleteModal.getRoot().on(ModalEvents.save, function() {
+                CalendarRepository.deleteEvent(eventId).then(function() {
+                    $('body').trigger(CalendarEvents.deleted, [eventId]);
+                    summaryModal.hide();
+                    return;
+                }).catch(Notification.exception);
+            });
+            return deleteModal;
+        }).fail(Notification.exception);
+    }
+
     // Automatically register with the modal registry the first time this module is imported so that you can create modals
     // of this type using the modal factory.
     if (!registered) {
index efc236f..f1d6153 100644 (file)
@@ -104,7 +104,7 @@ Feature: Perform basic calendar functionality
     When I follow "This month"
     And I click on "Really awesome event!" "link"
     And I click on "Delete" "button"
-    And I click on "Yes" "button"
+    And I click on "Delete event" "button"
     And I wait to be redirected
     Then I should not see "Really awesome event!"
 
index 905b621..68df031 100644 (file)
Binary files a/lib/amd/build/modal_confirm.min.js and b/lib/amd/build/modal_confirm.min.js differ
index 9b2eaab..5fc8341 100644 (file)
Binary files a/lib/amd/build/modal_events.min.js and b/lib/amd/build/modal_events.min.js differ
index 02e0604..04cb7e1 100644 (file)
Binary files a/lib/amd/build/modal_factory.min.js and b/lib/amd/build/modal_factory.min.js differ
index 171c433..f396c8e 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Contain the logic for the save/cancel modal.
+ * Contain the logic for the yes/no confirmation modal.
+ * This has been deprecated and should not be used anymore. Please use core/modal_save_cancel instead.
+ * See MDL-59759.
  *
- * @module     core/modal_save_cancel
- * @class      modal_save_cancel
+ * @deprecated Since Moodle 3.4
+ * @module     core/modal_confirm
+ * @class      modal_confirm
  * @package    core
  * @copyright  2016 Ryan Wyllie <ryan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_events'],
-        function($, Notification, CustomEvents, Modal, ModalEvents) {
-
-    var SELECTORS = {
-        YES_BUTTON: '[data-action="yes"]',
-        NO_BUTTON: '[data-action="no"]',
-    };
+define(['jquery', 'core/custom_interaction_events', 'core/modal_events', 'core/modal_save_cancel', 'core/log'],
+        function($, CustomEvents, ModalEvents, ModalSaveCancel, Log) {
 
     /**
      * Constructor for the Modal.
@@ -36,57 +34,13 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
      * @param {object} root The root jQuery element for the modal
      */
     var ModalConfirm = function(root) {
-        Modal.call(this, root);
-
-        if (!this.getFooter().find(SELECTORS.YES_BUTTON).length) {
-            Notification.exception({message: 'No "yes" button found'});
-        }
-
-        if (!this.getFooter().find(SELECTORS.NO_BUTTON).length) {
-            Notification.exception({message: 'No "no" button found'});
-        }
+        Log.warn("The CONFIRM modal type has been deprecated and should not be used anymore." +
+            " Please use the SAVE_CANCEL modal type instead.");
+        ModalSaveCancel.call(this, root);
     };
 
-    ModalConfirm.prototype = Object.create(Modal.prototype);
+    ModalConfirm.prototype = Object.create(ModalSaveCancel.prototype);
     ModalConfirm.prototype.constructor = ModalConfirm;
 
-    /**
-     * Override parent implementation to prevent changing the footer content.
-     */
-    ModalConfirm.prototype.setFooter = function() {
-        Notification.exception({message: 'Can not change the footer of a confirm modal'});
-        return;
-    };
-
-    /**
-     * Set up all of the event handling for the modal.
-     *
-     * @method registerEventListeners
-     */
-    ModalConfirm.prototype.registerEventListeners = function() {
-        // Apply parent event listeners.
-        Modal.prototype.registerEventListeners.call(this);
-
-        this.getModal().on(CustomEvents.events.activate, SELECTORS.YES_BUTTON, function(e, data) {
-            var yesEvent = $.Event(ModalEvents.yes);
-            this.getRoot().trigger(yesEvent, this);
-
-            if (!yesEvent.isDefaultPrevented()) {
-                this.hide();
-                data.originalEvent.preventDefault();
-            }
-        }.bind(this));
-
-        this.getModal().on(CustomEvents.events.activate, SELECTORS.NO_BUTTON, function(e, data) {
-            var noEvent = $.Event(ModalEvents.no);
-            this.getRoot().trigger(noEvent, this);
-
-            if (!noEvent.isDefaultPrevented()) {
-                this.hide();
-                data.originalEvent.preventDefault();
-            }
-        }.bind(this));
-    };
-
     return ModalConfirm;
 });
index bbd3979..9c26179 100644 (file)
@@ -32,8 +32,9 @@ define([], function() {
         // ModalSaveCancel events.
         save: 'modal-save-cancel:save',
         cancel: 'modal-save-cancel:cancel',
-        // ModalConfirm events.
-        yes: 'modal-confirm:yes',
-        no: 'modal-confirm:no',
+        // ModalConfirm events. Deprecated since Moodle 3.4. See MDL-59759.
+        // Point core/modal_confirm events to save/cancel events of core/modal_save_cancel.
+        yes: 'modal-save-cancel:save',
+        no: 'modal-save-cancel:cancel',
     };
 });
index f9db208..770ecdf 100644 (file)
@@ -32,7 +32,8 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
     var TEMPLATES = {
         DEFAULT: 'core/modal',
         SAVE_CANCEL: 'core/modal_save_cancel',
-        CONFIRM: 'core/modal_confirm',
+        // Deprecated since Moodle 3.4. Point template to core/modal_save_cancel instead. See MDL-59759.
+        CONFIRM: 'core/modal_save_cancel',
         CANCEL: 'core/modal_cancel',
     };
 
@@ -40,6 +41,7 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
     var TYPES = {
         DEFAULT: 'DEFAULT',
         SAVE_CANCEL: 'SAVE_CANCEL',
+        // Deprecated since Moodle 3.4. See MDL-59759.
         CONFIRM: 'CONFIRM',
         CANCEL: 'CANCEL',
     };
@@ -47,6 +49,7 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
     // Register the common set of modals.
     ModalRegistry.register(TYPES.DEFAULT, Modal, TEMPLATES.DEFAULT);
     ModalRegistry.register(TYPES.SAVE_CANCEL, ModalSaveCancel, TEMPLATES.SAVE_CANCEL);
+    // Deprecated since Moodle 3.4. See MDL-59759.
     ModalRegistry.register(TYPES.CONFIRM, ModalConfirm, TEMPLATES.CONFIRM);
     ModalRegistry.register(TYPES.CANCEL, ModalCancel, TEMPLATES.CANCEL);
 
index edf6258..1fbec98 100644 (file)
@@ -2115,6 +2115,55 @@ class core_ddl_testcase extends database_driver_testcase {
         }
     }
 
+    /**
+     * Data provider for test_get_enc_quoted().
+     *
+     * @return array The type-value pair fixture.
+     */
+    public function test_get_enc_quoted_provider() {
+        return array(
+            // Reserved: some examples from SQL-92.
+            [true, 'from'],
+            [true, 'table'],
+            [true, 'where'],
+            // Not reserved.
+            [false, 'my_awesome_column_name']
+        );
+    }
+
+    /**
+     * This is a test for sql_generator::getEncQuoted().
+     *
+     * @dataProvider test_get_enc_quoted_provider
+     * @param string $reserved Whether the column name is reserved or not.
+     * @param string $columnname The column name to be quoted, according to the value of $reserved.
+     **/
+    public function test_get_enc_quoted($reserved, $columnname) {
+        $DB = $this->tdb;
+        $gen = $DB->get_manager()->generator;
+
+        if (!$reserved) {
+            // No need to quote the column name.
+            $this->assertSame($columnname, $gen->getEncQuoted($columnname));
+        } else {
+            // Column name should be quoted.
+            $dbfamily = $DB->get_dbfamily();
+
+            switch ($dbfamily) {
+                case 'mysql':
+                    $this->assertSame("`$columnname`", $gen->getEncQuoted($columnname));
+                    break;
+                case 'mssql': // The Moodle connection runs under 'QUOTED_IDENTIFIER ON'.
+                case 'oracle':
+                case 'postgres':
+                case 'sqlite':
+                default:
+                    $this->assertSame('"' . $columnname . '"', $gen->getEncQuoted($columnname));
+                    break;
+            }
+        }
+    }
+
     // Following methods are not supported == Do not test.
     /*
         public function testRenameIndex() {
index 3dd3586..8741823 100644 (file)
@@ -2398,7 +2398,8 @@ abstract class moodle_database {
         // NOTE: override this methods if following standard compliant SQL
         //       does not work for your driver.
 
-        $columnname = $column->name;
+        // Enclose the column name by the proper quotes if it's a reserved word.
+        $columnname = $this->get_manager()->generator->getEncQuoted($column->name);
         $sql = "UPDATE {".$table."}
                        SET $columnname = REPLACE($columnname, ?, ?)
                      WHERE $columnname IS NOT NULL";
index 849fbf3..8786585 100644 (file)
@@ -4573,19 +4573,55 @@ class core_dml_testcase extends database_driver_testcase {
         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
         $table->add_field('name', XMLDB_TYPE_CHAR, '20', null, null);
         $table->add_field('intro', XMLDB_TYPE_TEXT, 'big', null, null);
+        // Add a CHAR field named using a word reserved for all the supported DB servers.
+        $table->add_field('where', XMLDB_TYPE_CHAR, '20', null, null, null, 'localhost');
+        // Add a TEXT field named using a word reserved for all the supported DB servers.
+        $table->add_field('from', XMLDB_TYPE_TEXT, 'big', null, null);
         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
         $dbman->create_table($table);
 
-        $id1 = (string)$DB->insert_record($tablename, array('name' => null, 'intro' => null));
-        $id2 = (string)$DB->insert_record($tablename, array('name' => '', 'intro' => ''));
-        $id3 = (string)$DB->insert_record($tablename, array('name' => 'xxyy', 'intro' => 'vvzz'));
-        $id4 = (string)$DB->insert_record($tablename, array('name' => 'aa bb aa bb', 'intro' => 'cc dd cc aa'));
-        $id5 = (string)$DB->insert_record($tablename, array('name' => 'kkllll', 'intro' => 'kkllll'));
+        $fromfield = $dbman->generator->getEncQuoted('from');
+        $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES (NULL,NULL,'localhost')");
+        $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('','','localhost')");
+        $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('xxyy','vvzz','localhost')");
+        $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('aa bb aa bb','cc dd cc aa','localhost')");
+        $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('kkllll','kkllll','localhost')");
 
         $expected = $DB->get_records($tablename, array(), 'id ASC');
+        $idx = 1;
+        $id1 = $id2 = $id3 = $id4 = $id5 = 0;
+        foreach (array_keys($expected) as $identifier) {
+            ${"id$idx"} = (string)$identifier;
+            $idx++;
+        }
 
         $columns = $DB->get_columns($tablename);
 
+        // Replace should work even with columns named using a reserved word.
+        $this->assertEquals('C', $columns['where']->meta_type);
+        $this->assertEquals('localhost', $expected[$id1]->where);
+        $this->assertEquals('localhost', $expected[$id2]->where);
+        $this->assertEquals('localhost', $expected[$id3]->where);
+        $this->assertEquals('localhost', $expected[$id4]->where);
+        $this->assertEquals('localhost', $expected[$id5]->where);
+        $DB->replace_all_text($tablename, $columns['where'], 'localhost', '::1');
+        $result = $DB->get_records($tablename, array(), 'id ASC');
+        $expected[$id1]->where = '::1';
+        $expected[$id2]->where = '::1';
+        $expected[$id3]->where = '::1';
+        $expected[$id4]->where = '::1';
+        $expected[$id5]->where = '::1';
+        $this->assertEquals($expected, $result);
+        $this->assertEquals('X', $columns['from']->meta_type);
+        $DB->replace_all_text($tablename, $columns['from'], 'localhost', '127.0.0.1');
+        $result = $DB->get_records($tablename, array(), 'id ASC');
+        $expected[$id1]->from = '127.0.0.1';
+        $expected[$id2]->from = '127.0.0.1';
+        $expected[$id3]->from = '127.0.0.1';
+        $expected[$id4]->from = '127.0.0.1';
+        $expected[$id5]->from = '127.0.0.1';
+        $this->assertEquals($expected, $result);
+
         $DB->replace_all_text($tablename, $columns['name'], 'aa', 'o');
         $result = $DB->get_records($tablename, array(), 'id ASC');
         $expected[$id4]->name = 'o bb o bb';
diff --git a/lib/templates/modal_confirm.mustache b/lib/templates/modal_confirm.mustache
deleted file mode 100644 (file)
index 549a3c6..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-{{!
-    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/modal_confirm
-
-    Moodle template for a confirmation modal.
-
-    The purpose of this template is to render a modal.
-
-    Classes required for JS:
-    * none
-
-    Data attributes required for JS:
-    * none
-
-    Context variables required for this template:
-    * body HTML content for the boday
-
-    Example context (json):
-    {
-    }
-}}
-
-{{< core/modal }}
-    {{$title}}{{#str}} confirm {{/str}}{{/title}}
-    {{$body}}{{#str}} areyousure {{/str}}{{/body}}
-    {{$footer}}
-        <button type="button" class="btn btn-primary" data-action="yes">{{#str}} yes {{/str}}</button>
-        <button type="button" class="btn btn-secondary" data-action="no">{{#str}} no {{/str}}</button>
-    {{/footer}}
-{{/ core/modal }}
index 77e180a..cd4d742 100644 (file)
@@ -42,6 +42,8 @@ information provided here is intended especially for developers.
   close the suggestions popup immediately after an option has been selected. If not specified, it defaults to true for single-select
   elements and false for multiple-select elements.
 * user_can_view_profile() now also checks the moodle/user:viewalldetails capability.
+* The core/modal_confirm dialogue has been deprecated. Please use the core/modal_save_cancel dialogue instead. Please ensure you
+  update to use the ModalEvents.save and ModalEvents.cancel events instead of their yes/no counterparts.
 
 === 3.3.1 ===
 
index 9a6fab9..fb627be 100644 (file)
Binary files a/message/amd/build/message_area_messages.min.js and b/message/amd/build/message_area_messages.min.js differ
index 4c5711b..77e6f50 100644 (file)
@@ -531,19 +531,26 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
                 return;
             }
 
-            Str.get_strings([
+            var stringsPromise = Str.get_strings([
                 {key: 'confirm'},
-                {key: 'deleteallconfirm', component: 'message'}
-            ]).then(function(s) {
-                return ModalFactory.create({
-                    title: s[0],
-                    type: ModalFactory.types.CONFIRM,
-                    body: s[1]
-                }, this.messageArea.find(SELECTORS.DELETEALLMESSAGES));
-            }.bind(this)).then(function(modal) {
+                {key: 'deleteallconfirm', component: 'message'},
+                {key: 'delete'}
+            ]);
+            var deleteModalPromise = ModalFactory.create(
+                {
+                    type: ModalFactory.types.SAVE_CANCEL
+                },
+                this.messageArea.find(SELECTORS.DELETEALLMESSAGES)
+            );
+
+            $.when(stringsPromise, deleteModalPromise).then(function(s, modal) {
+                modal.setTitle(s[0]);
+                modal.setBody(s[1]);
+                modal.setSaveButtonText(s[2]);
+
                 this._confirmationModal = modal;
                 // Only delete the conversation if the user agreed in the confirmation modal.
-                modal.getRoot().on(ModalEvents.yes, function() {
+                modal.getRoot().on(ModalEvents.save, function() {
                     var otherUserId = this._getUserId();
                     var request = {
                         methodname: 'core_message_delete_conversation',
index ebfbef7..2adf5b3 100644 (file)
@@ -19,7 +19,7 @@ Feature: Delete all messages
     And I click on "start-delete-messages" "message_area_action"
     And I click on "Delete all" "button"
     # Confirm dialogue.
-    And I click on "Yes" "button"
+    And I click on "Delete" "button" in the ".modal-footer" "css_element"
     # Confirm the interface is immediately updated.
     Then I should not see "User 2" in the "conversations" "message_area_region_content"
     # Confirm the changes are persisted.
index 565c8dd..c1743f5 100644 (file)
Binary files a/user/amd/build/status_field.min.js and b/user/amd/build/status_field.min.js differ
index 3e1303b..46cb8b6 100644 (file)
@@ -145,18 +145,19 @@ define(['core/templates',
                     }
                 ];
 
-                $.when(Str.get_strings(strings)).then(function(results) {
+                var deleteModalPromise = ModalFactory.create({
+                    type: ModalFactory.types.SAVE_CANCEL
+                });
+
+                $.when(Str.get_strings(strings), deleteModalPromise).done(function(results, modal) {
                     var title = results[0];
                     var confirmMessage = results[1];
-                    return ModalFactory.create({
-                        body: confirmMessage,
-                        large: true,
-                        title: title,
-                        type: ModalFactory.types.CONFIRM
-                    });
-                }).done(function(modal) {
+                    modal.setTitle(title);
+                    modal.setBody(confirmMessage);
+                    modal.setSaveButtonText(title);
+
                     // Handle confirm event.
-                    modal.getRoot().on(ModalEvents.yes, function() {
+                    modal.getRoot().on(ModalEvents.save, function() {
                         // Build params.
                         var unenrolParams = {
                             confirm: 1,
index 1fe2b52..9c3f44b 100644 (file)
@@ -45,7 +45,7 @@ Feature: Edit user enrolment
     And I am on "Course 1" course homepage
     And I navigate to course participants
     When I click on "Unenrol" "icon" in the "student1" "table_row"
-    And I click on "Yes" "button"
+    And I click on "Unenrol" "button" in the ".modal-footer" "css_element"
     Then I should not see "Student 1" in the "participants" "table"
 
   @javascript