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;
}
}