diff --git a/www/addons/messages/controllers/discussion.js b/www/addons/messages/controllers/discussion.js index d42b435d0f0..4064ca2a891 100644 --- a/www/addons/messages/controllers/discussion.js +++ b/www/addons/messages/controllers/discussion.js @@ -148,18 +148,14 @@ angular.module('mm.addons.messages') notifyNewMessage(); }); - }, function(error) { + }).catch(function(error) { messagesBeingSent--; // Only close the keyboard if an error happens, we want the user to be able to send multiple // messages without the keyboard being closed. $mmApp.closeKeyboard(); - if (typeof error === 'string') { - $mmUtil.showErrorModal(error); - } else { - $mmUtil.showErrorModal('mma.messages.messagenotsent', true); - } + $mmUtil.showErrorModalDefault(error, 'mma.messages.messagenotsent', true); $scope.messages.splice($scope.messages.indexOf(message), 1); }); }); @@ -570,11 +566,7 @@ angular.module('mm.addons.messages') $scope.messages.splice(index, 1); // Remove message from the list without having to wait for re-fetch. fetchMessages(); // Re-fetch messages to update cached data. }).catch(function(error) { - if (typeof error === 'string') { - $mmUtil.showErrorModal(error); - } else { - $mmUtil.showErrorModal('mma.messages.errordeletemessage', true); - } + $mmUtil.showErrorModalDefault(error, 'mma.messages.errordeletemessage', true); }).finally(function() { modal.dismiss(); }); diff --git a/www/addons/messages/services/messages.js b/www/addons/messages/services/messages.js index b7b536c45cd..288cb440f6f 100644 --- a/www/addons/messages/services/messages.js +++ b/www/addons/messages/services/messages.js @@ -21,7 +21,7 @@ angular.module('mm.addons.messages') * @ngdoc service * @name $mmaMessages */ -.factory('$mmaMessages', function($mmSite, $mmSitesManager, $log, $q, $mmUser, $mmaMessagesOffline, $mmApp, +.factory('$mmaMessages', function($mmSite, $mmSitesManager, $log, $q, $mmUser, $mmaMessagesOffline, $mmApp, $mmUtil, mmaMessagesNewMessageEvent, mmaMessagesLimitMessages) { $log = $log.getInstance('$mmaMessages'); @@ -1092,7 +1092,7 @@ angular.module('mm.addons.messages') return self.sendMessagesOnline(messages, siteId).catch(function(error) { return $q.reject({ error: error, - wserror: false + wserror: $mmUtil.isWebServiceError(error) }); }).then(function(response) { if (response && response[0] && response[0].msgid === -1) { @@ -1122,8 +1122,6 @@ angular.module('mm.addons.messages') * have been sent, the resolve param can contain errors for messages not sent. */ self.sendMessagesOnline = function(messages, siteId) { - siteId = siteId || $mmSite.getId(); - return $mmSitesManager.getSite(siteId).then(function(site) { var data = { messages: messages diff --git a/www/core/lang/en.json b/www/core/lang/en.json index 31844eb4944..aca559c43df 100644 --- a/www/core/lang/en.json +++ b/www/core/lang/en.json @@ -179,13 +179,15 @@ "twoparagraphs": "{{p1}}

{{p2}}", "tryagain": "Try again", "uhoh": "Uh oh!", + "unicodenotsupported" : "Unicode text like emojis are not supported on this site, text will be sent removing those characters.", + "unicodenotsupportedcleanerror" : "Empty text was found when cleaning Unicode chars.", + "unknown": "Unknown", + "unlimited": "Unlimited", + "unzipping": "Unzipping", "upgraderunning": "Site is being upgraded, please retry later.", "unexpectederror": "Unexepected error. Please close and reopen the application to try again", "userdeleted": "This user account has been deleted", "userdetails": "User details", - "unknown": "Unknown", - "unlimited": "Unlimited", - "unzipping": "Unzipping", "usernotfullysetup": "User not fully set-up", "users": "Users", "view": "View", diff --git a/www/core/lib/sitesfactory.js b/www/core/lib/sitesfactory.js index f27ccb0820b..cf1a94b39db 100644 --- a/www/core/lib/sitesfactory.js +++ b/www/core/lib/sitesfactory.js @@ -121,7 +121,7 @@ angular.module('mm.core') mmCoreWSPrefix, mmCoreSessionExpired, $mmEvents, mmCoreEventSessionExpired, mmCoreUserDeleted, mmCoreEventUserDeleted, $mmText, $translate, mmCoreConfigConstants, mmCoreUserPasswordChangeForced, mmCoreEventPasswordChangeForced, mmCoreLoginTokenChangePassword, mmCoreSecondsMinute, mmCoreUserNotFullySetup, mmCoreEventUserNotFullySetup, - mmCoreSitePolicyNotAgreed, mmCoreEventSitePolicyNotAgreed) { + mmCoreSitePolicyNotAgreed, mmCoreEventSitePolicyNotAgreed, mmCoreUnicodeNotSupported) { $log = $log.getInstance('$mmSite'); @@ -182,6 +182,7 @@ angular.module('mm.core') this.privateToken = privateToken; this.config = config; this.loggedOut = !!loggedOut; + this.cleanUnicode = false; if (this.id) { this.db = $mmDB.getDB('Site-' + this.id, siteSchema, dboptions); @@ -423,6 +424,9 @@ angular.module('mm.core') saveToCache: 0 }; + // Reset clean Unicode to check if it's supported again. + site.cleanUnicode = false; + site.read('core_webservice_get_site_info', {}, preSets).then(deferred.resolve, function(error) { site.read('moodle_webservice_get_siteinfo', {}, preSets).then(deferred.resolve, function(error) { deferred.reject(error); @@ -537,6 +541,16 @@ angular.module('mm.core') preSets = angular.copy(preSets) || {}; preSets.wstoken = site.token; preSets.siteurl = site.siteurl; + preSets.cleanUnicode = site.cleanUnicode; + + if (preSets.cleanUnicode && $mmText.hasUnicodeData(data)) { + // Data will be cleaned, notify the user. + // @todo: Detect if the call is a syncing call and not notify. + $mmUtil.showToast('mm.core.unicodenotsupported', true, 3000); + } else { + // No need to clean data in this call. + preSets.cleanUnicode = false; + } // Enable text filtering by default. data.moodlewssettingfilter = preSets.filter === false ? false : true; @@ -597,6 +611,14 @@ angular.module('mm.core') // Site policy not agreed, trigger event. $mmEvents.trigger(mmCoreEventSitePolicyNotAgreed, site.id); return $mmLang.translateAndReject('mm.login.sitepolicynotagreederror'); + } else if (error === mmCoreUnicodeNotSupported) { + if (!site.cleanUnicode) { + // Try again cleaning unicode. + site.cleanUnicode = true; + return site.request(method, data, preSets); + } + // This should not happen. + return $mmLang.translateAndReject('mm.core.unicodenotsupported'); } else if (typeof preSets.emergencyCache !== 'undefined' && !preSets.emergencyCache) { $log.debug('WS call ' + method + ' failed. Emergency cache is forbidden, rejecting.'); return $q.reject(error); diff --git a/www/core/lib/text.js b/www/core/lib/text.js index 3f6c0d2c3e9..3f4e9d7c724 100644 --- a/www/core/lib/text.js +++ b/www/core/lib/text.js @@ -579,5 +579,66 @@ angular.module('mm.core') return /<[a-z][\s\S]*>/i.test(text); }; + /** + * Check if a text contains Unicode long chars. + * Using as threshold Hex value D800 + * + * @module mm.core + * @ngdoc method + * @name $mmText#hasUnicode + * @param {String} text Text to check. + * @return {Boolean} True if has Unicode chars, false otherwise. + */ + self.hasUnicode = function(text) { + for (var x = 0; x < text.length; x++) { + if (text.charCodeAt(x) > 55295) { + return true; + } + } + return false; + }; + + /** + * Check if an object has any long Unicode char. + * + * @module mm.core + * @ngdoc method + * @name $mmText#hasUnicodeData + * @param {Mixed} data Object to be checked. + * @return {Boolean} If the data has any long Unicode char on it. + */ + self.hasUnicodeData = function(data) { + for (var el in data) { + if (angular.isObject(data[el])) { + if (self.hasUnicodeData(data[el])) { + return true; + } + } else if (typeof data[el] == "string" && self.hasUnicode(data[el])) { + return true; + } + } + return false; + } + + /** + * Strip Unicode long char of a given text. + * Using as threshold Hex value D800 + * + * @module mm.core + * @ngdoc method + * @name $mmText#stripUnicode + * @param {String} text Text to check. + * @return {String} Without the Unicode chars. + */ + self.stripUnicode = function(text) { + var stripped = ""; + for (var x = 0; x < text.length; x++) { + if (text.charCodeAt(x) <= 55295){ + stripped += text.charAt(x); + } + } + return stripped; + }; + return self; }); diff --git a/www/core/lib/util.js b/www/core/lib/util.js index e8f95fec15c..0a62932473b 100644 --- a/www/core/lib/util.js +++ b/www/core/lib/util.js @@ -1945,7 +1945,8 @@ angular.module('mm.core') $translate.instant('mm.core.errorinvalidresponse'), $translate.instant('mm.core.sitemaintenance'), $translate.instant('mm.core.upgraderunning'), - $translate.instant('mm.core.nopasswordchangeforced') + $translate.instant('mm.core.nopasswordchangeforced'), + $translate.instant('mm.core.unicodenotsupported') ]; return error && localErrors.indexOf(error) == -1; }; diff --git a/www/core/lib/ws.js b/www/core/lib/ws.js index 4a9c8073947..fa626508324 100644 --- a/www/core/lib/ws.js +++ b/www/core/lib/ws.js @@ -25,8 +25,8 @@ angular.module('mm.core') * @name $mmWS */ .factory('$mmWS', function($http, $q, $log, $mmLang, $cordovaFileTransfer, $mmApp, $mmFS, mmCoreSessionExpired, $translate, $window, - mmCoreUserDeleted, md5, $timeout, mmWSTimeout, mmCoreUserPasswordChangeForced, mmCoreUserNotFullySetup, - mmCoreSitePolicyNotAgreed) { + mmCoreUserDeleted, md5, $timeout, mmWSTimeout, mmCoreUserPasswordChangeForced, mmCoreUserNotFullySetup, $mmText, + mmCoreSitePolicyNotAgreed, mmCoreUnicodeNotSupported) { $log = $log.getInstance('$mmWS'); @@ -49,14 +49,13 @@ angular.module('mm.core') * - wstoken string The Webservice token. * - responseExpected boolean Defaults to true. Set to false when the expected response is null. * - typeExpected string Defaults to 'object'. Use it when you expect a type that's not an object|array. + * - cleanUnicode boolean Defaults to false. Clean multibyte Unicode chars from data. * @return {Promise} Promise resolved with the response data in success and rejected with the error message if it fails. */ self.call = function(method, data, preSets) { var siteurl; - data = convertValuesToString(data); - if (typeof preSets == 'undefined' || preSets === null || typeof preSets.wstoken == 'undefined' || typeof preSets.siteurl == 'undefined') { return $mmLang.translateAndReject('mm.core.unexpectederror'); @@ -69,6 +68,13 @@ angular.module('mm.core') preSets.responseExpected = true; } + try { + data = convertValuesToString(data, preSets.cleanUnicode); + } catch (e) { + // Empty cleaned text found. + return $mmLang.translateAndReject('mm.core.unicodenotsupportedcleanerror'); + } + data.wsfunction = method; data.wstoken = preSets.wstoken; siteurl = preSets.siteurl + '/webservice/rest/server.php?moodlewsrestformat=json'; @@ -133,6 +139,8 @@ angular.module('mm.core') return $q.reject(mmCoreUserNotFullySetup); } else if (data.errorcode === 'sitepolicynotagreed') { return $q.reject(mmCoreSitePolicyNotAgreed); + } else if (data.errorcode === 'dmlwriteexception' && $mmText.hasUnicodeData(ajaxData)) { + return $q.reject(mmCoreUnicodeNotSupported); } else { return $q.reject(data.message); } @@ -280,19 +288,27 @@ angular.module('mm.core') * Converts an objects values to strings where appropriate. * Arrays (associative or otherwise) will be maintained. * - * @param {Object} data The data that needs all the non-object values set to strings. + * @param {Object} data The data that needs all the non-object values set to strings. + * @param {Boolean} stripUnicode If Unicode long chars need to be stripped. * @return {Object} The cleaned object, with multilevel array and objects preserved. */ - function convertValuesToString(data) { + function convertValuesToString(data, stripUnicode) { var result = []; if (!angular.isArray(data) && angular.isObject(data)) { result = {}; } for (var el in data) { if (angular.isObject(data[el])) { - result[el] = convertValuesToString(data[el]); + result[el] = convertValuesToString(data[el], stripUnicode); } else { - result[el] = data[el] + ''; + if (typeof data[el] == "string") { + result[el] = stripUnicode ? $mmText.stripUnicode(data[el]) : data[el]; + if (stripUnicode && data[el] != result[el] && result[el].trim().length == 0) { + throw new Exception(); + } + } else { + result[el] = data[el] + ''; + } } } return result; diff --git a/www/core/main.js b/www/core/main.js index ba02e28836c..658069e33d4 100644 --- a/www/core/main.js +++ b/www/core/main.js @@ -19,6 +19,7 @@ angular.module('mm.core', ['pascalprecht.translate']) .constant('mmCoreUserPasswordChangeForced', 'mmCoreUserPasswordChangeForced') .constant('mmCoreUserNotFullySetup', 'mmCoreUserNotFullySetup') .constant('mmCoreSitePolicyNotAgreed', 'mmCoreSitePolicyNotAgreed') +.constant('mmCoreUnicodeNotSupported', 'mmCoreUnicodeNotSupported') .constant('mmCoreSecondsYear', 31536000) .constant('mmCoreSecondsDay', 86400) .constant('mmCoreSecondsHour', 3600) diff --git a/www/core/scss/styles.scss b/www/core/scss/styles.scss index fb93a038203..4782a602a1a 100644 --- a/www/core/scss/styles.scss +++ b/www/core/scss/styles.scss @@ -1099,6 +1099,7 @@ mm-timer { .loading { padding: 10px 20px; border-radius: 25px; + margin: 0 10px; } }