diff --git a/www/addons/mod_wiki/controllers/index.js b/www/addons/mod_wiki/controllers/index.js
new file mode 100644
index 00000000000..a5faeafaeea
--- /dev/null
+++ b/www/addons/mod_wiki/controllers/index.js
@@ -0,0 +1,479 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+angular.module('mm.addons.mod_wiki')
+
+/**
+ * Wiki index controller.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc controller
+ * @name mmaModWikiIndexCtrl
+ */
+.controller('mmaModWikiIndexCtrl', function($q, $scope, $stateParams, $mmCourse, $mmUser, $mmGroups, $ionicPopover, $mmUtil, $state,
+ $mmSite, $mmaModWiki, $ionicTabsDelegate, $ionicHistory, $translate, mmaModWikiSubwikiPagesLoaded) {
+ var module = $stateParams.module || {},
+ courseId = $stateParams.courseid,
+ action = $stateParams.action || 'page',
+ currentPage = $stateParams.pageid || false,
+ popover, wiki, currentSubwiki, loadedSubwikis,
+ tabsDelegate;
+
+ $scope.title = $stateParams.pagetitle || module.name;
+ $scope.description = module.description;
+ $scope.moduleUrl = module.url;
+ $scope.courseId = courseId;
+ $scope.subwikiData = {
+ selected: 0,
+ subwikis: [],
+ count: 0
+ };
+
+ $scope.tabsDelegateName = 'mmaModWikiTabs_'+(module.id || 0) + '_' + (currentPage || 0) + '_' + new Date().getTime();
+ tabsDelegate = $ionicTabsDelegate.$getByHandle($scope.tabsDelegateName);
+
+ $scope.showSubwikiPicker = function(e) {
+ popover.show(e);
+ };
+
+ $scope.goHomeWiki = function(e) {
+ var backTimes = getHistoryBackCounter();
+ // Go back X times until the wiki home.
+ $ionicHistory.goBack(backTimes);
+ };
+
+ $scope.gotoPage = function(pageId) {
+ if (currentPage != pageId) {
+ // Add a new State.
+ return fetchPageContents(pageId).then(function(page) {
+ var stateParams = {
+ module: module,
+ moduleid: module.id,
+ courseid: courseId,
+ pageid: page.id,
+ pagetitle: page.title,
+ wikiid: page.wikiid,
+ subwikiid: page.subwikiid,
+ action: 'page'
+ };
+ return $state.go('site.mod_wiki', stateParams);
+ });
+ }
+
+ // No changes done.
+ tabsDelegate.select(0);
+ };
+
+ $scope.gotoSubwiki = function(subwikiId) {
+
+ // Check if the subwiki is disabled.
+ if (subwikiId > 0) {
+ popover.hide();
+
+ if (subwikiId != currentSubwiki.id) {
+ // Add a new State.
+ var stateParams = {
+ module: module,
+ moduleid: module.id,
+ courseid: courseId,
+ pageid: null,
+ pagetitle: null,
+ wikiid: wiki.id,
+ subwikiid: subwikiId,
+ action: tabsDelegate.selectedIndex() == 0 ? 'page' : 'map'
+ };
+ return $state.go('site.mod_wiki', stateParams);
+ }
+ }
+ };
+
+ // Convenience function to get wiki data.
+ function fetchWikiData(refresh) {
+ var id = module.id || $stateParams.wikiid,
+ paramName = module.id ? 'coursemodule' : 'id';
+ return $mmaModWiki.getWiki(courseId, id, paramName).then(function(wikiData) {
+ var promise;
+
+ wiki = wikiData;
+ $scope.wiki = wiki;
+
+ $scope.showHomeButton = getHistoryBackCounter() < 0;
+
+ // Get module url if not defined.
+ if (!module.url) {
+ promise = $mmCourse.getModule(wiki.coursemodule, wiki.course, null, true);
+ } else {
+ // This is done to ensure stateparams.module.instance is available when wikiid is not (needed in home button).
+ // This is done only for older Moodle versions that does not send module.instance see MDL-48357.
+ module.instance = wiki.id;
+ promise = $q.when(module);
+ }
+
+ return promise.then(function(mod) {
+ module = mod;
+
+ $scope.title = $scope.title || wiki.title;
+ $scope.description = wiki.intro || module.description;
+ $scope.moduleUrl = module.url;
+
+ // Get real groupmode, in case it's forced by the course.
+ $mmGroups.getActivityGroupMode(wiki.coursemodule).then(function(groupmode) {
+
+ if (groupmode === $mmGroups.SEPARATEGROUPS || groupmode === $mmGroups.VISIBLEGROUPS) {
+ // Get the groups available for the user.
+ promise = $mmGroups.getActivityAllowedGroups(wiki.coursemodule);
+ } else {
+ promise = $q.when([]);
+ }
+
+ return promise.then(function(userGroups) {
+ return fetchSubwikis(wiki.id).then(function() {
+ var subwikiList = $mmaModWiki.getSubwikiList(wiki.id);
+
+ if (!subwikiList) {
+ return createSubwikiList(userGroups);
+ }
+
+ $scope.subwikiData.count = subwikiList.count;
+ $scope.subwikiData.selected = $stateParams.subwikiid || subwikiList.selected;
+ $scope.subwikiData.subwikis = subwikiList.subwikis;
+ return $q.when();
+ });
+ }).then(function() {
+
+ if ($scope.subwikiData.count > 1) {
+ // More than one subwiki available.
+ handleSubwikiPopover();
+ }
+
+ if (!refresh) {
+ tabsDelegate.select(action == 'map' ? 1 : 0);
+ }
+
+ if (!$scope.subwikiData.selected || $scope.subwikiData.count <= 0) {
+ return $q.reject($translate.instant('mma.mod_wiki.errornowikiavailable'));
+ }
+ }).then(function() {
+ return fetchWikiPage();
+ });
+ });
+ });
+ }).catch(function(message) {
+ if (!refresh && !wiki) {
+ // Some call failed, retry without using cache since it might be a new activity.
+ return refreshAllData();
+ }
+
+ if (message) {
+ $mmUtil.showErrorModal(message);
+ } else {
+ $mmUtil.showErrorModal('Error getting wiki data.');
+ }
+ return $q.reject();
+ });
+ }
+
+ // Convinience function that handles Subwiki Popover.
+ function handleSubwikiPopover() {
+ $ionicPopover.fromTemplateUrl('addons/mod_wiki/templates/subwiki_picker.html', {
+ scope: $scope
+ }).then(function(po) {
+ popover = po;
+ });
+ $scope.$on('$destroy', function() {
+ popover.remove();
+ });
+ }
+
+
+ // Create the Subwiki List for the selector
+ function createSubwikiList(userGroups) {
+ var subwikiList = [],
+ promises = [],
+ userGroupsIds = [],
+ allParticipants = false,
+ myGroups = false,
+ multiLevelList = false,
+ currentUserId = $mmSite.getUserId() || false,
+ allParticipantsTitle = $translate.instant('mm.core.allparticipants'),
+ nonInGroupTitle = $translate.instant('mma.mod_wiki.notingroup'),
+ myGroupsTitle = $translate.instant('mm.core.mygroups'),
+ otherGroupsTitle = $translate.instant('mm.core.othergroups');
+
+ $scope.subwikiData.subwikis = [];
+ $scope.subwikiData.selected = false;
+ $scope.subwikiData.count = 0;
+
+ // Group mode available.
+ if (userGroups.length > 0) {
+ userGroupsIds = userGroups.map(function(g) {
+ return g.id;
+ });
+ }
+
+ angular.forEach(loadedSubwikis, function(subwiki) {
+ var groupIdx,
+ promise,
+ groupId = parseInt(subwiki.groupid, 10),
+ groupLabel = "",
+ userId = parseInt(subwiki.userid, 10);
+
+ if (groupId == 0 && userId == 0) {
+ // Add 'All participants' subwiki if needed at the start.
+ if (!allParticipants) {
+ subwikiList.unshift({
+ name: allParticipantsTitle,
+ id: subwiki.id,
+ group: -1,
+ groupLabel: ""
+ });
+ allParticipants = true;
+ }
+ } else {
+ if (groupId != 0 && userGroupsIds.length > 0) {
+ // Get groupLabel if it has groupId.
+ groupIdx = userGroupsIds.indexOf(groupId);
+ if (groupIdx > -1) {
+ groupLabel = userGroups[groupIdx].name;
+ }
+ } else {
+ groupLabel = nonInGroupTitle;
+ }
+
+ if (userId != 0) {
+ // Get user if it has userid.
+ promise = $mmUser.getProfile(userId, null, true).then(function(user) {
+ subwikiList.push({
+ name: user.fullname,
+ id: subwiki.id,
+ group: groupId,
+ groupLabel: groupLabel
+ });
+
+ });
+ promises.push(promise);
+
+ if (!multiLevelList && groupId != 0) {
+ multiLevelList = true;
+ }
+ } else {
+ subwikiList.push({
+ name: groupLabel,
+ id: subwiki.id,
+ group: groupId,
+ groupLabel: groupLabel,
+ canedit: subwiki.canedit
+ });
+ myGroups = true;
+ }
+ }
+
+ // Select always the current user, or the first subwiki if not previously selected
+ if (subwiki.id > 0 && ((userId > 0 && currentUserId == userId) ||
+ !$scope.subwikiData.selected)) {
+ $scope.subwikiData.selected = subwiki.id;
+ }
+ });
+
+ return $q.all(promises).then(function() {
+ var groupValue = -1,
+ grouping;
+
+
+ subwikiList.sort(function(a, b) {
+ return a.group - b.group;
+ });
+
+ $scope.subwikiData.count = subwikiList.length;
+
+ if (multiLevelList) {
+ // As we loop over each subwiki, add it to the current group
+ for (var i in subwikiList) {
+ var subwiki = subwikiList[i];
+
+ // Should we create a new grouping?
+ if (subwiki.group !== groupValue) {
+ grouping = {label: subwiki.groupLabel, subwikis: []};
+ groupValue = subwiki.group;
+
+ $scope.subwikiData.subwikis.push(grouping);
+ }
+
+ // Add the subwiki to the currently active grouping.
+ grouping.subwikis.push(subwiki);
+ }
+ } else if (myGroups) {
+ var noGrouping = {label: "", subwikis: []},
+ myGroupsGrouping = {label: myGroupsTitle, subwikis: []},
+ otherGroupsGrouping = {label: otherGroupsTitle, subwikis: []};
+
+ // As we loop over each subwiki, add it to the current group
+ for (var i in subwikiList) {
+ var subwiki = subwikiList[i];
+
+ // Add the subwiki to the currently active grouping.
+ if (typeof subwiki.canedit == 'undefined') {
+ noGrouping.subwikis.push(subwiki);
+ } else if(subwiki.canedit) {
+ myGroupsGrouping.subwikis.push(subwiki);
+ } else {
+ otherGroupsGrouping.subwikis.push(subwiki);
+ }
+ }
+
+ // Add each grouping to the subwikis
+ if (noGrouping.subwikis.length > 0) {
+ $scope.subwikiData.subwikis.push(noGrouping);
+ }
+ if (myGroupsGrouping.subwikis.length > 0) {
+ $scope.subwikiData.subwikis.push(myGroupsGrouping);
+ }
+ if (otherGroupsGrouping.subwikis.length > 0) {
+ $scope.subwikiData.subwikis.push(otherGroupsGrouping);
+ }
+ } else {
+ $scope.subwikiData.subwikis.push({label: "", subwikis: subwikiList});
+ }
+
+ $mmaModWiki.setSubwikiList(wiki.id, $scope.subwikiData.subwikis, $scope.subwikiData.count, $scope.subwikiData.selected);
+ });
+ }
+
+ // Get number of steps to get the first page of the wiki in the history
+ function getHistoryBackCounter() {
+ var view, historyInstance, backTimes = 0,
+ backViewId = $ionicHistory.currentView().backViewId;
+
+ if (!wiki.id) {
+ return 0;
+ }
+
+ while (backViewId) {
+ view = $ionicHistory.viewHistory().views[backViewId];
+
+ if (view.stateName != 'site.mod_wiki') {
+ break;
+ }
+
+ historyInstance = view.stateParams.wikiid ? view.stateParams.wikiid : view.stateParams.module.instance;
+
+ // Check we are not changing to another Wiki.
+ if (historyInstance && historyInstance == wiki.id) {
+ backTimes--;
+ } else {
+ break;
+ }
+
+ backViewId = view.backViewId;
+ }
+
+ return backTimes;
+ }
+
+ // Convenience function to get wiki options.
+ function fetchSubwikis(wikiId) {
+ return $mmaModWiki.getSubwikis(wikiId).then(function(subwikis) {
+ loadedSubwikis = subwikis;
+ });
+ }
+
+ // Fetch the page to be shown.
+ function fetchWikiPage() {
+ // Search the current Subwiki.
+ currentSubwiki = false;
+ angular.forEach(loadedSubwikis, function(subwiki) {
+ if (!currentSubwiki && subwiki.id == $scope.subwikiData.selected) {
+ currentSubwiki = subwiki;
+ }
+ });
+
+ if (!currentSubwiki) {
+ return $q.reject();
+ }
+
+ $scope.subwikiData.selected = currentSubwiki.id;
+
+ // We need fetchSubwikis to finish before calling fetchSubwikiPages because it needs subwikiid and pageid variable.
+ return fetchSubwikiPages(currentSubwiki).then(function() {
+ return fetchPageContents(currentPage).then(function(pageContents) {
+ $scope.title = pageContents.title;
+ $scope.subwikiData.selected = pageContents.subwikiid;
+ $scope.pageContent = pageContents.cachedcontent;
+ });
+ }).finally(function() {
+ $scope.wikiLoaded = true;
+ });
+ }
+
+ // Convenience function to get wiki subwikiPages.
+ function fetchSubwikiPages(subwiki) {
+ return $mmaModWiki.getSubwikiPages(subwiki.wikiid, subwiki.groupid, subwiki.userid).then(function(subwikiPages) {
+
+ angular.forEach(subwikiPages, function(subwikiPage) {
+ if (!currentPage && subwikiPage.firstpage) {
+ currentPage = subwikiPage.id;
+ }
+ });
+ $scope.subwikiPages = subwikiPages;
+
+ if (!currentPage) {
+ return $q.reject();
+ }
+
+ $scope.$broadcast(mmaModWikiSubwikiPagesLoaded, $scope.subwikiPages);
+ });
+ }
+
+ // Convenience function to get wiki page contents.
+ function fetchPageContents(pageId) {
+ return $mmaModWiki.getPageContents(pageId).then(function(pageContents) {
+ return pageContents;
+ });
+ }
+
+
+ // Convenience function to refresh all the data.
+ function refreshAllData() {
+ var p1 = $mmaModWiki.invalidateWikiData(courseId),
+ p2 = wiki ? $mmaModWiki.invalidateSubwikis(wiki.id) : $q.when(),
+ p3 = currentSubwiki ? $mmaModWiki.invalidateSubwikiPages(currentSubwiki.wikiid) : $q.when(),
+ p4 = currentPage ? $mmaModWiki.invalidatePage(currentPage) : $q.when();
+ p5 = wiki ? $mmGroups.invalidateActivityAllowedGroups(wiki.coursemodule) : $q.when();
+ p6 = wiki ? $mmGroups.invalidateActivityGroupMode(wiki.coursemodule) : $q.when();
+ var ps = [p1, p2, p3, p4, p5, p6];
+
+ return $q.all(ps).finally(function() {
+ return fetchWikiData(true);
+ });
+ }
+
+ fetchWikiData().then(function() {
+ if (!currentPage) {
+ $mmaModWiki.logView(wiki.id).then(function() {
+ $mmCourse.checkModuleCompletion(courseId, module.completionstatus);
+ });
+ } else {
+ $mmaModWiki.logPageView(currentPage);
+ }
+ }).finally(function() {
+ $scope.wikiLoaded = true;
+ });
+
+ // Pull to refresh.
+ $scope.refreshWiki = function() {
+ refreshAllData().finally(function() {
+ $scope.$broadcast('scroll.refreshComplete');
+ });
+ };
+});
diff --git a/www/addons/mod_wiki/controllers/map.js b/www/addons/mod_wiki/controllers/map.js
new file mode 100644
index 00000000000..5f7dc6d3371
--- /dev/null
+++ b/www/addons/mod_wiki/controllers/map.js
@@ -0,0 +1,54 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+angular.module('mm.addons.mod_wiki')
+
+/**
+ * Wiki map controller.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc controller
+ * @name mmaModWikiMapCtrl
+ */
+.controller('mmaModWikiMapCtrl', function($scope, mmaModWikiSubwikiPagesLoaded) {
+ $scope.map = [];
+
+ $scope.constructMap = function(subwikiPages) {
+ var initialLetter = false,
+ letter = {};
+
+ $scope.map = [];
+
+ angular.forEach(subwikiPages, function(page) {
+ // Should we create a new grouping?
+ if (page.title.charAt(0).toLocaleUpperCase() !== initialLetter) {
+ initialLetter = page.title.charAt(0).toLocaleUpperCase();
+ letter = {label: initialLetter, pages: []};
+
+ $scope.map.push(letter);
+ }
+
+ // Add the subwiki to the currently active grouping.
+ letter.pages.push(page);
+ });
+ };
+
+ var obsLoaded = $scope.$on(mmaModWikiSubwikiPagesLoaded, function(event, subwikiPages) {
+ $scope.constructMap(subwikiPages);
+ });
+
+ $scope.constructMap($scope.subwikiPages);
+
+ $scope.$on('$destroy', obsLoaded);
+});
diff --git a/www/addons/mod_wiki/lang/en.json b/www/addons/mod_wiki/lang/en.json
new file mode 100644
index 00000000000..e9011fad026
--- /dev/null
+++ b/www/addons/mod_wiki/lang/en.json
@@ -0,0 +1,9 @@
+{
+ "errorloadingpage": "An error occured while loading page.",
+ "errornowikiavailable": "There's no wiki available to be shown.",
+ "gowikihome": "Go Wiki home",
+ "map": "Map",
+ "notingroup": "Not in group",
+ "subwiki": "Subwiki",
+ "viewpage": "View page"
+}
diff --git a/www/addons/mod_wiki/main.js b/www/addons/mod_wiki/main.js
new file mode 100644
index 00000000000..c11e1610180
--- /dev/null
+++ b/www/addons/mod_wiki/main.js
@@ -0,0 +1,53 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+angular.module('mm.addons.mod_wiki', [])
+
+.constant('mmaModWikiSubwikiPagesLoaded', 'mma_mod_wiki_subwiki_pages_loaded')
+
+.config(function($stateProvider) {
+
+ $stateProvider
+
+ .state('site.mod_wiki', {
+ url: '/mod_wiki',
+ params: {
+ module: null,
+ moduleid: null, // Redundant parameter to fix a problem passing object as parameters. To be fixed in MOBILE-1370.
+ courseid: null,
+ pageid: null,
+ pagetitle: null,
+ wikiid: null,
+ subwikiid: null,
+ action: null
+ },
+ views: {
+ 'site': {
+ controller: 'mmaModWikiIndexCtrl',
+ templateUrl: 'addons/mod_wiki/templates/index.html'
+ }
+ }
+ });
+
+})
+
+.config(function($mmCourseDelegateProvider, $mmContentLinksDelegateProvider) {
+ $mmCourseDelegateProvider.registerContentHandler('mmaModWiki', 'wiki', '$mmaModWikiHandlers.courseContent');
+ $mmContentLinksDelegateProvider.registerLinkHandler('mmaModWiki', '$mmaModWikiHandlers.linksHandler');
+})
+
+.run(function($mmEvents, mmCoreEventLogout, $mmaModWiki) {
+ // Clear cache for SubwikiLists
+ $mmEvents.on(mmCoreEventLogout, $mmaModWiki.clearSubwikiList);
+});
\ No newline at end of file
diff --git a/www/addons/mod_wiki/scss/styles.scss b/www/addons/mod_wiki/scss/styles.scss
new file mode 100644
index 00000000000..8a61f84f83b
--- /dev/null
+++ b/www/addons/mod_wiki/scss/styles.scss
@@ -0,0 +1,55 @@
+$mma-mod-wiki-toc-level-padding: 12px !default;
+$mma-mod-wiki-newentry-link-color: red !default;
+$mma-mod-wiki-toc-title-color: #666 !default;
+$mma-mod-wiki-toc-border-color: #bbb !default;
+$mma-mod-wiki-toc-background-color: #eee !default;
+$mma-mod-wiki-subwiki-selected-background-color: #f7f7f7 !default;
+
+.wiki-toc {
+ border: 1px solid $mma-mod-wiki-toc-border-color;
+ background: $mma-mod-wiki-toc-background-color;
+ margin: 16px;
+ padding: 8px;
+}
+
+.wiki-toc-title {
+ color: $mma-mod-wiki-toc-title-color;
+ font-size: 1.1em;
+ font-variant: small-caps;
+ text-align: center;
+}
+
+.wiki-toc-section {
+ padding: 0;
+ margin: 2px 8px;
+}
+
+.wiki-toc-section-2 {
+ padding-left: $mma-mod-wiki-toc-level-padding;
+}
+
+.wiki-toc-section-3 {
+ padding-left: $mma-mod-wiki-toc-level-padding * 2;
+}
+
+.mm-site_mod_wiki .wiki_newentry {
+ color: $mma-mod-wiki-newentry-link-color;
+ font-style: italic;
+}
+
+/* Hide edit section links */
+a.wiki_edit_section {
+ display: none;
+}
+
+.mma-mod_wiki-subwiki-picker .item-divider {
+ font-weight: bold;
+}
+
+.mma-mod_wiki-subwiki-picker .item.item-selected {
+ background-color: $mma-mod-wiki-subwiki-selected-background-color;
+}
+
+.mma-mod_wiki-subwiki-picker .item.item-selected .icon {
+ font-size: 24px;
+}
\ No newline at end of file
diff --git a/www/addons/mod_wiki/services/handlers.js b/www/addons/mod_wiki/services/handlers.js
new file mode 100644
index 00000000000..07de05d26d9
--- /dev/null
+++ b/www/addons/mod_wiki/services/handlers.js
@@ -0,0 +1,241 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+angular.module('mm.addons.mod_wiki')
+
+/**
+ * Mod wiki handlers.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc service
+ * @name $mmaModWikiHandlers
+ */
+.factory('$mmaModWikiHandlers', function($mmCourse, $mmaModWiki, $state, $mmContentLinksHelper, $mmCourseHelper, $mmUtil, $q) {
+ var self = {};
+
+ /**
+ * Course content handler.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWikiHandlers#courseContent
+ */
+ self.courseContent = function() {
+
+ var self = {};
+
+ /**
+ * Whether or not the module is enabled for the site.
+ *
+ * @return {Boolean}
+ */
+ self.isEnabled = function() {
+ return $mmaModWiki.isPluginEnabled();
+ };
+
+ /**
+ * Get the controller.
+ *
+ * @param {Object} module The module info.
+ * @param {Number} courseId The course ID.
+ * @return {Function}
+ */
+ self.getController = function(module, courseId) {
+ return function($scope) {
+ $scope.title = module.name;
+ $scope.icon = $mmCourse.getModuleIconSrc('wiki');
+ $scope.action = function(e) {
+ $state.go('site.mod_wiki', {module: module, moduleid: module.id, courseid: courseId});
+ };
+ };
+ };
+
+ return self;
+ };
+
+ /**
+ * Content links handler.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWikiHandlers#linksHandler
+ */
+ self.linksHandler = function() {
+
+ var self = {},
+ patterns = ['/mod/wiki/view.php', '/mod/wiki/map.php'];
+
+ /**
+ * Whether or not the handler is enabled for a certain site.
+ *
+ * @param {String} siteId Site ID.
+ * @param {Number} [courseId] Course ID related to the URL.
+ * @return {Promise} Promise resolved with true if enabled.
+ */
+ function isEnabled(siteId, courseId) {
+ return $mmaModWiki.isPluginEnabled(siteId).then(function(enabled) {
+ if (!enabled) {
+ return false;
+ }
+ return courseId || $mmCourse.canGetModuleWithoutCourseId(siteId);
+ });
+ }
+
+ /**
+ * Retrieves page contents
+ * @param {Number} pageId Page ID to be retrieved
+ * @param {String} siteId Site ID page belongs to.
+ * @return {Promise} Promise resolved with the page retrieved.
+ */
+ function getPageContents(pageId, siteId) {
+ return $mmaModWiki.getPageContents(pageId, siteId).then(function(page) {
+ return page;
+ }).catch(function(error) {
+ if (error) {
+ $mmUtil.showErrorModal(error);
+ } else {
+ $mmUtil.showErrorModal('mma.mod_wiki.errorloadingpage', true);
+ }
+ return $q.reject();
+ });
+ }
+
+ /**
+ * Treat a Wiki page with action link.
+ *
+ * @param {String[]} siteIds Site IDs the URL belongs to.
+ * @param {String} url URL to treat.
+ * @param {String} action The action (tab) to go.
+ * @param {Number} [courseId] Course ID related to the URL.
+ * @return {Promise} Promise resolved with the list of actions.
+ */
+ function treatActionLink(siteIds, url, action, courseId) {
+ var params = $mmUtil.extractUrlParams(url);
+ // Pass false because all sites should have the same siteurl.
+ return $mmContentLinksHelper.filterSupportedSites(siteIds, isEnabled, false, courseId).then(function(ids) {
+ if (!ids.length) {
+ return [];
+ }
+
+ // Return actions.
+ return [{
+ message: 'mm.core.view',
+ icon: 'ion-eye',
+ sites: ids,
+ action: function(siteId) {
+ var modal = $mmUtil.showModalLoading();
+ return getPageContents(parseInt(params.pageid, 10), siteId).then(function(page) {
+ var promise;
+ if (courseId) {
+ promise = $q.when(courseId);
+ } else {
+ promise = $mmCourseHelper.getModuleCourseIdByInstance(page.wikiid, 'wiki', siteId);
+ }
+ return promise.then(function(courseId) {
+ var stateParams = {
+ module: null,
+ moduleid: null,
+ courseid: courseId,
+ pageid: page.id,
+ pagetitle: page.title,
+ wikiid: page.wikiid,
+ subwikiid: page.subwikiid,
+ action: action
+ };
+ return $mmContentLinksHelper.goInSite('site.mod_wiki', stateParams, siteIds);
+ });
+ }).finally(function() {
+ modal.dismiss();
+ });
+ }
+ }];
+ });
+ }
+
+ /**
+ * Treat a Wiki page or index link.
+ *
+ * @param {String[]} siteIds Site IDs the URL belongs to.
+ * @param {String} url URL to treat.
+ * @param {Number} [courseId] Course ID related to the URL.
+ * @return {Promise} Promise resolved with the list of actions.
+ */
+ function treatPageLink(siteIds, url, courseId) {
+ // Wiki page or index.
+ var params = $mmUtil.extractUrlParams(url);
+ if (typeof params.pageid != 'undefined') {
+ return treatActionLink(siteIds, url, 'page', courseId);
+ } else {
+ return $mmContentLinksHelper.treatModuleIndexUrl(siteIds, url, isEnabled, courseId);
+ }
+ }
+
+ /**
+ * Treat a Wiki map link.
+ *
+ * @param {String[]} siteIds Site IDs the URL belongs to.
+ * @param {String} url URL to treat.
+ * @param {Number} [courseId] Course ID related to the URL.
+ * @return {Promise} Promise resolved with the list of actions.
+ */
+ function treatMapLink(siteIds, url, courseId) {
+ // Map links.
+ var params = $mmUtil.extractUrlParams(url);
+ if (typeof params.pageid != 'undefined' && (typeof params.option == 'undefined' || params.option == 5)) {
+ return treatActionLink(siteIds, url, 'map', courseId);
+ } else {
+ return $q.when([]);
+ }
+ }
+
+ /**
+ * Get actions to perform with the link.
+ *
+ * @param {String[]} siteIds Site IDs the URL belongs to.
+ * @param {String} url URL to treat.
+ * @param {Number} [courseId] Course ID related to the URL.
+ * @return {Promise} Promise resolved with the list of actions.
+ * See {@link $mmContentLinksDelegate#registerLinkHandler}.
+ */
+ self.getActions = function(siteIds, url, courseId) {
+ if (url.indexOf(patterns[0]) > -1) {
+ // Check it's a wiki URL.
+ return treatPageLink(siteIds, url, courseId);
+ } else if (url.indexOf(patterns[1]) > -1) {
+ // Map URL.
+ return treatMapLink(siteIds, url, courseId);
+ }
+ return $q.when([]);
+ };
+
+ /**
+ * Check if the URL is handled by this handler. If so, returns the URL of the site.
+ *
+ * @param {String} url URL to check.
+ * @return {String} Site URL. Undefined if the URL doesn't belong to this handler.
+ */
+ self.handles = function(url) {
+ for (var i = 0; i < patterns.length; i++) {
+ var position = url.indexOf(patterns[i]);
+ if (position > -1) {
+ return url.substr(0, position);
+ }
+ }
+ };
+
+ return self;
+ };
+
+ return self;
+});
diff --git a/www/addons/mod_wiki/services/wiki.js b/www/addons/mod_wiki/services/wiki.js
new file mode 100644
index 00000000000..0133c533d8d
--- /dev/null
+++ b/www/addons/mod_wiki/services/wiki.js
@@ -0,0 +1,400 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+angular.module('mm.addons.mod_wiki')
+
+/**
+ * Wiki service.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc service
+ * @name $mmaModWiki
+ */
+.factory('$mmaModWiki', function($q, $mmSite, $mmSitesManager) {
+ var self = {},
+ subwikiListsCache = {};
+
+ /**
+ * Get cache key for wiki data WS calls.
+ *
+ * @param {Number} courseId Course ID.
+ * @return {String} Cache key.
+ */
+ function getWikiDataCacheKey(courseId) {
+ return 'mmaModWiki:wiki:' + courseId;
+ }
+
+ /**
+ * Get cache key for wiki SubWikis WS calls.
+ *
+ * @param {Number} wikiId Wiki ID.
+ * @return {String} Cache key.
+ */
+ function getWikiSubwikisCacheKey(wikiId) {
+ return 'mmaModWiki:subwikis:' + wikiId;
+ }
+
+ /**
+ * Get cache key for wiki Subwiki Pages WS calls.
+ *
+ * @param {Number} wikiId Wiki ID.
+ * @param {Number} groupId Group ID.
+ * @param {Number} userId User ID.
+ * @return {String} Cache key.
+ */
+ function getWikiSubwikiPagesCacheKey(wikiId, groupId, userId) {
+ return getWikiSubwikiPagesCacheKeyPrefix(wikiId) + ':' + groupId + ':' + userId;
+ }
+
+ /**
+ * Get cache key for all wiki Subwiki Pages WS calls.
+ *
+ * @param {Number} wikiId Wiki ID.
+ * @return {String} Cache key.
+ */
+ function getWikiSubwikiPagesCacheKeyPrefix(wikiId) {
+ return 'mmaModWiki:subwikipages:' + wikiId;
+ }
+
+ /**
+ * Get cache key for wiki Pages Contents WS calls.
+ *
+ * @param {Number} pageId Wiki Page ID.
+ * @return {String} Cache key.
+ */
+ function getWikiPageCacheKey(pageId) {
+ return 'mmaModWiki:page:' + pageId;
+ }
+
+ /**
+ * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the wiki WS are available.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#isPluginEnabled
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
+ */
+ self.isPluginEnabled = function(siteId) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ return site.wsAvailable('mod_wiki_get_wikis_by_courses') &&
+ site.wsAvailable('mod_wiki_get_subwikis') &&
+ site.wsAvailable('mod_wiki_get_subwiki_pages') &&
+ site.wsAvailable('mod_wiki_get_page_contents');
+ });
+ };
+
+ /**
+ * Get a wiki.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#getWiki
+ * @param {Number} courseId Course ID.
+ * @param {Number} id Wiki ID or cmid to look for.
+ * @param {String} paramName Name of the param id to look for.
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved when the wiki is retrieved.
+ */
+ self.getWiki = function(courseId, id, paramName, siteId) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ var params = {
+ courseids: [courseId]
+ },
+ preSets = {
+ cacheKey: getWikiDataCacheKey(courseId)
+ };
+
+ return site.read('mod_wiki_get_wikis_by_courses', params, preSets).then(function(response) {
+ if (response.wikis) {
+ var currentWiki;
+ angular.forEach(response.wikis, function(wiki) {
+ if (wiki[paramName] == id) {
+ currentWiki = wiki;
+ }
+ });
+ if (currentWiki) {
+ return currentWiki;
+ }
+ }
+ return $q.reject();
+ });
+ });
+ };
+
+ /**
+ * Get Subwiki List for a Wiki from the cache
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#getSubwikiList
+ * @param {Number} wikiId wiki Id
+ * @return {Array} Of subwiki lists
+ */
+ self.getSubwikiList = function(wikiId) {
+ return subwikiListsCache[wikiId];
+ };
+
+ /**
+ * Save Subwiki List for a Wiki to the cache
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#setSubwikiList
+ * @param {Number} wikiId wiki Id
+ * @param {Number} subwikis List of subwikis
+ * @param {Number} count Number of subwikis in the subwikis list
+ * @param {Number} selected subwiki Id currently selected
+ */
+ self.setSubwikiList = function(wikiId, subwikis, count, selected) {
+ var subwikiLists = {
+ count: count,
+ selected: selected,
+ subwikis: subwikis
+ };
+ subwikiListsCache[wikiId] = subwikiLists;
+ };
+
+ /**
+ * Clear Subwiki List for a Wiki from the cache
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#clearSubwikiList
+ * @param {Number} [wikiId] wiki Id, if not provided all will be cleared
+ */
+ self.clearSubwikiList = function(wikiId) {
+ if(typeof wikiId == 'undefined') {
+ subwikiListsCache = {};
+ } else {
+ delete subwikiListsCache[wikiId];
+ }
+
+ };
+
+ /**
+ * Get a wiki Subwikis.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#getSubwikis
+ * @param {Number} wikiId Wiki ID.
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved with wiki subwikis.
+ */
+ self.getSubwikis = function(wikiId, siteId) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ var params = {
+ wikiid: wikiId
+ },
+ preSets = {
+ cacheKey: getWikiSubwikisCacheKey(wikiId)
+ };
+
+ return site.read('mod_wiki_get_subwikis', params, preSets).then(function(response) {
+ if (response.subwikis) {
+ return response.subwikis;
+ }
+ return $q.reject();
+ });
+ });
+ };
+
+ /**
+ * Get the list of Pages of a SubWiki.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#getSubwikiPages
+ * @param {Number} wikiId Wiki ID.
+ * @param {Number} [groupId] to get pages from
+ * @param {Number} [userId] to get pages from
+ * @param {String} [sortBy] the attribute to sort the returned list. Default: title
+ * @param {String} [sortDirection] ASC | DESC direction to sort the returned list. Default: ASC
+ * @param {Boolean} [includeContent] if the pages have to include its content. Default: false.
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved with wiki subwiki pages.
+ */
+ self.getSubwikiPages = function(wikiId, groupId, userId, sortBy, sortDirection, includeContent, siteId) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ groupId = groupId || -1;
+ userId = userId || 0;
+ sortBy = sortBy || 'title';
+ sortDirection = sortDirection || 'ASC';
+ includeContent = includeContent || 0;
+ var params = {
+ wikiid: wikiId,
+ groupid: groupId,
+ userid: userId,
+ options: {
+ sortby: sortBy,
+ sortdirection: sortDirection,
+ includecontent: includeContent
+ }
+
+ },
+ preSets = {
+ cacheKey: getWikiSubwikiPagesCacheKey(wikiId, groupId, userId)
+ };
+
+ return site.read('mod_wiki_get_subwiki_pages', params, preSets).then(function(response) {
+ if (response.pages) {
+ return response.pages;
+ }
+ return $q.reject();
+ });
+ });
+ };
+
+ /**
+ * Get a wiki page contents.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#getPageContents
+ * @param {Number} pageId Page ID.
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved with wiki page contents.
+ */
+ self.getPageContents = function(pageId, siteId) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ var params = {
+ pageid: pageId
+ },
+ preSets = {
+ cacheKey: getWikiPageCacheKey(pageId)
+ };
+
+ return site.read('mod_wiki_get_page_contents', params, preSets).then(function(response) {
+ if (response.page) {
+ return response.page;
+ }
+ return $q.reject();
+ });
+ });
+ };
+
+ /**
+ * Invalidates wiki data.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#invalidateWikiData
+ * @param {Number} courseId Course ID.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ self.invalidateWikiData = function(courseId) {
+ return $mmSite.invalidateWsCacheForKey(getWikiDataCacheKey(courseId));
+ };
+
+ /**
+ * Invalidates Subwikis.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#invalidateSubwikis
+ * @param {Number} wikiId Wiki ID.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ self.invalidateSubwikis = function(wikiId) {
+ self.clearSubwikiList(wikiId);
+ return $mmSite.invalidateWsCacheForKey(getWikiSubwikisCacheKey(wikiId));
+ };
+
+ /**
+ * Invalidates Subwiki Pages.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#invalidateSubwikiPages
+ * @param {Number} wikiId Wiki ID.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ self.invalidateSubwikiPages = function(wikiId) {
+ return $mmSite.invalidateWsCacheForKeyStartingWith(getWikiSubwikiPagesCacheKeyPrefix(wikiId));
+ };
+
+ /**
+ * Invalidates Subwiki Pages.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#invalidatePage
+ * @param {Number} pageId Wiki Page ID.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ self.invalidatePage = function(pageId) {
+ return $mmSite.invalidateWsCacheForKey(getWikiPageCacheKey(pageId));
+ };
+
+ /**
+ * Report the wiki as being viewed.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#logView
+ * @param {String} id Wiki ID.
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved when the WS call is successful.
+ */
+ self.logView = function(id, siteId) {
+ if (id) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ var params = {
+ wikiid: id
+ };
+ return site.write('mod_wiki_view_wiki', params);
+ });
+ }
+ return $q.reject();
+ };
+
+ /**
+ * Report a wiki page as being viewed.
+ *
+ * @module mm.addons.mod_wiki
+ * @ngdoc method
+ * @name $mmaModWiki#logPageView
+ * @param {String} id Page ID.
+ * @param {String} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved when the WS call is successful.
+ */
+ self.logPageView = function(id, siteId) {
+ if (id) {
+ siteId = siteId || $mmSite.getId();
+
+ return $mmSitesManager.getSite(siteId).then(function(site) {
+ var params = {
+ pageid: id
+ };
+ return site.write('mod_wiki_view_page', params);
+ });
+ }
+ return $q.reject();
+ };
+
+ return self;
+});
diff --git a/www/addons/mod_wiki/templates/index.html b/www/addons/mod_wiki/templates/index.html
new file mode 100644
index 00000000000..ecbc5510f51
--- /dev/null
+++ b/www/addons/mod_wiki/templates/index.html
@@ -0,0 +1,18 @@
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/www/addons/mod_wiki/templates/map.html b/www/addons/mod_wiki/templates/map.html
new file mode 100644
index 00000000000..1558e2c8854
--- /dev/null
+++ b/www/addons/mod_wiki/templates/map.html
@@ -0,0 +1,11 @@
+
+
+
+
+
{{ letter.label }}
+
+ {{ page.title }}
+
+
+
+
\ No newline at end of file
diff --git a/www/addons/mod_wiki/templates/page.html b/www/addons/mod_wiki/templates/page.html
new file mode 100644
index 00000000000..5d55c0aeb9c
--- /dev/null
+++ b/www/addons/mod_wiki/templates/page.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+ {{ pageContent }}
+
+
+
\ No newline at end of file
diff --git a/www/addons/mod_wiki/templates/subwiki_picker.html b/www/addons/mod_wiki/templates/subwiki_picker.html
new file mode 100644
index 00000000000..451e80a7608
--- /dev/null
+++ b/www/addons/mod_wiki/templates/subwiki_picker.html
@@ -0,0 +1,12 @@
+
+
+
+
{{ group.label }}
+
+ {{ subwiki.name }}
+
+
+
+
+
\ No newline at end of file
diff --git a/www/core/components/course/services/course.js b/www/core/components/course/services/course.js
index b9194f73901..96ce6612121 100644
--- a/www/core/components/course/services/course.js
+++ b/www/core/components/course/services/course.js
@@ -227,14 +227,19 @@ angular.module('mm.core.course')
* @param {Number} moduleId The module ID.
* @param {Number} [courseId] The course ID. Recommended to speed up the process and minimize data usage.
* @param {Number} [sectionId] The section ID.
+ * @param {Boolean} [preferCache=false] True if shouldn't call WS if data is cached, false otherwise.
* @return {Promise}
*/
- self.getModule = function(moduleId, courseId, sectionId) {
+ self.getModule = function(moduleId, courseId, sectionId, preferCache) {
if (!moduleId) {
return $q.reject();
}
+ if (typeof preferCache == 'undefined') {
+ preferCache = false;
+ }
+
var promise;
if (!courseId) {
@@ -260,7 +265,8 @@ angular.module('mm.core.course')
]
};
preSets = {
- cacheKey: getModuleCacheKey(moduleId)
+ cacheKey: getModuleCacheKey(moduleId),
+ omitExpires: preferCache
};
if (sectionId) {
diff --git a/www/core/lang/en.json b/www/core/lang/en.json
index 80f106652de..c05bdd32b23 100644
--- a/www/core/lang/en.json
+++ b/www/core/lang/en.json
@@ -76,6 +76,7 @@
"mod_url": "URL",
"mod_wiki": "Wiki",
"mod_workshop": "Workshop",
+ "mygroups": "My groups",
"networkerrormsg": "Network not enabled or not working.",
"next": "Next",
"no": "No",
@@ -86,6 +87,7 @@
"online": "Online",
"openfullimage": "Click here to display the image at full size",
"openinbrowser": "Open in browser",
+ "othergroups": "Other groups",
"percentagenumber": "{{$a}}%",
"phone": "Phone",
"pictureof": "Picture of {{$a}}",