diff --git a/scripts/langindex.json b/scripts/langindex.json index 390e3fa12..6e9b7a740 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -56,6 +56,16 @@ "addon.block_timeline.pluginname": "block_timeline", "addon.block_timeline.sortbycourses": "block_timeline", "addon.block_timeline.sortbydates": "block_timeline", + "addon.blog.blog": "blog", + "addon.blog.blogentries": "blog", + "addon.blog.errorloadentries": "local_moodlemobileapp", + "addon.blog.linktooriginalentry": "blog", + "addon.blog.noentriesyet": "blog", + "addon.blog.publishtonoone": "blog", + "addon.blog.publishtosite": "blog", + "addon.blog.publishtoworld": "blog", + "addon.blog.showonlyyourentries": "local_moodlemobileapp", + "addon.blog.siteblogheading": "blog", "addon.calendar.calendar": "calendar", "addon.calendar.calendarevents": "local_moodlemobileapp", "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", diff --git a/src/addon/blog/blog.module.ts b/src/addon/blog/blog.module.ts new file mode 100644 index 000000000..275a16873 --- /dev/null +++ b/src/addon/blog/blog.module.ts @@ -0,0 +1,46 @@ +// (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. + +import { NgModule } from '@angular/core'; +import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; +import { CoreUserDelegate } from '@core/user/providers/user-delegate'; +import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; +import { AddonBlogProvider } from './providers/blog'; +import { AddonBlogMainMenuHandler } from './providers/mainmenu-handler'; +import { AddonBlogUserHandler } from './providers/user-handler'; +import { AddonBlogCourseOptionHandler } from './providers/course-option-handler'; +import { AddonBlogComponentsModule } from './components/components.module'; + +@NgModule({ + declarations: [ + ], + imports: [ + AddonBlogComponentsModule + ], + providers: [ + AddonBlogProvider, + AddonBlogMainMenuHandler, + AddonBlogUserHandler, + AddonBlogCourseOptionHandler + ] +}) +export class AddonBlogModule { + constructor(mainMenuDelegate: CoreMainMenuDelegate, menuHandler: AddonBlogMainMenuHandler, + userHandler: AddonBlogUserHandler, userDelegate: CoreUserDelegate, + courseOptionHandler: AddonBlogCourseOptionHandler, courseOptionsDelegate: CoreCourseOptionsDelegate) { + mainMenuDelegate.registerHandler(menuHandler); + userDelegate.registerHandler(userHandler); + courseOptionsDelegate.registerHandler(courseOptionHandler); + } +} diff --git a/src/addon/blog/components/components.module.ts b/src/addon/blog/components/components.module.ts new file mode 100644 index 000000000..0e56fcc3f --- /dev/null +++ b/src/addon/blog/components/components.module.ts @@ -0,0 +1,47 @@ +// (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. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreCommentsComponentsModule } from '@core/comments/components/components.module'; +import { AddonBlogEntriesComponent } from './entries/entries'; + +@NgModule({ + declarations: [ + AddonBlogEntriesComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + CoreCommentsComponentsModule + ], + providers: [ + ], + exports: [ + AddonBlogEntriesComponent + ], + entryComponents: [ + AddonBlogEntriesComponent + ] +}) +export class AddonBlogComponentsModule {} diff --git a/src/addon/blog/components/entries/addon-blog-entries.html b/src/addon/blog/components/entries/addon-blog-entries.html new file mode 100644 index 000000000..9312a00cd --- /dev/null +++ b/src/addon/blog/components/entries/addon-blog-entries.html @@ -0,0 +1,49 @@ + + + + + +
+ + {{ 'addon.blog.showonlyyourentries' | translate }} + + +
+ + + + + +

+ + + {{ 'addon.blog.' + entry.publishTranslated | translate}} + +

+

+ + {{entry.created | coreDateDayOrTime}} + + {{entry.user && entry.user.fullname}} +

+
+ + + + + + + {{ 'addon.blog.linktooriginalentry' | translate }} + + + + + {{entry.lastmodified | coreTimeAgo}} + + + +
+
+ +
+
diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts new file mode 100644 index 000000000..f946264a8 --- /dev/null +++ b/src/addon/blog/components/entries/entries.ts @@ -0,0 +1,163 @@ +// (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. + +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Content } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { AddonBlogProvider } from '../../providers/blog'; + +/** + * Component that displays the blog entries. + */ +@Component({ + selector: 'addon-blog-entries', + templateUrl: 'addon-blog-entries.html', +}) +export class AddonBlogEntriesComponent implements OnInit { + @Input() userId?: number; + @Input() courseId?: number; + @Input() cmId?: number; + + protected filter = {}; + protected pageLoaded = 0; + + @ViewChild(Content) content: Content; + + loaded = false; + canLoadMore = false; + loadMoreError = false; + entries = []; + currentUserId: number; + showMyIssuesToggle = false; + onlyMyEntries = false; + component = AddonBlogProvider.COMPONENT; + + constructor(protected blogProvider: AddonBlogProvider, protected domUtils: CoreDomUtilsProvider, + protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider) { + this.currentUserId = sitesProvider.getCurrentSiteUserId(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.userId) { + this.filter['userid'] = this.userId; + } + + if (this.courseId) { + this.filter['courseid'] = this.courseId; + } + + if (this.cmId) { + this.filter['cmid'] = this.cmId; + } + + this.fetchEntries().then(() => { + this.blogProvider.logView(this.filter).catch(() => { + // Ignore errors. + }); + }); + } + + /** + * Fetch blog entries. + * + * @param {boolean} [refresh] Empty events array first. + * @return {Promise} Promise with the entries. + */ + private fetchEntries(refresh: boolean = false): Promise { + this.loadMoreError = false; + + if (refresh) { + this.pageLoaded = 0; + } + + return this.blogProvider.getEntries(this.filter, this.pageLoaded).then((result) => { + const promises = result.entries.map((entry) => { + switch (entry.publishstate) { + case 'draft': + entry.publishTranslated = 'publishtonoone'; + break; + case 'site': + entry.publishTranslated = 'publishtosite'; + break; + case 'public': + entry.publishTranslated = 'publishtoworld'; + break; + default: + entry.publishTranslated = 'privacy:unknown'; + break; + } + + return this.userProvider.getProfile(entry.userid, entry.courseid, true).then((user) => { + entry.user = user; + }).catch(() => { + // Ignore errors. + }); + }); + + if (refresh) { + this.showMyIssuesToggle = false; + this.entries = result.entries; + } else { + this.entries = this.entries.concat(result.entries); + } + + this.canLoadMore = result.totalentries > this.entries.length; + this.pageLoaded++; + + this.showMyIssuesToggle = !this.userId && (this.showMyIssuesToggle || this.entries.some((entry) => { + return entry.userid == this.currentUserId; + })); + + return Promise.all(promises); + }).catch((message) => { + this.domUtils.showErrorModalDefault(message, 'addon.blog.errorloadentries', true); + this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Function to load more entries. + * + * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @return {Promise} Resolved when done. + */ + loadMore(infiniteComplete?: any): Promise { + return this.fetchEntries().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Refresh blog entries on PTR. + * + * @param {any} refresher Refresher instance. + */ + refresh(refresher?: any): void { + this.blogProvider.invalidateEntries(this.filter).finally(() => { + this.fetchEntries(true).finally(() => { + if (refresher) { + refresher.complete(); + } + }); + }); + } + +} diff --git a/src/addon/blog/lang/en.json b/src/addon/blog/lang/en.json new file mode 100644 index 000000000..6e183232f --- /dev/null +++ b/src/addon/blog/lang/en.json @@ -0,0 +1,12 @@ +{ + "blog": "Blog", + "blogentries": "Blog entries", + "errorloadentries": "Error loading blog entries.", + "linktooriginalentry": "Link to original blog entry", + "noentriesyet": "No visible entries here", + "publishtonoone": "Yourself (draft)", + "publishtosite": "Anyone on this site", + "publishtoworld": "Anyone in the world", + "showonlyyourentries": "Show only your entries", + "siteblogheading": "Site blog" +} \ No newline at end of file diff --git a/src/addon/blog/pages/entries/entries.html b/src/addon/blog/pages/entries/entries.html new file mode 100644 index 000000000..ff5f1b240 --- /dev/null +++ b/src/addon/blog/pages/entries/entries.html @@ -0,0 +1,7 @@ + + + {{ title | translate }} + + + + diff --git a/src/addon/blog/pages/entries/entries.module.ts b/src/addon/blog/pages/entries/entries.module.ts new file mode 100644 index 000000000..a9cb5564f --- /dev/null +++ b/src/addon/blog/pages/entries/entries.module.ts @@ -0,0 +1,33 @@ +// (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. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonBlogEntriesPage } from './entries'; +import { AddonBlogComponentsModule } from '../../components/components.module'; + +@NgModule({ + declarations: [ + AddonBlogEntriesPage, + ], + imports: [ + CoreDirectivesModule, + AddonBlogComponentsModule, + IonicPageModule.forChild(AddonBlogEntriesPage), + TranslateModule.forChild() + ], +}) +export class AddonBlogEntriesPageModule {} diff --git a/src/addon/blog/pages/entries/entries.ts b/src/addon/blog/pages/entries/entries.ts new file mode 100644 index 000000000..1a268268f --- /dev/null +++ b/src/addon/blog/pages/entries/entries.ts @@ -0,0 +1,43 @@ +// (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. + +import { Component } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; + +/** + * Page that displays the list of blog entries. + */ +@IonicPage({ segment: 'addon-blog-entries' }) +@Component({ + selector: 'page-addon-blog-entries', + templateUrl: 'entries.html', +}) +export class AddonBlogEntriesPage { + userId: number; + courseId: number; + cmId: number; + title: string; + + constructor(params: NavParams) { + this.userId = params.get('userId'); + this.courseId = params.get('courseId'); + this.cmId = params.get('cmId'); + + if (!this.userId && !this.courseId && !this.cmId) { + this.title = 'addon.blog.siteblogheading'; + } else { + this.title = 'addon.blog.blogentries'; + } + } +} diff --git a/src/addon/blog/providers/blog.ts b/src/addon/blog/providers/blog.ts new file mode 100644 index 000000000..adc15b1f2 --- /dev/null +++ b/src/addon/blog/providers/blog.ts @@ -0,0 +1,113 @@ +// (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. + +import { Injectable } from '@angular/core'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Service to handle blog entries. + */ +@Injectable() +export class AddonBlogProvider { + static ENTRIES_PER_PAGE = 10; + static COMPONENT = 'blog'; + protected ROOT_CACHE_KEY = 'addonBlog:'; + protected logger; + + constructor(logger: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider) { + this.logger = logger.getInstance('AddonBlogProvider'); + } + + /** + * Returns whether or not the blog plugin is enabled for a certain site. + * + * This method is called quite often and thus should only perform a quick + * check, we should not be calling WS from here. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if enabled, resolved with false or rejected otherwise. + */ + isPluginEnabled(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('core_blog_get_entries') && + site.canUseAdvancedFeature('enableblogs'); + }); + } + + /** + * Get the cache key for the blog entries. + * + * @param {any} [filter] Filter to apply on search. + * @return {string} Cache key. + */ + getEntriesCacheKey(filter: any = {}): string { + return this.ROOT_CACHE_KEY + this.utils.sortAndStringify(filter); + } + + /** + * Get blog entries. + * + * @param {any} [filter] Filter to apply on search. + * @param {any} [page=0] Page of the blog entries to fetch. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the entries are retrieved. + */ + getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const data = { + filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'), + page: page, + perpage: AddonBlogProvider.ENTRIES_PER_PAGE + }; + + const preSets = { + cacheKey: this.getEntriesCacheKey(filter) + }; + + return site.read('core_blog_get_entries', data, preSets); + }); + } + + /** + * Invalidate blog entries WS call. + * + * @param {any} [filter] Filter to apply on search + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when data is invalidated. + */ + invalidateEntries(filter: any = {}, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getEntriesCacheKey(filter)); + }); + } + + /** + * Trigger the blog_entries_viewed event. + * + * @param {any} [filter] Filter to apply on search. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when done. + */ + logView(filter: any = {}, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const data = { + filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value') + }; + + return site.write('core_blog_view_entries', data); + }); + } +} diff --git a/src/addon/blog/providers/course-option-handler.ts b/src/addon/blog/providers/course-option-handler.ts new file mode 100644 index 000000000..9c28973fb --- /dev/null +++ b/src/addon/blog/providers/course-option-handler.ts @@ -0,0 +1,120 @@ +// (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. + +import { Injectable, Injector } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '@core/course/providers/options-delegate'; +import { CoreCoursesProvider } from '@core/courses/providers/courses'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonBlogEntriesComponent } from '../components/entries/entries'; +import { AddonBlogProvider } from './blog'; + +/** + * Course nav handler. + */ +@Injectable() +export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { + name = 'AddonBlog'; + priority = 100; + + constructor(protected coursesProvider: CoreCoursesProvider, protected blogProvider: AddonBlogProvider, + protected courseHelper: CoreCourseHelperProvider, protected courseProvider: CoreCourseProvider, + protected sitesProvider: CoreSitesProvider, protected filepoolProvider: CoreFilepoolProvider) {} + + /** + * Should invalidate the data to determine if the handler is enabled for a certain course. + * + * @param {number} courseId The course ID. + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {Promise} Promise resolved when done. + */ + invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise { + return this.courseProvider.invalidateCourseBlocks(courseId); + } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Whether or not the handler is enabled for a certain course. + * + * @param {number} courseId The course ID. + * @param {any} accessData Access type and data. Default, guest, ... + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { + return this.courseHelper.hasABlockNamed(courseId, 'blog_menu').then((enabled) => { + if (enabled && navOptions && typeof navOptions.blogs != 'undefined') { + return navOptions.blogs; + } + + return enabled; + }); + } + + /** + * Returns the data needed to render the handler. + * + * @param {Injector} injector Injector. + * @param {number} courseId The course ID. + * @return {CoreCourseOptionsHandlerData|Promise} Data or promise resolved with the data. + */ + getDisplayData(injector: Injector, courseId: number): CoreCourseOptionsHandlerData | Promise { + return { + title: 'addon.blog.blog', + class: 'addon-blog-handler', + component: AddonBlogEntriesComponent + }; + } + + /** + * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. + * + * @param {any} course The course. + * @return {Promise} Promise resolved when done. + */ + prefetch(course: any): Promise { + const siteId = this.sitesProvider.getCurrentSiteId(); + + return this.blogProvider.getEntries({courseid: course.id}).then((result) => { + return result.entries.map((entry) => { + let files = []; + + if (entry.attachmentfiles && entry.attachmentfiles.length) { + files = entry.attachmentfiles; + } + if (entry.summaryfiles && entry.summaryfiles.length) { + files = files.concat(entry.summaryfiles); + } + + if (files.length > 0) { + return this.filepoolProvider.addFilesToQueue(siteId, files, entry.module, entry.id); + } + + return Promise.resolve(); + }); + }); + } +} diff --git a/src/addon/blog/providers/mainmenu-handler.ts b/src/addon/blog/providers/mainmenu-handler.ts new file mode 100644 index 000000000..e45bfd06f --- /dev/null +++ b/src/addon/blog/providers/mainmenu-handler.ts @@ -0,0 +1,51 @@ +// (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. + +import { Injectable } from '@angular/core'; +import { AddonBlogProvider } from './blog'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable() +export class AddonBlogMainMenuHandler implements CoreMainMenuHandler { + name = 'AddonBlog'; + priority = 450; + + constructor(private blogProvider: AddonBlogProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreMainMenuHandlerData} Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + return { + icon: 'fa-newspaper-o', + title: 'addon.blog.siteblogheading', + page: 'AddonBlogEntriesPage', + class: 'addon-blog-handler' + }; + } +} diff --git a/src/addon/blog/providers/user-handler.ts b/src/addon/blog/providers/user-handler.ts new file mode 100644 index 000000000..039b9ed56 --- /dev/null +++ b/src/addon/blog/providers/user-handler.ts @@ -0,0 +1,71 @@ +// (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. + +import { Injectable } from '@angular/core'; +import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonBlogProvider } from './blog'; + +/** + * Profile item handler. + */ +@Injectable() +export class AddonBlogUserHandler implements CoreUserProfileHandler { + name = 'AddonBlog:blogs'; + priority = 300; + type = CoreUserDelegate.TYPE_NEW_PAGE; + + constructor(protected linkHelper: CoreContentLinksHelperProvider, protected blogProvider: AddonBlogProvider) { + } + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Check if handler is enabled for this user in this context. + * + * @param {any} user User to check. + * @param {number} courseId Course ID. + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + */ + isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { + return true; + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreUserProfileHandlerData} Data needed to render the handler. + */ + getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { + return { + icon: 'fa-newspaper-o', + title: 'addon.blog.blogentries', + class: 'addon-blog-handler', + action: (event, navCtrl, user, courseId): void => { + event.preventDefault(); + event.stopPropagation(); + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonBlogEntriesPage', { userId: user.id, courseId: courseId }); + } + }; + } +} diff --git a/src/addon/mod/assign/components/index/addon-mod-assign-index.html b/src/addon/mod/assign/components/index/addon-mod-assign-index.html index fd9fd8488..96a7e5865 100644 --- a/src/addon/mod/assign/components/index/addon-mod-assign-index.html +++ b/src/addon/mod/assign/components/index/addon-mod-assign-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/book/components/index/addon-mod-book-index.html b/src/addon/mod/book/components/index/addon-mod-book-index.html index 32c49d009..51fa95154 100644 --- a/src/addon/mod/book/components/index/addon-mod-book-index.html +++ b/src/addon/mod/book/components/index/addon-mod-book-index.html @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/chat/components/index/addon-mod-chat-index.html b/src/addon/mod/chat/components/index/addon-mod-chat-index.html index 43432d53b..b2f0d53c3 100644 --- a/src/addon/mod/chat/components/index/addon-mod-chat-index.html +++ b/src/addon/mod/chat/components/index/addon-mod-chat-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/chat/components/index/index.ts b/src/addon/mod/chat/components/index/index.ts index 34f3bc248..55d60961f 100644 --- a/src/addon/mod/chat/components/index/index.ts +++ b/src/addon/mod/chat/components/index/index.ts @@ -35,7 +35,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp protected title: string; constructor(injector: Injector, private chatProvider: AddonModChatProvider, private timeUtils: CoreTimeUtilsProvider, - private navCtrl: NavController) { + protected navCtrl: NavController) { super(injector); } diff --git a/src/addon/mod/choice/components/index/addon-mod-choice-index.html b/src/addon/mod/choice/components/index/addon-mod-choice-index.html index 58eeaefe7..59a85ab7b 100644 --- a/src/addon/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addon/mod/choice/components/index/addon-mod-choice-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/data/components/index/addon-mod-data-index.html b/src/addon/mod/data/components/index/addon-mod-data-index.html index 94b658e30..62d8c38a0 100644 --- a/src/addon/mod/data/components/index/addon-mod-data-index.html +++ b/src/addon/mod/data/components/index/addon-mod-data-index.html @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html index 10df6623c..9fef582e3 100644 --- a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html +++ b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/feedback/components/index/index.ts b/src/addon/mod/feedback/components/index/index.ts index 1eb116187..6914e4b03 100644 --- a/src/addon/mod/feedback/components/index/index.ts +++ b/src/addon/mod/feedback/components/index/index.ts @@ -70,7 +70,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity constructor(injector: Injector, private feedbackProvider: AddonModFeedbackProvider, @Optional() content: Content, private feedbackOffline: AddonModFeedbackOfflineProvider, private groupsProvider: CoreGroupsProvider, - private feedbackSync: AddonModFeedbackSyncProvider, private navCtrl: NavController, + private feedbackSync: AddonModFeedbackSyncProvider, protected navCtrl: NavController, private feedbackHelper: AddonModFeedbackHelperProvider, private timeUtils: CoreTimeUtilsProvider) { super(injector, content); diff --git a/src/addon/mod/folder/components/index/addon-mod-folder-index.html b/src/addon/mod/folder/components/index/addon-mod-folder-index.html index 48bc258e1..f9ccdcf31 100644 --- a/src/addon/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addon/mod/folder/components/index/addon-mod-folder-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/forum/components/index/addon-mod-forum-index.html b/src/addon/mod/forum/components/index/addon-mod-forum-index.html index bcbce5865..e4d9b273d 100644 --- a/src/addon/mod/forum/components/index/addon-mod-forum-index.html +++ b/src/addon/mod/forum/components/index/addon-mod-forum-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html index f57a4fd31..2b748a568 100644 --- a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html +++ b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html index 3bd7cdebd..eab430fd2 100644 --- a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html index a394f589b..61a19ce3c 100644 --- a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/lti/components/index/addon-mod-lti-index.html b/src/addon/mod/lti/components/index/addon-mod-lti-index.html index 049830e41..492303fa3 100644 --- a/src/addon/mod/lti/components/index/addon-mod-lti-index.html +++ b/src/addon/mod/lti/components/index/addon-mod-lti-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/page/components/index/addon-mod-page-index.html b/src/addon/mod/page/components/index/addon-mod-page-index.html index 3919124c4..8c2c78a40 100644 --- a/src/addon/mod/page/components/index/addon-mod-page-index.html +++ b/src/addon/mod/page/components/index/addon-mod-page-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html index 6316b64cc..5113cd105 100644 --- a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html +++ b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/resource/components/index/addon-mod-resource-index.html b/src/addon/mod/resource/components/index/addon-mod-resource-index.html index 5b4289d28..7a964f220 100644 --- a/src/addon/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addon/mod/resource/components/index/addon-mod-resource-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html index fdd0eee67..947e4be78 100644 --- a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/survey/components/index/addon-mod-survey-index.html b/src/addon/mod/survey/components/index/addon-mod-survey-index.html index 4ad777c3a..01645b24b 100644 --- a/src/addon/mod/survey/components/index/addon-mod-survey-index.html +++ b/src/addon/mod/survey/components/index/addon-mod-survey-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/url/components/index/addon-mod-url-index.html b/src/addon/mod/url/components/index/addon-mod-url-index.html index 422095419..bf2d57265 100644 --- a/src/addon/mod/url/components/index/addon-mod-url-index.html +++ b/src/addon/mod/url/components/index/addon-mod-url-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html index 976ae64c9..0a8a6c5b1 100644 --- a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html @@ -13,6 +13,7 @@ + diff --git a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html index bd3b2b12f..f778b5f19 100644 --- a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html +++ b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/workshop/components/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index 419ef4d4a..496039184 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -67,7 +67,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity constructor(injector: Injector, private workshopProvider: AddonModWorkshopProvider, @Optional() content: Content, private workshopOffline: AddonModWorkshopOfflineProvider, private groupsProvider: CoreGroupsProvider, - private navCtrl: NavController, private modalCtrl: ModalController, private utils: CoreUtilsProvider, + protected navCtrl: NavController, private modalCtrl: ModalController, private utils: CoreUtilsProvider, platform: Platform, private workshopHelper: AddonModWorkshopHelperProvider, private workshopSync: AddonModWorkshopSyncProvider) { super(injector, content); diff --git a/src/addon/notes/providers/user-handler.ts b/src/addon/notes/providers/user-handler.ts index a5a409ded..a348ffb13 100644 --- a/src/addon/notes/providers/user-handler.ts +++ b/src/addon/notes/providers/user-handler.ts @@ -79,7 +79,7 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { return this.noteEnabledCache[courseId]; } - return this.notesProvider.isPluginAddNoteEnabledForCourse(courseId).then((enabled) => { + return this.notesProvider.isPluginViewNotesEnabledForCourse(courseId).then((enabled) => { this.noteEnabledCache[courseId] = enabled; return enabled; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 83003549f..551e7a880 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -81,6 +81,7 @@ import { CoreBlockModule } from '@core/block/block.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; +import { AddonBlogModule } from '@addon/blog/blog.module'; import { AddonCalendarModule } from '@addon/calendar/calendar.module'; import { AddonCompetencyModule } from '@addon/competency/competency.module'; import { AddonCourseCompletionModule } from '@addon/coursecompletion/coursecompletion.module'; @@ -198,6 +199,7 @@ export const CORE_PROVIDERS: any[] = [ CoreCommentsModule, CoreBlockModule, AddonBadgesModule, + AddonBlogModule, AddonCalendarModule, AddonCompetencyModule, AddonCourseCompletionModule, diff --git a/src/app/app.scss b/src/app/app.scss index 04e9a7c77..5fe61c0fa 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -990,7 +990,9 @@ details summary { pointer-events: auto; } -.icon.fa-graduation-cap{ +.icon.fa-graduation-cap, +.item > .icon.fa, +.item-inner > .icon.fa { font-size: 21px; width: 21px; line-height: 28px; diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index f440f6e3a..a82101823 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -56,6 +56,16 @@ "addon.block_timeline.pluginname": "Timeline", "addon.block_timeline.sortbycourses": "Sort by courses", "addon.block_timeline.sortbydates": "Sort by dates", + "addon.blog.blog": "Blog", + "addon.blog.blogentries": "Blog entries", + "addon.blog.errorloadentries": "Error loading blog entries.", + "addon.blog.linktooriginalentry": "Link to original blog entry", + "addon.blog.noentriesyet": "No visible entries here", + "addon.blog.publishtonoone": "Yourself (draft)", + "addon.blog.publishtosite": "Anyone on this site", + "addon.blog.publishtoworld": "Anyone in the world", + "addon.blog.showonlyyourentries": "Show only your entries", + "addon.blog.siteblogheading": "Site blog", "addon.calendar.calendar": "Calendar", "addon.calendar.calendarevents": "Calendar events", "addon.calendar.defaultnotificationtime": "Default notification time", diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index cf60439d7..8bc986812 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -13,6 +13,7 @@ // limitations under the License. import { OnInit, OnDestroy, Input, Output, EventEmitter, Injector } from '@angular/core'; +import { NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; @@ -20,6 +21,8 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCourseModuleMainComponent, CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreCourseSectionPage } from '@core/course/pages/section/section.ts'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonBlogProvider } from '@addon/blog/providers/blog'; /** * Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing). @@ -32,6 +35,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, loaded: boolean; // If the component has been loaded. component: string; // Component name. componentId: number; // Component ID. + blog: boolean; // If blog is avalaible. // Data for context menu. externalUrl: string; // External URL to open in browser. @@ -54,6 +58,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, protected domUtils: CoreDomUtilsProvider; protected moduleDelegate: CoreCourseModuleDelegate; protected courseSectionPage: CoreCourseSectionPage; + protected linkHelper: CoreContentLinksHelperProvider; + protected navCtrl: NavController; + protected blogProvider: AddonBlogProvider; protected logger; @@ -64,6 +71,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.domUtils = injector.get(CoreDomUtilsProvider); this.moduleDelegate = injector.get(CoreCourseModuleDelegate); this.courseSectionPage = injector.get(CoreCourseSectionPage, null); + this.linkHelper = injector.get(CoreContentLinksHelperProvider); + this.navCtrl = injector.get(NavController, null); + this.blogProvider = injector.get(AddonBlogProvider, null); this.dataRetrieved = new EventEmitter(); const loggerProvider = injector.get(CoreLoggerProvider); @@ -79,6 +89,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.externalUrl = this.module.url; this.loaded = false; this.refreshIcon = 'spinner'; + this.blogProvider.isPluginEnabled().then((enabled) => { + this.blog = enabled; + }); } /** @@ -216,6 +229,16 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id); } + /** + * Go to blog posts. + * + * @param {any} event Event. + */ + gotoBlog(event: any): void { + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(this.navCtrl, 'AddonBlogEntriesPage', { cmId: this.module.id }); + } + /** * Prefetch the module. */ diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 8e22ab42f..6f05e157f 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -753,6 +753,25 @@ export class CoreCourseHelperProvider { }); } + /** + * Check if the course has a block with that name. + * + * @param {number} courseId Course ID. + * @param {string} name Block name to search. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if the block exists or false otherwise. + * @since 3.3 + */ + hasABlockNamed(courseId: number, name: string, siteId?: string): Promise { + return this.courseProvider.getCourseBlocks(courseId, siteId).then((blocks) => { + return blocks.some((block) => { + return block.name == name; + }); + }).catch(() => { + return false; + }); + } + /** * Initialize the prefetch icon for selected courses. *