commit
9fc9b166a9
|
@ -64,6 +64,7 @@ jobs:
|
||||||
"@addon_block_timeline"
|
"@addon_block_timeline"
|
||||||
"@addon_calendar"
|
"@addon_calendar"
|
||||||
"@addon_competency"
|
"@addon_competency"
|
||||||
|
"@addon_coursecompletion"
|
||||||
"@addon_messages"
|
"@addon_messages"
|
||||||
"@addon_mod_assign"
|
"@addon_mod_assign"
|
||||||
"@addon_mod_bigbluebuttonbn"
|
"@addon_mod_bigbluebuttonbn"
|
||||||
|
@ -93,6 +94,7 @@ jobs:
|
||||||
"@core_search"
|
"@core_search"
|
||||||
"@core_settings"
|
"@core_settings"
|
||||||
"@core_siteplugins"
|
"@core_siteplugins"
|
||||||
|
"@core_tag"
|
||||||
"@core_user"
|
"@core_user"
|
||||||
"@core_usertour"
|
"@core_usertour"
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { AddonBlockBlogMenuComponent } from '../components/blogmenu/blogmenu';
|
import { AddonBlockBlogMenuComponent } from '../components/blogmenu/blogmenu';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '@addons/blog/services/blog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -28,6 +29,13 @@ export class AddonBlockBlogMenuHandlerService extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockBlogMenu';
|
name = 'AddonBlockBlogMenu';
|
||||||
blockName = 'blog_menu';
|
blockName = 'blog_menu';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return await AddonBlog.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* Returns the data needed to render the block.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { AddonBlockBlogRecentComponent } from '../components/blogrecent/blogrecent';
|
import { AddonBlockBlogRecentComponent } from '../components/blogrecent/blogrecent';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '@addons/blog/services/blog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -28,6 +29,13 @@ export class AddonBlockBlogRecentHandlerService extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockBlogRecent';
|
name = 'AddonBlockBlogRecent';
|
||||||
blockName = 'blog_recent';
|
blockName = 'blog_recent';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return await AddonBlog.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* Returns the data needed to render the block.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { AddonBlockBlogTagsComponent } from '../components/blogtags/blogtags';
|
import { AddonBlockBlogTagsComponent } from '../components/blogtags/blogtags';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreTag } from '@features/tag/services/tag';
|
||||||
|
import { AddonBlog } from '@addons/blog/services/blog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -28,6 +30,16 @@ export class AddonBlockBlogTagsHandlerService extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockBlogTags';
|
name = 'AddonBlockBlogTags';
|
||||||
blockName = 'blog_tags';
|
blockName = 'blog_tags';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
const tags = await CoreTag.areTagsAvailable();
|
||||||
|
const blogs = await AddonBlog.isPluginEnabled();
|
||||||
|
|
||||||
|
return blogs && tags;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* Returns the data needed to render the block.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-tit
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { CoreCourseBlock } from '@features/course/services/course';
|
import { CoreCourseBlock } from '@features/course/services/course';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreComments } from '@features/comments/services/comments';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -28,6 +29,13 @@ export class AddonBlockCommentsHandlerService extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockComments';
|
name = 'AddonBlockComments';
|
||||||
blockName = 'comments';
|
blockName = 'comments';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return await CoreComments.areCommentsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* Returns the data needed to render the block.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-tit
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { CoreCourseBlock } from '@features/course/services/course';
|
import { CoreCourseBlock } from '@features/course/services/course';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -31,7 +32,27 @@ export class AddonBlockCompletionStatusHandlerService extends CoreBlockBaseHandl
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return AddonCourseCompletion.isCompletionEnabledInSite();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getDisplayData(
|
||||||
|
block: CoreCourseBlock,
|
||||||
|
contextLevel: string,
|
||||||
|
instanceId: number,
|
||||||
|
): Promise<undefined | CoreBlockHandlerData> {
|
||||||
|
if (contextLevel !== 'course') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseEnabled = await AddonCourseCompletion.isPluginViewEnabledForCourse(instanceId);
|
||||||
|
if (!courseEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'addon.block_completionstatus.pluginname',
|
title: 'addon.block_completionstatus.pluginname',
|
||||||
class: 'addon-block-completion-status',
|
class: 'addon-block-completion-status',
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-tit
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { CoreCourseBlock } from '@features/course/services/course';
|
import { CoreCourseBlock } from '@features/course/services/course';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -31,7 +32,27 @@ export class AddonBlockSelfCompletionHandlerService extends CoreBlockBaseHandler
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return AddonCourseCompletion.isCompletionEnabledInSite();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getDisplayData(
|
||||||
|
block: CoreCourseBlock,
|
||||||
|
contextLevel: string,
|
||||||
|
instanceId: number,
|
||||||
|
): Promise<undefined | CoreBlockHandlerData> {
|
||||||
|
if (contextLevel !== 'course') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseEnabled = await AddonCourseCompletion.isPluginViewEnabledForCourse(instanceId);
|
||||||
|
if (!courseEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'addon.block_selfcompletion.pluginname',
|
title: 'addon.block_selfcompletion.pluginname',
|
||||||
class: 'addon-block-self-completion',
|
class: 'addon-block-self-completion',
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
|
||||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
import { AddonBlockTagsComponent } from '../components/tags/tags';
|
import { AddonBlockTagsComponent } from '../components/tags/tags';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreTag } from '@features/tag/services/tag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -28,6 +29,13 @@ export class AddonBlockTagsHandlerService extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockTags';
|
name = 'AddonBlockTags';
|
||||||
blockName = 'tags';
|
blockName = 'tags';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return await CoreTag.areTagsAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* Returns the data needed to render the block.
|
||||||
*
|
*
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
import { CoreCourseIndexRoutingModule } from '@features/course/course-routing.module';
|
import { CoreCourseIndexRoutingModule } from '@features/course/course-routing.module';
|
||||||
|
@ -22,17 +22,12 @@ import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-ro
|
||||||
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
|
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
|
||||||
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
|
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
|
||||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
import { AddonBlogProvider } from './services/blog';
|
|
||||||
import { AddonBlogCourseOptionHandler } from './services/handlers/course-option';
|
import { AddonBlogCourseOptionHandler } from './services/handlers/course-option';
|
||||||
import { AddonBlogIndexLinkHandler } from './services/handlers/index-link';
|
import { AddonBlogIndexLinkHandler } from './services/handlers/index-link';
|
||||||
import { AddonBlogMainMenuHandler, AddonBlogMainMenuHandlerService } from './services/handlers/mainmenu';
|
import { AddonBlogMainMenuHandler, AddonBlogMainMenuHandlerService } from './services/handlers/mainmenu';
|
||||||
import { AddonBlogTagAreaHandler } from './services/handlers/tag-area';
|
import { AddonBlogTagAreaHandler } from './services/handlers/tag-area';
|
||||||
import { AddonBlogUserHandler } from './services/handlers/user';
|
import { AddonBlogUserHandler } from './services/handlers/user';
|
||||||
|
|
||||||
export const ADDON_BLOG_SERVICES: Type<unknown>[] = [
|
|
||||||
AddonBlogProvider,
|
|
||||||
];
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: AddonBlogMainMenuHandlerService.PAGE_NAME,
|
path: AddonBlogMainMenuHandlerService.PAGE_NAME,
|
||||||
|
|
|
@ -139,7 +139,7 @@ export class AddonBlogEntriesPage implements OnInit {
|
||||||
this.contextInstanceId = 0;
|
this.contextInstanceId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
this.commentsEnabled = CoreComments.areCommentsEnabledInSite();
|
||||||
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
||||||
|
|
||||||
const deepLinkManager = new CoreMainMenuDeepLinkManager();
|
const deepLinkManager = new CoreMainMenuDeepLinkManager();
|
||||||
|
|
|
@ -19,14 +19,22 @@ import { CoreCourseIndexRoutingModule } from '@features/course/course-routing.mo
|
||||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
import { AddonCourseCompletionProvider } from './services/coursecompletion';
|
|
||||||
import { AddonCourseCompletionStatusLinkHandler } from './services/handlers/completionstatus-link';
|
import { AddonCourseCompletionStatusLinkHandler } from './services/handlers/completionstatus-link';
|
||||||
import { AddonCourseCompletionCourseOptionHandler } from './services/handlers/course-option';
|
import { AddonCourseCompletionCourseOptionHandler } from './services/handlers/course-option';
|
||||||
import { AddonCourseCompletionUserHandler } from './services/handlers/user';
|
import { AddonCourseCompletionUserHandler } from './services/handlers/user';
|
||||||
|
|
||||||
export const ADDON_COURSECOMPLETION_SERVICES: Type<unknown>[] = [
|
/**
|
||||||
AddonCourseCompletionProvider,
|
* Get course completion services.
|
||||||
];
|
*
|
||||||
|
* @returns Course completion services.
|
||||||
|
*/
|
||||||
|
export async function getCourseCompletionServices(): Promise<Type<unknown>[]> {
|
||||||
|
const { AddonCourseCompletionProvider } = await import('@addons/coursecompletion/services/coursecompletion');
|
||||||
|
|
||||||
|
return [
|
||||||
|
AddonCourseCompletionProvider,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCourses } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
|
||||||
import { CoreSite } from '@classes/sites/site';
|
import { CoreSite } from '@classes/sites/site';
|
||||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@services/ws';
|
import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@services/ws';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
@ -40,6 +40,44 @@ export class AddonCourseCompletionProvider {
|
||||||
this.logger = CoreLogger.getInstance('AddonCourseCompletion');
|
this.logger = CoreLogger.getInstance('AddonCourseCompletion');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether completion is available in a certain site.
|
||||||
|
*
|
||||||
|
* @param site Site. If not defined, use current site.
|
||||||
|
* @returns True if available.
|
||||||
|
*/
|
||||||
|
isCompletionEnabledInSite(site?: CoreSite): boolean {
|
||||||
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
|
||||||
|
return !!site && site.canUseAdvancedFeature('enablecompletion');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether completion is available in a certain course.
|
||||||
|
*
|
||||||
|
* @param course Course.
|
||||||
|
* @param site Site. If not defined, use current site.
|
||||||
|
* @returns True if available.
|
||||||
|
*/
|
||||||
|
isCompletionEnabledInCourse(course: CoreCourseAnyCourseData, site?: CoreSite): boolean {
|
||||||
|
if (!this.isCompletionEnabledInSite(site)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isCompletionEnabledInCourseObject(course);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether completion is enabled in a certain course object.
|
||||||
|
*
|
||||||
|
* @param course Course object.
|
||||||
|
* @returns True if completion is enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
protected isCompletionEnabledInCourseObject(course: CoreCourseAnyCourseData): boolean {
|
||||||
|
// Undefined means it's not supported, so it's enabled by default.
|
||||||
|
return course.enablecompletion !== false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the user can mark a course as self completed.
|
* Returns whether or not the user can mark a course as self completed.
|
||||||
* It can if it's configured in the course and it hasn't been completed yet.
|
* It can if it's configured in the course and it hasn't been completed yet.
|
||||||
|
@ -180,7 +218,7 @@ export class AddonCourseCompletionProvider {
|
||||||
* @returns True if plugin enabled, false otherwise.
|
* @returns True if plugin enabled, false otherwise.
|
||||||
*/
|
*/
|
||||||
isPluginViewEnabled(): boolean {
|
isPluginViewEnabled(): boolean {
|
||||||
return CoreSites.isLoggedIn();
|
return CoreSites.isLoggedIn() && this.isCompletionEnabledInSite();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,26 +228,19 @@ export class AddonCourseCompletionProvider {
|
||||||
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
|
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
|
||||||
* @returns Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
* @returns Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
||||||
*/
|
*/
|
||||||
async isPluginViewEnabledForCourse(courseId?: number, preferCache: boolean = true): Promise<boolean> {
|
async isPluginViewEnabledForCourse(courseId?: number, preferCache = true): Promise<boolean> {
|
||||||
if (!courseId) {
|
if (!courseId || !this.isCompletionEnabledInSite()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const course = await CoreCourses.getUserCourse(courseId, preferCache);
|
const course = await CoreCourses.getUserCourse(courseId, preferCache);
|
||||||
|
|
||||||
if (course) {
|
if (!course) {
|
||||||
if (course.enablecompletion !== undefined && !course.enablecompletion) {
|
return true;
|
||||||
// Completion not enabled for the course.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (course.completionhascriteria !== undefined && !course.completionhascriteria) {
|
|
||||||
// No criteria, cannot view completion.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// Check completion is enabled in the course and it has criteria, to view completion.
|
||||||
|
return this.isCompletionEnabledInCourseObject(course) && course.completionhascriteria !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
@addon_coursecompletion @app @javascript
|
||||||
|
Feature: Course completion navigation
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username | firstname | lastname | email | idnumber |
|
||||||
|
| teacher1 | Teacher | 1 | teacher1@example.com | T1 |
|
||||||
|
| student1 | Student | 1 | student1@example.com | S1 |
|
||||||
|
And the following "courses" exist:
|
||||||
|
| fullname | shortname | category | enablecompletion | showcompletionconditions |
|
||||||
|
| Course 1 | C1 | 0 | 1 | 1 |
|
||||||
|
| Course 2 | C2 | 0 | | |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| teacher1 | C1 | editingteacher |
|
||||||
|
| teacher1 | C2 | editingteacher |
|
||||||
|
| student1 | C1 | student |
|
||||||
|
| student1 | C2 | student |
|
||||||
|
And the following "activity" exists:
|
||||||
|
| activity | assign |
|
||||||
|
| course | C1 |
|
||||||
|
| name | Test assignment name |
|
||||||
|
| assignsubmission_onlinetext_enabled | 1 |
|
||||||
|
| grade[modgrade_type] | Point |
|
||||||
|
| grade[modgrade_point] | 100 |
|
||||||
|
| gradepass | 70 |
|
||||||
|
| completion | 2 |
|
||||||
|
| completionusegrade | 1 |
|
||||||
|
| completionpassgrade | 1 |
|
||||||
|
And the following "activity" exists:
|
||||||
|
| activity | page |
|
||||||
|
| course | C2 |
|
||||||
|
| name | P1 |
|
||||||
|
And I enable "selfcompletion" "block" plugin
|
||||||
|
And the following "blocks" exist:
|
||||||
|
| blockname | contextlevel | reference |
|
||||||
|
| completionstatus | Course | C1 |
|
||||||
|
| selfcompletion | Course | C1 |
|
||||||
|
| activity_modules | Course | C1 |
|
||||||
|
| completionstatus | Course | C2 |
|
||||||
|
| selfcompletion | Course | C2 |
|
||||||
|
| activity_modules | Course | C2 |
|
||||||
|
And the following config values are set as admin:
|
||||||
|
| enablecompletion | 1 |
|
||||||
|
And I am on the "Course 1" course page logged in as teacher1
|
||||||
|
And I navigate to "Course completion" in current page administration
|
||||||
|
And I click on "Condition: Activity completion" "link"
|
||||||
|
And I set the field "Assignment - Test assignment name" to "1"
|
||||||
|
And I expand all fieldsets
|
||||||
|
And I set the following fields to these values:
|
||||||
|
| id_criteria_self | 1 |
|
||||||
|
And I press "Save changes"
|
||||||
|
|
||||||
|
Scenario: Completion is available only when enabled for the course
|
||||||
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
|
When I press "Open block drawer" in the app
|
||||||
|
Then I should find "Course completion status" in the app
|
||||||
|
And I should find "Self completion" in the app
|
||||||
|
When I press "Close" in the app
|
||||||
|
And I press "Completion" in the app
|
||||||
|
Then I should find "Status" in the app
|
||||||
|
|
||||||
|
Given I entered the course "Course 2" as "student1" in the app
|
||||||
|
When I press "Open block drawer" in the app
|
||||||
|
Then I should not find "Course completion status" in the app
|
||||||
|
And I should not find "Self completion" in the app
|
||||||
|
When I press "Close" in the app
|
||||||
|
Then I should not find "Completion" in the app
|
||||||
|
|
||||||
|
Given the following config values are set as admin:
|
||||||
|
| enablecompletion | 0 |
|
||||||
|
Then I entered the course "Course 1" as "student1" in the app
|
||||||
|
And I pull to refresh in the app
|
||||||
|
When I press "Open block drawer" in the app
|
||||||
|
Then I should not find "Course completion status" in the app
|
||||||
|
And I should not find "Self completion" in the app
|
||||||
|
When I press "Close" in the app
|
||||||
|
Then I should not find "Completion" in the app
|
|
@ -33,7 +33,7 @@ export class AddonModAssignSubmissionCommentsComponent extends AddonModAssignSub
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
this.commentsEnabled = CoreComments.areCommentsEnabledInSite();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -157,7 +157,7 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
this.commentsEnabled = CoreComments.areCommentsEnabledInSite();
|
||||||
|
|
||||||
await this.fetchEntryData();
|
await this.fetchEntryData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet
|
||||||
// Prefetch the database data.
|
// Prefetch the database data.
|
||||||
const database = info.database;
|
const database = info.database;
|
||||||
|
|
||||||
const commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
const commentsEnabled = CoreComments.areCommentsEnabledInSite();
|
||||||
|
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ Feature: Users can manage entries in database activities
|
||||||
| student1 | C1 | student |
|
| student1 | C1 | student |
|
||||||
| student2 | C1 | student |
|
| student2 | C1 | student |
|
||||||
And the following "activities" exist:
|
And the following "activities" exist:
|
||||||
| activity | name | intro | course | idnumber |
|
| activity | name | intro | course | idnumber | comments |
|
||||||
| data | Web links | Useful links | C1 | data1 |
|
| data | Web links | Useful links | C1 | data1 | 0 |
|
||||||
And the following "mod_data > fields" exist:
|
And the following "mod_data > fields" exist:
|
||||||
| database | type | name | description |
|
| database | type | name | description |
|
||||||
| data1 | text | URL | URL link |
|
| data1 | text | URL | URL link |
|
||||||
|
@ -38,7 +38,13 @@ Feature: Users can manage entries in database activities
|
||||||
And I should find "Moodle community site" in the app
|
And I should find "Moodle community site" in the app
|
||||||
|
|
||||||
Scenario: Browse entry
|
Scenario: Browse entry
|
||||||
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
Given the following "activities" exist:
|
||||||
|
| activity | name | intro | course | idnumber | comments |
|
||||||
|
| data | Data with comments | - | C1 | data2 | 1 |
|
||||||
|
And the following "mod_data > fields" exist:
|
||||||
|
| database | type | name | description |
|
||||||
|
| data2 | text | Description | Description |
|
||||||
|
And I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
||||||
|
|
||||||
# TODO Create and use a generator for database entries.
|
# TODO Create and use a generator for database entries.
|
||||||
When I press "Add entries" in the app
|
When I press "Add entries" in the app
|
||||||
|
@ -54,6 +60,7 @@ Feature: Users can manage entries in database activities
|
||||||
And I press "Save" near "Web links" in the app
|
And I press "Save" near "Web links" in the app
|
||||||
And I press "Show more" near "Moodle community site" in the app
|
And I press "Show more" near "Moodle community site" in the app
|
||||||
Then I should find "Moodle community site" in the app
|
Then I should find "Moodle community site" in the app
|
||||||
|
And I should not find "Comments" in the app
|
||||||
And I should be able to press "Previous" in the app
|
And I should be able to press "Previous" in the app
|
||||||
But I should not be able to press "Next" in the app
|
But I should not be able to press "Next" in the app
|
||||||
|
|
||||||
|
@ -70,6 +77,21 @@ Feature: Users can manage entries in database activities
|
||||||
And I should find "Moodle community site" in the app
|
And I should find "Moodle community site" in the app
|
||||||
And I should find "Moodle Cloud" in the app
|
And I should find "Moodle Cloud" in the app
|
||||||
|
|
||||||
|
Given I entered the data activity "Data with comments" on course "Course 1" as "student1" in the app
|
||||||
|
When I press "Add entries" in the app
|
||||||
|
And I set the following fields to these values in the app:
|
||||||
|
| Description | Moodle community site |
|
||||||
|
And I press "Save" near "Data with comments" in the app
|
||||||
|
And I press "Show more" near "Moodle community site" in the app
|
||||||
|
Then I should find "Moodle community site" in the app
|
||||||
|
And I should find "Comments" in the app
|
||||||
|
|
||||||
|
Given the following config values are set as admin:
|
||||||
|
| usecomments | 0 |
|
||||||
|
And I entered the data activity "Data with comments" on course "Course 1" as "student1" in the app
|
||||||
|
When I press "Show more" near "Moodle community site" in the app
|
||||||
|
Then I should not find "Comments" in the app
|
||||||
|
|
||||||
Scenario: Students can not edit or delete other user's entries from list and single view in the app
|
Scenario: Students can not edit or delete other user's entries from list and single view in the app
|
||||||
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
||||||
And I press "Add entries" in the app
|
And I press "Add entries" in the app
|
||||||
|
|
|
@ -99,7 +99,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
try {
|
try {
|
||||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
||||||
this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
this.commentsEnabled = CoreComments.areCommentsEnabledInSite();
|
||||||
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||||
|
|
||||||
const entrySlug = CoreNavigator.getRequiredRouteParam<string>('entrySlug');
|
const entrySlug = CoreNavigator.getRequiredRouteParam<string>('entrySlug');
|
||||||
|
|
|
@ -155,7 +155,7 @@ export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPr
|
||||||
options,
|
options,
|
||||||
).then((entries) => {
|
).then((entries) => {
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
const commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
const commentsEnabled = CoreComments.areCommentsEnabledInSite();
|
||||||
|
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
// Don't fetch individual entries, it's too many WS calls.
|
// Don't fetch individual entries, it's too many WS calls.
|
||||||
|
|
|
@ -111,6 +111,15 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the delegate is enabled so handlers are not updated if not..
|
||||||
|
*
|
||||||
|
* @returns Whether the delegate is enabled.
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a certain function in a enabled handler.
|
* Execute a certain function in a enabled handler.
|
||||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||||
|
@ -315,6 +324,12 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
|
||||||
* @returns Resolved when done.
|
* @returns Resolved when done.
|
||||||
*/
|
*/
|
||||||
async updateHandlers(): Promise<void> {
|
async updateHandlers(): Promise<void> {
|
||||||
|
const enabled = await this.isEnabled();
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
@ -329,7 +344,7 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Never reject
|
// Never reject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,17 +18,26 @@ import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-ro
|
||||||
import { CoreCronDelegate } from '@services/cron';
|
import { CoreCronDelegate } from '@services/cron';
|
||||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||||
import { CoreCommentsComponentsModule } from './components/components.module';
|
import { CoreCommentsComponentsModule } from './components/components.module';
|
||||||
import { CoreComments, CoreCommentsProvider } from './services/comments';
|
import { CoreComments } from './services/comments';
|
||||||
import { CoreCommentsOfflineProvider } from './services/comments-offline';
|
|
||||||
import { CoreCommentsSyncProvider } from './services/comments-sync';
|
|
||||||
import { COMMENTS_OFFLINE_SITE_SCHEMA } from './services/database/comments';
|
import { COMMENTS_OFFLINE_SITE_SCHEMA } from './services/database/comments';
|
||||||
import { CoreCommentsSyncCronHandler } from './services/handlers/sync-cron';
|
import { CoreCommentsSyncCronHandler } from './services/handlers/sync-cron';
|
||||||
|
|
||||||
export const CORE_COMMENTS_SERVICES: Type<unknown>[] = [
|
/**
|
||||||
CoreCommentsOfflineProvider,
|
* Get comments services.
|
||||||
CoreCommentsSyncProvider,
|
*
|
||||||
CoreCommentsProvider,
|
* @returns Comments services.
|
||||||
];
|
*/
|
||||||
|
export async function getCommentsServices(): Promise<Type<unknown>[]> {
|
||||||
|
const { CoreCommentsOfflineProvider } = await import('@features/comments/services/comments-offline');
|
||||||
|
const { CoreCommentsSyncProvider } = await import('@features/comments/services/comments-sync');
|
||||||
|
const { CoreCommentsProvider } = await import('@features/comments/services/comments');
|
||||||
|
|
||||||
|
return [
|
||||||
|
CoreCommentsOfflineProvider,
|
||||||
|
CoreCommentsSyncProvider,
|
||||||
|
CoreCommentsProvider,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestr
|
||||||
commentsLoaded = false;
|
commentsLoaded = false;
|
||||||
commentsCount = '';
|
commentsCount = '';
|
||||||
countError = false;
|
countError = false;
|
||||||
disabled = false;
|
enabled = false;
|
||||||
|
|
||||||
protected updateSiteObserver?: CoreEventObserver;
|
protected updateSiteObserver?: CoreEventObserver;
|
||||||
protected refreshCommentsObserver?: CoreEventObserver;
|
protected refreshCommentsObserver?: CoreEventObserver;
|
||||||
|
@ -55,15 +55,15 @@ export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestr
|
||||||
|
|
||||||
this.onLoading = new EventEmitter<boolean>();
|
this.onLoading = new EventEmitter<boolean>();
|
||||||
|
|
||||||
this.disabled = CoreComments.areCommentsDisabledInSite();
|
this.enabled = CoreComments.areCommentsEnabledInSite();
|
||||||
|
|
||||||
// Update visibility if current site info is updated.
|
// Update visibility if current site info is updated.
|
||||||
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
const wasDisabled = this.disabled;
|
const wasEnabled = this.enabled;
|
||||||
|
|
||||||
this.disabled = CoreComments.areCommentsDisabledInSite();
|
this.enabled = CoreComments.areCommentsEnabledInSite();
|
||||||
|
|
||||||
if (wasDisabled && !this.disabled) {
|
if (!wasEnabled && this.enabled) {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
}, CoreSites.getCurrentSiteId());
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
@ -123,7 +123,7 @@ export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestr
|
||||||
* Fetch comments data.
|
* Fetch comments data.
|
||||||
*/
|
*/
|
||||||
async fetchData(): Promise<void> {
|
async fetchData(): Promise<void> {
|
||||||
if (this.disabled) {
|
if (!this.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,12 +173,10 @@ export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestr
|
||||||
* Opens the comments page.
|
* Opens the comments page.
|
||||||
*/
|
*/
|
||||||
openComments(e?: Event): void {
|
openComments(e?: Event): void {
|
||||||
if (e) {
|
e?.preventDefault();
|
||||||
e.preventDefault();
|
e?.stopPropagation();
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.disabled || this.countError) {
|
if (!this.enabled || this.countError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<ng-container *ngIf="!disabled">
|
<ng-container *ngIf="enabled">
|
||||||
<core-loading *ngIf="!showItem" [hideUntil]="commentsLoaded" [fullscreen]="false">
|
<core-loading *ngIf="!showItem" [hideUntil]="commentsLoaded" [fullscreen]="false">
|
||||||
<button *ngIf="!countError" (click)="openComments($event)" class="as-link">
|
<button *ngIf="!countError" (click)="openComments($event)" class="as-link">
|
||||||
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
|
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
|
||||||
|
|
|
@ -181,15 +181,14 @@ export class CoreCommentsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if Calendar is disabled in a certain site.
|
* Check if comments are disabled in a certain site.
|
||||||
*
|
*
|
||||||
* @param site Site. If not defined, use current site.
|
* @param site Site. If not defined, use current site.
|
||||||
* @returns Whether it's disabled.
|
* @returns Whether it's disabled.
|
||||||
|
* @deprecated since 4.4. Use areCommentsEnabledInSite instead.
|
||||||
*/
|
*/
|
||||||
areCommentsDisabledInSite(site?: CoreSite): boolean {
|
areCommentsDisabledInSite(site?: CoreSite): boolean {
|
||||||
site = site || CoreSites.getCurrentSite();
|
return !this.areCommentsEnabledInSite(site);
|
||||||
|
|
||||||
return !!site?.isFeatureDisabled('NoDelegate_CoreComments');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,11 +196,38 @@ export class CoreCommentsProvider {
|
||||||
*
|
*
|
||||||
* @param siteId Site Id. If not defined, use current site.
|
* @param siteId Site Id. If not defined, use current site.
|
||||||
* @returns Promise resolved with true if disabled, rejected or resolved with false otherwise.
|
* @returns Promise resolved with true if disabled, rejected or resolved with false otherwise.
|
||||||
|
* @deprecated since 4.4. Use areCommentsEnabled instead.
|
||||||
*/
|
*/
|
||||||
async areCommentsDisabled(siteId?: string): Promise<boolean> {
|
async areCommentsDisabled(siteId?: string): Promise<boolean> {
|
||||||
|
return !this.areCommentsEnabled(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if comments are enabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param site Site. If not defined, use current site.
|
||||||
|
* @returns Whether it's enabled.
|
||||||
|
*/
|
||||||
|
areCommentsEnabledInSite(site?: CoreSite): boolean {
|
||||||
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.canUseAdvancedFeature('usecomments') && !site.isFeatureDisabled('NoDelegate_CoreComments');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if comments are enabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param siteId Site Id. If not defined, use current site.
|
||||||
|
* @returns Promise resolved with true if enabled, rejected or resolved with false otherwise.
|
||||||
|
*/
|
||||||
|
async areCommentsEnabled(siteId?: string): Promise<boolean> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
return this.areCommentsDisabledInSite(site);
|
return this.areCommentsEnabledInSite(site);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -323,7 +349,7 @@ export class CoreCommentsProvider {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
// First check if it's disabled.
|
// First check if it's disabled.
|
||||||
if (this.areCommentsDisabledInSite(site)) {
|
if (!this.areCommentsEnabledInSite(site)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ import { makeSingleton } from '@singletons';
|
||||||
// Import core services.
|
// Import core services.
|
||||||
import { CORE_SERVICES } from '@/core/core.module';
|
import { CORE_SERVICES } from '@/core/core.module';
|
||||||
import { CORE_BLOCK_SERVICES } from '@features/block/block.module';
|
import { CORE_BLOCK_SERVICES } from '@features/block/block.module';
|
||||||
import { CORE_COMMENTS_SERVICES } from '@features/comments/comments.module';
|
import { getCommentsServices } from '@features/comments/comments.module';
|
||||||
import { CORE_CONTENTLINKS_SERVICES } from '@features/contentlinks/contentlinks.module';
|
import { CORE_CONTENTLINKS_SERVICES } from '@features/contentlinks/contentlinks.module';
|
||||||
import { CORE_COURSE_SERVICES } from '@features/course/course.module';
|
import { CORE_COURSE_SERVICES } from '@features/course/course.module';
|
||||||
import { CORE_COURSES_SERVICES } from '@features/courses/courses.module';
|
import { CORE_COURSES_SERVICES } from '@features/courses/courses.module';
|
||||||
|
@ -62,7 +62,7 @@ import { CORE_RATING_SERVICES } from '@features/rating/rating.module';
|
||||||
import { CORE_SEARCH_SERVICES } from '@features/search/search.module';
|
import { CORE_SEARCH_SERVICES } from '@features/search/search.module';
|
||||||
import { CORE_SETTINGS_SERVICES } from '@features/settings/settings.module';
|
import { CORE_SETTINGS_SERVICES } from '@features/settings/settings.module';
|
||||||
import { CORE_SITEHOME_SERVICES } from '@features/sitehome/sitehome.module';
|
import { CORE_SITEHOME_SERVICES } from '@features/sitehome/sitehome.module';
|
||||||
import { CORE_TAG_SERVICES } from '@features/tag/tag.module';
|
import { getTagServices } from '@features/tag/tag.module';
|
||||||
import { CORE_STYLE_SERVICES } from '@features/styles/styles.module';
|
import { CORE_STYLE_SERVICES } from '@features/styles/styles.module';
|
||||||
import { CORE_USER_SERVICES } from '@features/user/user.module';
|
import { CORE_USER_SERVICES } from '@features/user/user.module';
|
||||||
import { CORE_XAPI_SERVICES } from '@features/xapi/xapi.module';
|
import { CORE_XAPI_SERVICES } from '@features/xapi/xapi.module';
|
||||||
|
@ -126,7 +126,7 @@ import { CoreSitePluginsAssignSubmissionComponent } from '@features/siteplugins/
|
||||||
// Import addon providers. Do not import database module because it causes circular dependencies.
|
// Import addon providers. Do not import database module because it causes circular dependencies.
|
||||||
import { ADDON_BADGES_SERVICES } from '@addons/badges/badges.module';
|
import { ADDON_BADGES_SERVICES } from '@addons/badges/badges.module';
|
||||||
import { ADDON_CALENDAR_SERVICES } from '@addons/calendar/calendar.module';
|
import { ADDON_CALENDAR_SERVICES } from '@addons/calendar/calendar.module';
|
||||||
import { ADDON_COURSECOMPLETION_SERVICES } from '@addons/coursecompletion/coursecompletion.module';
|
import { getCourseCompletionServices } from '@addons/coursecompletion/coursecompletion.module';
|
||||||
import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module';
|
import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module';
|
||||||
import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module';
|
import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module';
|
||||||
import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module';
|
import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module';
|
||||||
|
@ -280,7 +280,6 @@ export class CoreCompileProvider {
|
||||||
...CORE_SERVICES,
|
...CORE_SERVICES,
|
||||||
CoreAutoLogoutService,
|
CoreAutoLogoutService,
|
||||||
...CORE_BLOCK_SERVICES,
|
...CORE_BLOCK_SERVICES,
|
||||||
...CORE_COMMENTS_SERVICES,
|
|
||||||
...CORE_CONTENTLINKS_SERVICES,
|
...CORE_CONTENTLINKS_SERVICES,
|
||||||
...CORE_COURSE_SERVICES,
|
...CORE_COURSE_SERVICES,
|
||||||
...CORE_COURSES_SERVICES,
|
...CORE_COURSES_SERVICES,
|
||||||
|
@ -300,7 +299,6 @@ export class CoreCompileProvider {
|
||||||
...CORE_SHAREDFILES_SERVICES,
|
...CORE_SHAREDFILES_SERVICES,
|
||||||
...CORE_SITEHOME_SERVICES,
|
...CORE_SITEHOME_SERVICES,
|
||||||
CoreSitePluginsProvider,
|
CoreSitePluginsProvider,
|
||||||
...CORE_TAG_SERVICES,
|
|
||||||
...CORE_STYLE_SERVICES,
|
...CORE_STYLE_SERVICES,
|
||||||
...CORE_USER_SERVICES,
|
...CORE_USER_SERVICES,
|
||||||
...CORE_XAPI_SERVICES,
|
...CORE_XAPI_SERVICES,
|
||||||
|
@ -309,7 +307,6 @@ export class CoreCompileProvider {
|
||||||
...extraProviders,
|
...extraProviders,
|
||||||
...ADDON_BADGES_SERVICES,
|
...ADDON_BADGES_SERVICES,
|
||||||
...ADDON_CALENDAR_SERVICES,
|
...ADDON_CALENDAR_SERVICES,
|
||||||
...ADDON_COURSECOMPLETION_SERVICES,
|
|
||||||
...ADDON_COMPETENCY_SERVICES,
|
...ADDON_COMPETENCY_SERVICES,
|
||||||
...ADDON_MESSAGEOUTPUT_SERVICES,
|
...ADDON_MESSAGEOUTPUT_SERVICES,
|
||||||
...ADDON_MESSAGES_SERVICES,
|
...ADDON_MESSAGES_SERVICES,
|
||||||
|
@ -410,9 +407,16 @@ export class CoreCompileProvider {
|
||||||
*/
|
*/
|
||||||
async getLazyLibraries(): Promise<Type<unknown>[]> {
|
async getLazyLibraries(): Promise<Type<unknown>[]> {
|
||||||
const ADDON_MOD_WORKSHOP_SERVICES = await getWorkshopServices();
|
const ADDON_MOD_WORKSHOP_SERVICES = await getWorkshopServices();
|
||||||
|
const ADDON_COURSECOMPLETION_SERVICES = await getCourseCompletionServices();
|
||||||
|
|
||||||
|
const CORE_COMMENTS_SERVICES = await getCommentsServices();
|
||||||
|
const CORE_TAG_SERVICES = await getTagServices();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...ADDON_MOD_WORKSHOP_SERVICES,
|
...ADDON_MOD_WORKSHOP_SERVICES,
|
||||||
|
...ADDON_COURSECOMPLETION_SERVICES,
|
||||||
|
...CORE_COMMENTS_SERVICES,
|
||||||
|
...CORE_TAG_SERVICES,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper';
|
||||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
|
import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { ModalController } from '@singletons';
|
import { ModalController } from '@singletons';
|
||||||
|
@ -61,7 +62,7 @@ export class CoreCourseCourseIndexComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let completionEnabled = !!this.course.enablecompletion;
|
let completionEnabled = CoreCoursesHelper.isCompletionEnabledInCourse(this.course);
|
||||||
if (completionEnabled && 'completionusertracked' in this.course && this.course.completionusertracked !== undefined) {
|
if (completionEnabled && 'completionusertracked' in this.course && this.course.completionusertracked !== undefined) {
|
||||||
completionEnabled = this.course.completionusertracked;
|
completionEnabled = this.course.completionusertracked;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {
|
||||||
} from '@singletons/events';
|
} from '@singletons/events';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
||||||
|
import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the contents of a course.
|
* Page that displays the contents of a course.
|
||||||
|
@ -221,7 +222,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
let completionStatus: Record<string, CoreCourseCompletionActivityStatus> = {};
|
let completionStatus: Record<string, CoreCourseCompletionActivityStatus> = {};
|
||||||
|
|
||||||
// Get the completion status.
|
// Get the completion status.
|
||||||
if (this.course.enablecompletion !== false) {
|
if (CoreCoursesHelper.isCompletionEnabledInCourse(this.course)) {
|
||||||
const sectionWithModules = sections.find((section) => section.modules.length > 0);
|
const sectionWithModules = sections.find((section) => section.modules.length > 0);
|
||||||
|
|
||||||
if (sectionWithModules && sectionWithModules.modules[0].completion !== undefined) {
|
if (sectionWithModules && sectionWithModules.modules[0].completion !== undefined) {
|
||||||
|
@ -265,7 +266,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
protected async loadCourseFormatOptions(): Promise<void> {
|
protected async loadCourseFormatOptions(): Promise<void> {
|
||||||
|
|
||||||
// Load the course format options when course completion is enabled to show completion progress on sections.
|
// Load the course format options when course completion is enabled to show completion progress on sections.
|
||||||
if (!this.course.enablecompletion) {
|
if (!CoreCoursesHelper.isCompletionEnabledInCourse(this.course)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
||||||
import {
|
import {
|
||||||
|
CoreCourseAnyCourseData,
|
||||||
CoreCourseAnyCourseDataWithOptions,
|
CoreCourseAnyCourseDataWithOptions,
|
||||||
CoreCourses,
|
CoreCourses,
|
||||||
CoreCourseSearchedData,
|
CoreCourseSearchedData,
|
||||||
|
@ -31,6 +32,7 @@ import { zipIncludingComplete } from '@/core/utils/rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
import { catchError, map } from 'rxjs/operators';
|
||||||
import { chainRequests, WSObservable } from '@classes/sites/authenticated-site';
|
import { chainRequests, WSObservable } from '@classes/sites/authenticated-site';
|
||||||
import { LazyRoutesModule } from '@/app/app-routing.module';
|
import { LazyRoutesModule } from '@/app/app-routing.module';
|
||||||
|
import { CoreSite } from '@classes/sites/site';
|
||||||
|
|
||||||
// Id for a course item representing all courses (for example, for course filters).
|
// Id for a course item representing all courses (for example, for course filters).
|
||||||
export const ALL_COURSES_ID = -1;
|
export const ALL_COURSES_ID = -1;
|
||||||
|
@ -363,7 +365,7 @@ export class CoreCoursesHelperProvider {
|
||||||
return of(course);
|
return of(course);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (course.enablecompletion !== undefined && !course.enablecompletion) {
|
if (!this.isCompletionEnabledInCourse(course)) {
|
||||||
// Completion is disabled for this course, there is no need to fetch the completion status.
|
// Completion is disabled for this course, there is no need to fetch the completion status.
|
||||||
return of(course);
|
return of(course);
|
||||||
}
|
}
|
||||||
|
@ -437,6 +439,18 @@ export class CoreCoursesHelperProvider {
|
||||||
return import('../courses-my-lazy.module').then(m => m.CoreCoursesMyLazyModule);
|
return import('../courses-my-lazy.module').then(m => m.CoreCoursesMyLazyModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether completion is available in a certain course.
|
||||||
|
* This is a temporary function to be used until we move AddonCourseCompletion to core folder (MOBILE-4537).
|
||||||
|
*
|
||||||
|
* @param course Course.
|
||||||
|
* @param site Site. If not defined, use current site.
|
||||||
|
* @returns True if available.
|
||||||
|
*/
|
||||||
|
isCompletionEnabledInCourse(course: CoreCourseAnyCourseData, site?: CoreSite): boolean {
|
||||||
|
return AddonCourseCompletion.isCompletionEnabledInCourse(course, site);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreCoursesHelper = makeSingleton(CoreCoursesHelperProvider);
|
export const CoreCoursesHelper = makeSingleton(CoreCoursesHelperProvider);
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreTag } from '../tag';
|
import { CoreTag } from '../tag';
|
||||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
|
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,13 +34,7 @@ export class CoreTagMainMenuHandlerService implements CoreMainMenuHandler {
|
||||||
* @returns Whether or not the handler is enabled on a site level.
|
* @returns Whether or not the handler is enabled on a site level.
|
||||||
*/
|
*/
|
||||||
async isEnabled(): Promise<boolean> {
|
async isEnabled(): Promise<boolean> {
|
||||||
const available = await CoreTag.areTagsAvailable();
|
return await CoreTag.areTagsAvailable();
|
||||||
if (!available) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The only way to check whether tags are enabled on web is to perform a WS call.
|
|
||||||
return CoreUtils.promiseWorks(CoreTag.getTagCollections());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import { Injectable, Type } from '@angular/core';
|
import { Injectable, Type } from '@angular/core';
|
||||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreTag } from './tag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that all tag area handlers must implement.
|
* Interface that all tag area handlers must implement.
|
||||||
|
@ -53,6 +54,13 @@ export class CoreTagAreaDelegateService extends CoreDelegate<CoreTagAreaHandler>
|
||||||
super('CoreTagAreaDelegate', true);
|
super('CoreTagAreaDelegate', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return CoreTag.areTagsAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the display name string for this area.
|
* Returns the display name string for this area.
|
||||||
*
|
*
|
||||||
|
|
|
@ -53,7 +53,8 @@ export class CoreTagProvider {
|
||||||
areTagsAvailableInSite(site?: CoreSite): boolean {
|
areTagsAvailableInSite(site?: CoreSite): boolean {
|
||||||
site = site || CoreSites.getCurrentSite();
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
|
||||||
return !!site && site.wsAvailable('core_tag_get_tagindex_per_area') &&
|
return !!site && site.canUseAdvancedFeature('usetags') &&
|
||||||
|
site.wsAvailable('core_tag_get_tagindex_per_area') &&
|
||||||
site.wsAvailable('core_tag_get_tag_cloud') &&
|
site.wsAvailable('core_tag_get_tag_cloud') &&
|
||||||
site.wsAvailable('core_tag_get_tag_collections') &&
|
site.wsAvailable('core_tag_get_tag_collections') &&
|
||||||
!site.isFeatureDisabled('NoDelegate_CoreTag');
|
!site.isFeatureDisabled('NoDelegate_CoreTag');
|
||||||
|
|
|
@ -22,15 +22,23 @@ import { CoreTagIndexLinkHandler } from './services/handlers/index-link';
|
||||||
import { CoreTagSearchLinkHandler } from './services/handlers/search-link';
|
import { CoreTagSearchLinkHandler } from './services/handlers/search-link';
|
||||||
import { CoreTagComponentsModule } from './components/components.module';
|
import { CoreTagComponentsModule } from './components/components.module';
|
||||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
import { CoreTagAreaDelegateService } from './services/tag-area-delegate';
|
|
||||||
import { CoreTagHelperProvider } from './services/tag-helper';
|
|
||||||
import { CoreTagProvider } from './services/tag';
|
|
||||||
|
|
||||||
export const CORE_TAG_SERVICES: Type<unknown>[] = [
|
/**
|
||||||
CoreTagAreaDelegateService,
|
* Get tags services.
|
||||||
CoreTagHelperProvider,
|
*
|
||||||
CoreTagProvider,
|
* @returns Tags services.
|
||||||
];
|
*/
|
||||||
|
export async function getTagServices(): Promise<Type<unknown>[]> {
|
||||||
|
const { CoreTagAreaDelegateService } = await import('@features/tag/services/tag-area-delegate');
|
||||||
|
const { CoreTagHelperProvider } = await import('@features/tag/services/tag-helper');
|
||||||
|
const { CoreTagProvider } = await import('@features/tag/services/tag');
|
||||||
|
|
||||||
|
return [
|
||||||
|
CoreTagAreaDelegateService,
|
||||||
|
CoreTagHelperProvider,
|
||||||
|
CoreTagProvider,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
@core_tag @app @javascript
|
||||||
|
Feature: Tag navigation
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username | firstname | lastname | email | interests |
|
||||||
|
| user1 | User | 1 | user1@example.com | Cat |
|
||||||
|
| user2 | User | 2 | user1@example.com | Cat, Dog |
|
||||||
|
| user3 | User | 3 | user1@example.com | Dog |
|
||||||
|
And the following "courses" exist:
|
||||||
|
| fullname | shortname | tags |
|
||||||
|
| Course 1 | c1 | Cat, Dog |
|
||||||
|
| Course 2 | c2 | Cat |
|
||||||
|
| Course 3 | c3 | Cat |
|
||||||
|
| Course 4 | c4 | Cat |
|
||||||
|
| Course 5 | c5 | Cat |
|
||||||
|
| Course 6 | c6 | Cat |
|
||||||
|
| Course 7 | c7 | Cat |
|
||||||
|
|
||||||
|
Scenario: Tag menu item is found in the more menu when completion is enabled
|
||||||
|
Given I entered the app as "user1"
|
||||||
|
When I press the more menu button in the app
|
||||||
|
And I press "Tags" in the app
|
||||||
|
Then I should find "Cat" in the app
|
||||||
|
Then I should find "Dog" in the app
|
||||||
|
|
||||||
|
Given the following config values are set as admin:
|
||||||
|
| usetags | 0 |
|
||||||
|
And I entered the app as "user1"
|
||||||
|
When I press the more menu button in the app
|
||||||
|
Then I should not find "Tags" in the app
|
Loading…
Reference in New Issue