Skip to content

Commit 8f529ea

Browse files
committed
MOBILE-1966 glossary: Check duplicate entries
1 parent 85aad77 commit 8f529ea

File tree

6 files changed

+158
-65
lines changed

6 files changed

+158
-65
lines changed

www/addons/mod/glossary/controllers/edit.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ angular.module('mm.addons.mod_glossary')
2222
* @name mmaModGlossaryEditCtrl
2323
*/
2424
.controller('mmaModGlossaryEditCtrl', function($stateParams, $scope, mmaModGlossaryComponent, $mmUtil, $q, $mmaModGlossary, $mmText,
25-
$translate, $ionicHistory, $mmEvents, mmaModGlossaryAddEntryEvent, $mmaModGlossaryOffline, $mmaModGlossaryHelper,
25+
$translate, $ionicHistory, $mmEvents, mmaModGlossaryAddEntryEvent, $mmaModGlossaryOffline, $mmaModGlossaryHelper, $mmLang,
2626
$mmFileUploaderHelper) {
2727

2828
var module = $stateParams.module,
@@ -31,7 +31,8 @@ angular.module('mm.addons.mod_glossary')
3131
glossaryId = $stateParams.glossaryid,
3232
glossary = $stateParams.glossary || {},
3333
originalData = null,
34-
entry = $stateParams.entry || false;
34+
entry = $stateParams.entry || false,
35+
allowDuplicateEntries = !!glossary.allowduplicatedentries;
3536

3637
$scope.entry = {
3738
concept: '',
@@ -71,7 +72,7 @@ angular.module('mm.addons.mod_glossary')
7172

7273
// Treat offline attachments if any.
7374
if (entry.attachments && entry.attachments.offline) {
74-
$mmaModGlossaryHelper.getStoredFiles(glossaryId, entry.concept).then(function(files) {
75+
$mmaModGlossaryHelper.getStoredFiles(glossaryId, entry.concept, entry.timecreated).then(function(files) {
7576
$scope.attachments = files;
7677
originalData.files = angular.copy(files);
7778
});
@@ -121,6 +122,7 @@ angular.module('mm.addons.mod_glossary')
121122
definition = $scope.entry.text,
122123
modal,
123124
attachments,
125+
timecreated = entry && entry.timecreated || Date.now(),
124126
saveOffline = false;
125127

126128
if (!concept || !definition) {
@@ -137,9 +139,9 @@ angular.module('mm.addons.mod_glossary')
137139
definition = $mmText.formatHtmlLines(definition);
138140
}
139141

140-
// If editing an offline entry and concept is different, delete previous first.
141-
if (entry.concept && entry.concept != concept) {
142-
return $mmaModGlossaryOffline.deleteAddEntry(glossaryId, entry.concept);
142+
// If editing an offline entry, delete previous first.
143+
if (entry) {
144+
return $mmaModGlossaryOffline.deleteAddEntry(glossaryId, entry.concept, entry.timecreated);
143145
}
144146
return $q.when();
145147

@@ -148,11 +150,11 @@ angular.module('mm.addons.mod_glossary')
148150

149151
// Upload attachments first if any.
150152
if (attachments.length) {
151-
return $mmaModGlossaryHelper.uploadOrStoreFiles(glossaryId, concept, attachments, false)
153+
return $mmaModGlossaryHelper.uploadOrStoreFiles(glossaryId, concept, timecreated, attachments, false)
152154
.catch(function() {
153155
// Cannot upload them in online, save them in offline.
154156
saveOffline = true;
155-
return $mmaModGlossaryHelper.uploadOrStoreFiles(glossaryId, concept, attachments, true);
157+
return $mmaModGlossaryHelper.uploadOrStoreFiles(glossaryId, concept, timecreated, attachments, true);
156158
});
157159
}
158160
}).then(function(attach) {
@@ -176,6 +178,16 @@ angular.module('mm.addons.mod_glossary')
176178
}
177179

178180
if (saveOffline) {
181+
if (entry && !allowDuplicateEntries) {
182+
// Check if the entry is duplicated in online or offline mode.
183+
promise = $mmaModGlossary.isConceptUsed(glossaryId, concept).then(function() {
184+
// There's a page with same name, reject with error message.
185+
return $mmLang.translateAndReject('mma.mod_glossary.errconceptalreadyexists');
186+
}, function() {
187+
// Not found, page can be sent.
188+
});
189+
}
190+
179191
// Save entry in offline.
180192
return $mmaModGlossaryOffline.saveAddEntry(glossaryId, concept, definition, courseId, options, attach).then(function() {
181193
// Don't return anything.
@@ -184,13 +196,13 @@ angular.module('mm.addons.mod_glossary')
184196
// Try to send it to server.
185197
// Don't allow offline if there are attachments since they were uploaded fine.
186198
return $mmaModGlossary.addEntry(glossaryId, concept, definition, courseId, options, attach, undefined,
187-
!attachments.length);
199+
entry.timecreated, !attachments.length);
188200
}
189201
}).then(function(entryId) {
190202
if (entryId) {
191203
$scope.entry.id = entryId;
192204
// Data sent to server, delete stored files (if any).
193-
$mmaModGlossaryHelper.deleteStoredFiles(glossaryId, concept);
205+
$mmaModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated);
194206
}
195207
$scope.entry.glossaryid = glossaryId;
196208
$scope.entry.definition = definition;

www/addons/mod/glossary/lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"definition": "Definition",
1717
"entriestobesynced": "Entries to be synced",
1818
"entrypendingapproval": "This entry is pending approval.",
19+
"errconceptalreadyexists": "This concept already exists. No duplicates allowed in this glossary.",
1920
"errorloadingentries": "An error occured while loading entries.",
2021
"errorloadingentry": "An error occured while loading the entry.",
2122
"errorloadingglossary": "An error occured while loading the glossary.",

www/addons/mod/glossary/services/glossary.js

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ angular.module('mm.addons.mod_glossary')
2222
* @name $mmaModGlossary
2323
*/
2424
.factory('$mmaModGlossary', function($mmSite, $q, $mmSitesManager, $mmFilepool, mmaModGlossaryComponent, $mmaModGlossaryOffline,
25-
mmaModGlossaryLimitEntriesNum, $mmApp, $mmUtil, mmaModGlossaryLimitCategoriesNum, $mmText,
25+
mmaModGlossaryLimitEntriesNum, $mmApp, $mmUtil, mmaModGlossaryLimitCategoriesNum, $mmText, $mmLang,
2626
mmaModGlossaryShowAllCategories) {
2727
var self = {};
2828

@@ -747,19 +747,23 @@ angular.module('mm.addons.mod_glossary')
747747
* @param {Array} [options] Array of options for the entry.
748748
* @param {Mixed} [attach] Attachments ID if sending online, result of $mmFileUploader#storeFilesToUpload otherwise.
749749
* @param {String} [siteId] Site ID. If not defined, current site.
750+
* @param {Number} [timecreated] The time the entry was created. Only used when editing entries.
750751
* @param {Boolean} allowOffline True if it can be stored in offline, false otherwise.
751752
* @return {Promise} Promise resolved with entry ID if entry was created in server, false if stored in device.
752753
*/
753-
self.addEntry = function(glossaryId, concept, definition, courseId, options, attach, siteId, allowOffline) {
754+
self.addEntry = function(glossaryId, concept, definition, courseId, options, attach, siteId, timecreated, allowOffline) {
754755
siteId = siteId || $mmSite.getId();
755756

756757
if (!$mmApp.isOnline() && allowOffline) {
757758
// App is offline, store the action.
758759
return storeOffline();
759760
}
760761

761-
// Discard stored content for this entry. If it exists it means the user is editing it.
762-
return $mmaModGlossaryOffline.deleteAddEntry(glossaryId, concept, siteId).then(function() {
762+
// If we are editing an offline entry, discard previous first.
763+
var discardPromise = timecreated ?
764+
$mmaModGlossaryOffline.deleteAddEntry(glossaryId, concept, timecreated, siteId) : $q.when();
765+
766+
return discardPromise.then(function() {
763767
// Try to add it in online.
764768
return self.addEntryOnline(glossaryId, concept, definition, options, attach, siteId).then(function(entryId) {
765769
return entryId;
@@ -776,9 +780,16 @@ angular.module('mm.addons.mod_glossary')
776780

777781
// Convenience function to store a new page to be synchronized later.
778782
function storeOffline() {
779-
return $mmaModGlossaryOffline.saveAddEntry(glossaryId, concept, definition, courseId, options, attach,
780-
siteId).then(function() {
781-
return false;
783+
// Check if the entry is duplicated in online or offline mode.
784+
return self.isConceptUsed(glossaryId, concept, siteId).then(function(used) {
785+
if (used) {
786+
return $mmLang.translateAndReject('mma.mod_glossary.errconceptalreadyexists');
787+
}
788+
789+
return $mmaModGlossaryOffline.saveAddEntry(glossaryId, concept, definition, courseId, options, attach,
790+
siteId).then(function() {
791+
return false;
792+
});
782793
});
783794
}
784795
};
@@ -847,6 +858,40 @@ angular.module('mm.addons.mod_glossary')
847858
}
848859
};
849860

861+
/**
862+
* Check if a entry concept is already used.
863+
*
864+
* @param {Number} glossaryId Glossary ID.
865+
* @param {String} concept Concept to check.
866+
* @param {String} [siteId] Site ID. If not defined, current site.
867+
* @return {Promise} Promise resolved with true if used, resolved with false if not used or error.
868+
*/
869+
self.isConceptUsed = function(glossaryId, concept, siteId) {
870+
// Check offline first.
871+
return $mmaModGlossaryOffline.isConceptUsed(glossaryId, concept, siteId).then(function(exists) {
872+
if (exists) {
873+
return true;
874+
}
875+
876+
// If we get here, there's no offline entry with this name, check online.
877+
// Get entries from the cache.
878+
return self.fetchAllEntries(self.getEntriesByLetter, [glossaryId, 'ALL'], true, undefined, undefined, siteId)
879+
.then(function(entries) {
880+
// Check if there's any entry with the same concept.
881+
for (var i = 0, len = entries.length; i < len; i++) {
882+
if (entries[i].concept == concept) {
883+
return true;
884+
}
885+
}
886+
887+
return false;
888+
});
889+
}).catch(function() {
890+
// Error, assume not used.
891+
return false;
892+
});
893+
};
894+
850895
/**
851896
* Check if glossary plugin is enabled in a certain site.
852897
*

www/addons/mod/glossary/services/glossary_offline.js

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ angular.module('mm.addons.mod_glossary')
2020
var stores = [
2121
{
2222
name: mmaModGlossaryAddEntryStore,
23-
keyPath: ['glossaryid', 'concept'],
23+
keyPath: ['glossaryid', 'concept', 'timecreated'],
2424
indexes: [
2525
{
2626
name: 'glossaryid'
@@ -31,6 +31,10 @@ angular.module('mm.addons.mod_glossary')
3131
{
3232
name: 'userid'
3333
},
34+
{
35+
name: 'glossaryAndConcept',
36+
keyPath: ['glossaryid', 'concept']
37+
},
3438
{
3539
name: 'glossaryAndUser',
3640
keyPath: ['glossaryid', 'userid']
@@ -59,14 +63,15 @@ angular.module('mm.addons.mod_glossary')
5963
* @module mm.addons.mod_glossary
6064
* @ngdoc method
6165
* @name $mmaModGlossaryOffline#deleteAddEntry
62-
* @param {Number} glossaryId Glossary ID.
63-
* @param {String} concept Glossary entry concept.
64-
* @param {String} [siteId] Site ID. If not defined, current site.
65-
* @return {Promise} Promise resolved if deleted, rejected if failure.
66+
* @param {Number} glossaryId Glossary ID.
67+
* @param {String} concept Glossary entry concept.
68+
* @param {Number} timecreated Time to allow duplicated entries.
69+
* @param {String} [siteId] Site ID. If not defined, current site.
70+
* @return {Promise} Promise resolved if deleted, rejected if failure.
6671
*/
67-
self.deleteAddEntry = function(glossaryId, concept, siteId) {
72+
self.deleteAddEntry = function(glossaryId, concept, timecreated, siteId) {
6873
return $mmSitesManager.getSite(siteId).then(function(site) {
69-
return site.getDb().remove(mmaModGlossaryAddEntryStore, [glossaryId, concept]);
74+
return site.getDb().remove(mmaModGlossaryAddEntryStore, [glossaryId, concept, timecreated]);
7075
});
7176
};
7277

@@ -91,14 +96,15 @@ angular.module('mm.addons.mod_glossary')
9196
* @module mm.addons.mod_glossary
9297
* @ngdoc method
9398
* @name $mmaModGlossaryOffline#getAddEntry
94-
* @param {Number} glossaryId Glossary ID.
95-
* @param {String} concept Glossary entry concept.
96-
* @param {String} [siteId] Site ID. If not defined, current site.
97-
* @return {Promise} Promise resolved with page.
99+
* @param {Number} glossaryId Glossary ID.
100+
* @param {String} concept Glossary entry concept.
101+
* @param {Number} timecreated Time to allow duplicated entries.
102+
* @param {String} [siteId] Site ID. If not defined, current site.
103+
* @return {Promise} Promise resolved with page.
98104
*/
99-
self.getAddEntry = function(glossaryId, concept, siteId) {
105+
self.getAddEntry = function(glossaryId, concept, timecreated, siteId) {
100106
return $mmSitesManager.getSite(siteId).then(function(site) {
101-
return site.getDb().get(mmaModGlossaryAddEntryStore, [glossaryId, concept]);
107+
return site.getDb().get(mmaModGlossaryAddEntryStore, [glossaryId, concept, timecreated]);
102108
});
103109
};
104110

@@ -120,6 +126,29 @@ angular.module('mm.addons.mod_glossary')
120126
});
121127
};
122128

129+
/**
130+
* Check if a concept is used offline.
131+
*
132+
* @module mm.addons.mod_glossary
133+
* @ngdoc method
134+
* @name $mmaModGlossaryOffline#isConceptUsed
135+
* @param {Number} glossaryId Glossary ID.
136+
* @param {String} concept Concept to check.
137+
* @param {String} [siteId] Site ID. If not defined, current site.
138+
* @return {Promise} Promise resolved with true if concept is found, false otherwise.
139+
*/
140+
self.isConceptUsed = function(glossaryId, concept, siteId) {
141+
return $mmSitesManager.getSite(siteId).then(function(site) {
142+
return site.getDb().whereEqual(mmaModGlossaryAddEntryStore, 'glossaryAndConcept', [glossaryId, concept])
143+
.then(function(entries) {
144+
return !!entries.length;
145+
});
146+
}).catch(function() {
147+
// No offline data found, return false.
148+
return false;
149+
});
150+
};
151+
123152

124153
/**
125154
* Save an add entry data to be sent later.
@@ -188,12 +217,13 @@ angular.module('mm.addons.mod_glossary')
188217
* @name $mmaModGlossaryOffline#getEntryFolder
189218
* @param {Number} glossaryId Glossary ID.
190219
* @param {Number} entryName The name of the entry.
220+
* @param {Number} timecreated Time to allow duplicated entries.
191221
* @param {String} [siteId] Site ID. If not defined, current site.
192222
* @return {Promise} Promise resolved with the path.
193223
*/
194-
self.getEntryFolder = function(glossaryId, entryName, siteId) {
224+
self.getEntryFolder = function(glossaryId, entryName, timecreated, siteId) {
195225
return self.getGlossaryFolder(glossaryId, siteId).then(function(folderPath) {
196-
return $mmFS.concatenatePaths(folderPath, 'newentry_' + entryName);
226+
return $mmFS.concatenatePaths(folderPath, 'newentry_' + entryName + '_' + timecreated);
197227
});
198228
};
199229

www/addons/mod/glossary/services/glossary_sync.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@ angular.module('mm.addons.mod_glossary')
183183
promises.push(promise.then(function() {
184184
result.updated = true;
185185

186-
return deleteAddEntry(glossaryId, data.concept, siteId);
186+
return deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
187187
}).catch(function(error) {
188188
if (error && error.wserror) {
189189
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
190190
result.updated = true;
191-
return deleteAddEntry(glossaryId, data.concept, siteId).then(function() {
191+
return deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId).then(function() {
192192
// Responses deleted, add a warning.
193193
result.warnings.push($translate.instant('mm.core.warningofflinedatadeleted', {
194194
component: $mmCourse.translateModuleName('glossary'),
@@ -229,16 +229,17 @@ angular.module('mm.addons.mod_glossary')
229229
/**
230230
* Delete a new entry.
231231
*
232-
* @param {Number} glossaryId Glossary ID.
233-
* @param {String} concept Glossary entry concept.
234-
* @param {String} [siteId] Site ID. If not defined, current site.
235-
* @return {Promise} Promise resolved when deleted.
232+
* @param {Number} glossaryId Glossary ID.
233+
* @param {String} concept Glossary entry concept.
234+
* @param {Number} timecreated Time to allow duplicated entries.
235+
* @param {String} [siteId] Site ID. If not defined, current site.
236+
* @return {Promise} Promise resolved when deleted.
236237
*/
237-
function deleteAddEntry(glossaryId, concept, siteId) {
238+
function deleteAddEntry(glossaryId, concept, timecreated, siteId) {
238239
var promises = [];
239240

240-
promises.push($mmaModGlossaryOffline.deleteAddEntry(glossaryId, concept, siteId));
241-
promises.push($mmaModGlossaryHelper.deleteStoredFiles(glossaryId, concept, siteId).catch(function() {
241+
promises.push($mmaModGlossaryOffline.deleteAddEntry(glossaryId, concept, timecreated, siteId));
242+
promises.push($mmaModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated, siteId).catch(function() {
242243
// Ignore errors, maybe there are no files.
243244
}));
244245

@@ -262,7 +263,7 @@ angular.module('mm.addons.mod_glossary')
262263

263264
if (attachments.offline) {
264265
// Has offline files.
265-
promise = $mmaModGlossaryHelper.getStoredFiles(glossaryId, entry.concept, siteId).then(function(atts) {
266+
promise = $mmaModGlossaryHelper.getStoredFiles(glossaryId, entry.concept, entry.timecreated, siteId).then(function(atts) {
266267
files = files.concat(atts);
267268
}).catch(function() {
268269
// Folder not found, no files to add.

0 commit comments

Comments
 (0)