mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-04 10:14:13 +08:00
Add user preferences api and legacyhandler
This commit is contained in:
parent
1ab3d65b36
commit
6b2c10092d
31 changed files with 1159 additions and 111 deletions
|
@ -27,6 +27,7 @@ services:
|
|||
$legacyActionNameMap: '%legacy.action_name_map%'
|
||||
$menuItemMap: '%legacy.menu_item_map%'
|
||||
$legacyAssetPaths: '%legacy.asset_paths%'
|
||||
$exposedUserPreferences: '%legacy.exposed_user_preferences%'
|
||||
_instanceof:
|
||||
App\Service\ProcessHandlerInterface:
|
||||
tags: ['app.process.handler']
|
||||
|
|
47
config/services/legacy/exposed_user_preferences.yaml
Normal file
47
config/services/legacy/exposed_user_preferences.yaml
Normal file
|
@ -0,0 +1,47 @@
|
|||
parameters:
|
||||
legacy.exposed_user_preferences:
|
||||
global:
|
||||
sort_modules_by_name: true
|
||||
navigation_paradigm: true
|
||||
swap_shortcuts: true
|
||||
subpanel_tabs: true
|
||||
count_collapsed_subpanels: true
|
||||
user_theme: true
|
||||
module_favicon: true
|
||||
no_opps: true
|
||||
reminder_time: true
|
||||
email_reminder_time: true
|
||||
reminder_checked: true
|
||||
email_reminder_checked: true
|
||||
timezone: true
|
||||
ut: true
|
||||
currency: true
|
||||
default_currency_significant_digits: true
|
||||
num_grp_sep: true
|
||||
dec_sep: true
|
||||
fdow: true
|
||||
datef: true
|
||||
timef: true
|
||||
mail_fromname: true
|
||||
mail_fromaddress: true
|
||||
mail_sendtype: true
|
||||
mail_smtpserver: true
|
||||
mail_smtpport: true
|
||||
mail_smtpuser: true
|
||||
mail_smtppass: true
|
||||
default_locale_name_format: true
|
||||
export_delimiter: true
|
||||
default_export_charset: true
|
||||
use_real_names: true
|
||||
mail_smtpauth_req: true
|
||||
mail_smtpssl: true
|
||||
signature_default: true
|
||||
signature_prepend: true
|
||||
email_link_type: true
|
||||
editor_type: true
|
||||
email_show_counts: true
|
||||
email_editor_option: true
|
||||
default_email_charset: true
|
||||
calendar_publish_key: true
|
||||
subtheme: true
|
||||
syncGCal: true
|
|
@ -3,6 +3,7 @@ import {Routes, RouterModule} from '@angular/router';
|
|||
import {ClassicViewUiComponent} from '@components/classic-view/classic-view.component';
|
||||
import {ClassicViewResolver} from "@services/classic-view/classic-view.resolver";
|
||||
import {BaseMetadataResolver} from '@services/metadata/base-metadata.resolver';
|
||||
import {UserPreferenceResolver} from '@base/facades/user-preference/user-preference.resolver';
|
||||
import {AuthGuard} from '../services/auth/auth-guard.service';
|
||||
import {ListComponent} from '@views/list/list.component';
|
||||
|
||||
|
@ -11,13 +12,16 @@ const routes: Routes = [
|
|||
path: 'Listview',
|
||||
component: ListComponent,
|
||||
resolve: {
|
||||
metadata: BaseMetadataResolver
|
||||
},
|
||||
view: BaseMetadataResolver,
|
||||
userPreference: UserPreferenceResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'Login',
|
||||
loadChildren: () => import('../components/login/login.module').then(m => m.LoginUiModule),
|
||||
resolve: {metadata: BaseMetadataResolver},
|
||||
resolve: {
|
||||
metadata: BaseMetadataResolver
|
||||
},
|
||||
data: {
|
||||
load: {
|
||||
navigation: false,
|
||||
|
@ -30,6 +34,7 @@ const routes: Routes = [
|
|||
loadChildren: () => import('../components/home/home.module').then(m => m.HomeUiModule),
|
||||
resolve: {
|
||||
metadata: BaseMetadataResolver,
|
||||
userPreference: UserPreferenceResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -39,7 +44,8 @@ const routes: Routes = [
|
|||
runGuardsAndResolvers: 'always',
|
||||
resolve: {
|
||||
metadata: BaseMetadataResolver,
|
||||
view: ClassicViewResolver
|
||||
view: ClassicViewResolver,
|
||||
userPreference: UserPreferenceResolver
|
||||
},
|
||||
data: {
|
||||
reuseRoute: false
|
||||
|
@ -52,7 +58,8 @@ const routes: Routes = [
|
|||
runGuardsAndResolvers: 'always',
|
||||
resolve: {
|
||||
metadata: BaseMetadataResolver,
|
||||
view: ClassicViewResolver
|
||||
view: ClassicViewResolver,
|
||||
userPreference: UserPreferenceResolver
|
||||
},
|
||||
data: {
|
||||
reuseRoute: false
|
||||
|
@ -65,7 +72,8 @@ const routes: Routes = [
|
|||
runGuardsAndResolvers: 'always',
|
||||
resolve: {
|
||||
metadata: BaseMetadataResolver,
|
||||
view: ClassicViewResolver
|
||||
view: ClassicViewResolver,
|
||||
userPreference: UserPreferenceResolver
|
||||
},
|
||||
data: {
|
||||
reuseRoute: false
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</div>
|
||||
|
||||
|
||||
<button class="login-button"
|
||||
<button id="login-button" class="login-button"
|
||||
(click)="loginForm.control.markAllAsTouched(); loginForm.valid && doLogin()">
|
||||
{{vm.appStrings['LBL_LOGIN_BUTTON_LABEL']}}
|
||||
</button>
|
||||
|
@ -105,7 +105,7 @@
|
|||
{{vm.appStrings['LBL_GENERATE_PASSWORD_BUTTON_TITLE']}}
|
||||
</button>
|
||||
<div>
|
||||
<a href="#" class="forgotten-password-link" (click)="flipCard()">
|
||||
<a href="#" class="back-link forgotten-password-link" (click)="flipCard()">
|
||||
{{vm.appStrings['LBL_BACK']}}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {LoginUiComponent} from './login.component';
|
||||
import {ApiService} from '../../services/api/api.service';
|
||||
import {LanguageFacade} from '@base/facades/language/language.facade';
|
||||
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
|
||||
import {ApolloTestingModule} from 'apollo-angular/testing';
|
||||
import {systemConfigFacadeMock} from '@base/facades/system-config/system-config.facade.spec.mock';
|
||||
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
|
||||
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
|
||||
import {systemConfigFacadeMock} from '@base/facades/system-config/system-config.facade.spec.mock';
|
||||
import {
|
||||
recoverPasswordMock,
|
||||
recoverPasswordMockData
|
||||
} from '@services/process/processes/recover-password/recover-passoword.spec.mock';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Observable, of} from 'rxjs';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginUiComponent;
|
||||
let fixture: ComponentFixture<LoginUiComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [
|
||||
|
@ -30,6 +37,7 @@ describe('LoginComponent', () => {
|
|||
providers: [
|
||||
{provide: SystemConfigFacade, useValue: systemConfigFacadeMock},
|
||||
{provide: LanguageFacade, useValue: languageFacadeMock},
|
||||
{provide: RecoverPasswordService, useValue: recoverPasswordMock},
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
@ -45,4 +53,29 @@ describe('LoginComponent', () => {
|
|||
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
|
||||
expect(component).toBeTruthy();
|
||||
})));
|
||||
|
||||
it('should flip on forgot password click', async(inject([HttpTestingController],
|
||||
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component).toBeTruthy();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('.back-link'))).toBeNull();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeTruthy();
|
||||
expect(fixture.debugElement.query(By.css('#login-button'))).toBeTruthy();
|
||||
component.flipCard();
|
||||
// same test codmockRecoverPasswordServicee here
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#login-button'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('.forgotten-password'))).toBeNull();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeTruthy();
|
||||
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeTruthy();
|
||||
expect(fixture.debugElement.query(By.css('.back-link'))).toBeTruthy();
|
||||
});
|
||||
})));
|
||||
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import {fadeIn} from 'ng-animate';
|
|||
import {AuthService} from '@services/auth/auth.service';
|
||||
import {MessageService} from '@services/message/message.service';
|
||||
import {ApiService} from '@services/api/api.service';
|
||||
import {RecoverPasswordService} from '@services/process/processes/recover-password';
|
||||
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
|
||||
|
||||
import {SystemConfigFacade, SystemConfigMap} from '@base/facades/system-config/system-config.facade';
|
||||
import {LanguageFacade, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
|
|
|
@ -2,7 +2,7 @@ import {ActionLinkModel} from './action-link-model';
|
|||
import {CurrentUserModel} from './current-user-model';
|
||||
import {AllMenuModel} from './all-menu-model';
|
||||
import {LogoModel} from '../logo/logo-model';
|
||||
import {NavbarModuleMap} from '@base/facades/navigation/navigation.facade';
|
||||
import {NavbarModuleMap, GroupedTab} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
|
||||
|
@ -17,11 +17,21 @@ export interface NavbarModel {
|
|||
|
||||
resetMenu(): void;
|
||||
|
||||
buildMenu(
|
||||
buildGroupTabMenu(
|
||||
items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number): void;
|
||||
menuItemThreshold: number,
|
||||
groupedTabs: GroupedTab[]
|
||||
): void;
|
||||
|
||||
buildTabMenu(
|
||||
items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number): void;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {NavbarModel} from './navbar-model';
|
||||
import {LogoAbstract} from '../logo/logo-abstract';
|
||||
import {NavbarModule, NavbarModuleMap} from "@base/facades/navigation/navigation.facade";
|
||||
import {NavbarModule, NavbarModuleMap, GroupedTab} from "@base/facades/navigation/navigation.facade";
|
||||
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
|
||||
import {CurrentUserModel} from './current-user-model';
|
||||
import {ActionLinkModel} from './action-link-model';
|
||||
|
||||
|
@ -22,7 +23,6 @@ export interface MenuItem {
|
|||
recentRecords?: RecentRecordsMenuItem[];
|
||||
}
|
||||
|
||||
|
||||
const ROUTE_PREFIX = './#';
|
||||
|
||||
export class NavbarAbstract implements NavbarModel {
|
||||
|
@ -70,12 +70,40 @@ export class NavbarAbstract implements NavbarModel {
|
|||
this.all.modules = [];
|
||||
}
|
||||
|
||||
public buildMenu(items: string[],
|
||||
public buildGroupTabMenu(items: string[], modules: NavbarModuleMap, appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap, appListStrings: LanguageListStringMap, threshold: number, groupedTabs: GroupedTab[]): void
|
||||
{
|
||||
const navItems = [];
|
||||
const moreItems = [];
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
this.menu = navItems;
|
||||
this.all.extra = moreItems;
|
||||
return;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
groupedTabs.forEach((groupedTab: any) => {
|
||||
|
||||
if (count <= threshold) {
|
||||
navItems.push(this.buildTabGroupedMenuItem(groupedTab.labelKey, groupedTab.modules, modules, appStrings, modStrings, appListStrings));
|
||||
}
|
||||
|
||||
count++;
|
||||
});
|
||||
|
||||
this.menu = navItems;
|
||||
this.all.modules = moreItems;
|
||||
this.all.extra = moreItems;
|
||||
}
|
||||
|
||||
public buildTabMenu(items: string[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
threshold: number): void {
|
||||
threshold: number
|
||||
): void {
|
||||
|
||||
const navItems = [];
|
||||
const moreItems = [];
|
||||
|
@ -94,9 +122,9 @@ export class NavbarAbstract implements NavbarModel {
|
|||
}
|
||||
|
||||
if (count <= threshold) {
|
||||
navItems.push(this.buildMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
navItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
} else {
|
||||
moreItems.push(this.buildMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
moreItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
|
||||
}
|
||||
|
||||
count++;
|
||||
|
@ -107,22 +135,77 @@ export class NavbarAbstract implements NavbarModel {
|
|||
this.all.extra = moreItems;
|
||||
}
|
||||
|
||||
public buildMenuItem(
|
||||
module: string,
|
||||
moduleInfo: NavbarModule,
|
||||
public buildTabGroupedMenuItem(
|
||||
moduleLabel: string,
|
||||
groupedModules: any[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap): MenuItem {
|
||||
appListStrings: LanguageListStringMap
|
||||
): any {
|
||||
let moduleUrl = '';
|
||||
|
||||
let moduleRoute = null;
|
||||
|
||||
let moduleUrl = moduleInfo.defaultRoute;
|
||||
if (moduleUrl.startsWith(ROUTE_PREFIX)) {
|
||||
moduleRoute = moduleUrl.replace(ROUTE_PREFIX, '');
|
||||
moduleUrl = null;
|
||||
}
|
||||
|
||||
const menuItem = {
|
||||
link: {
|
||||
label: (appStrings && appStrings[moduleLabel]) || moduleLabel,
|
||||
url: moduleUrl,
|
||||
route: moduleRoute,
|
||||
params: null
|
||||
},
|
||||
icon: '',
|
||||
submenu: this.buildGroupedMenu(groupedModules, modules, appStrings, modStrings, appListStrings)
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
public buildGroupedMenu(
|
||||
groupedModules: any[],
|
||||
modules: NavbarModuleMap,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap
|
||||
): MenuItem[] {
|
||||
|
||||
const groupedItems = [];
|
||||
|
||||
groupedModules.forEach((groupedModule) => {
|
||||
|
||||
let module = modules[groupedModule];
|
||||
|
||||
if (module === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
groupedItems.push(this.buildTabMenuItem(groupedModule, module, appStrings, modStrings, appListStrings));
|
||||
});
|
||||
|
||||
return groupedItems;
|
||||
}
|
||||
|
||||
public buildTabMenuItem(
|
||||
module: string,
|
||||
moduleInfo: any,
|
||||
appStrings: LanguageStringMap,
|
||||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap
|
||||
): MenuItem {
|
||||
|
||||
let moduleUrl = (moduleInfo && moduleInfo.defaultRoute) || moduleInfo.defaultRoute;
|
||||
let moduleRoute = null;
|
||||
if (moduleUrl.startsWith(ROUTE_PREFIX)) {
|
||||
moduleRoute = moduleUrl.replace(ROUTE_PREFIX, '');
|
||||
moduleUrl = null;
|
||||
}
|
||||
|
||||
const moduleLabel = moduleInfo.labelKey;
|
||||
const moduleLabel = (moduleInfo && moduleInfo.labelKey) || moduleInfo.labelKey;
|
||||
const menuItem = {
|
||||
link: {
|
||||
label: (appListStrings && appListStrings.moduleList[moduleInfo.labelKey]) || moduleLabel,
|
||||
|
@ -134,37 +217,39 @@ export class NavbarAbstract implements NavbarModel {
|
|||
submenu: []
|
||||
};
|
||||
|
||||
moduleInfo.menu.forEach((subMenu) => {
|
||||
let label = modStrings[module][subMenu.labelKey];
|
||||
if (moduleInfo) {
|
||||
moduleInfo.menu.forEach((subMenu) => {
|
||||
let label = modStrings[module][subMenu.labelKey];
|
||||
|
||||
if (!label) {
|
||||
label = appStrings[subMenu.labelKey];
|
||||
}
|
||||
|
||||
let actionUrl = subMenu.url;
|
||||
let actionRoute = null;
|
||||
let actionParams = null;
|
||||
if (actionUrl.startsWith(ROUTE_PREFIX)) {
|
||||
actionRoute = actionUrl.replace(ROUTE_PREFIX, '');
|
||||
actionUrl = null;
|
||||
|
||||
if (subMenu.params) {
|
||||
actionParams = subMenu.params;
|
||||
if (!label) {
|
||||
label = appStrings[subMenu.labelKey];
|
||||
}
|
||||
|
||||
let actionUrl = subMenu.url;
|
||||
let actionRoute = null;
|
||||
let actionParams = null;
|
||||
if (actionUrl.startsWith(ROUTE_PREFIX)) {
|
||||
actionRoute = actionUrl.replace(ROUTE_PREFIX, '');
|
||||
actionUrl = null;
|
||||
|
||||
if (subMenu.params) {
|
||||
actionParams = subMenu.params;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
menuItem.submenu.push({
|
||||
link: {
|
||||
label,
|
||||
url: actionUrl,
|
||||
route: actionRoute,
|
||||
params: actionParams
|
||||
},
|
||||
icon: subMenu.icon,
|
||||
submenu: []
|
||||
menuItem.submenu.push({
|
||||
link: {
|
||||
label,
|
||||
url: actionUrl,
|
||||
route: actionRoute,
|
||||
params: actionParams
|
||||
},
|
||||
icon: subMenu.icon,
|
||||
submenu: []
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
|
||||
|
||||
<!-- Start of main navbar section -->
|
||||
<div class="top-panel fixed-top" *ngIf="(vm$ | async) as vm">
|
||||
|
||||
<!-- Start of empty navbar section until data is loaded -->
|
||||
|
||||
<!-- Start of empty navbar section until data is loaded -->
|
||||
|
||||
<ng-template [ngIf]="!loaded">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="navbar-collapse collapse order-4 order-md-0 collapsenav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="top-nav nav-item">
|
||||
</li>
|
||||
</ul>
|
||||
<ng-template [ngIf]="!loaded">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="navbar-collapse collapse order-4 order-md-0 collapsenav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="top-nav nav-item">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</ng-template>
|
||||
|
@ -60,11 +62,11 @@
|
|||
</a>
|
||||
<ul *ngIf="sub.submenu.length" class="">
|
||||
<li *ngFor="let subitem of sub.submenu" class="nav-item">
|
||||
<a class="mobile-nav-link action-link" href="{{ subitem.link.url }}">
|
||||
<a class="mobile-nav-link action-link" href="/#{{ subitem.link.route }}">
|
||||
{{ subitem.link.label }}
|
||||
</a>
|
||||
</li>
|
||||
<ng-template [ngIf]="sub.recentRecords.length">
|
||||
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
|
||||
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
|
||||
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
|
||||
<a class="mobile-nav-link action-link"
|
||||
|
@ -126,16 +128,16 @@
|
|||
|
||||
<!-- Navbar with grouped tabs -->
|
||||
|
||||
<ul *ngIf="navbar.useGroupTabs" class="navbar-nav grouped">
|
||||
<ul *ngIf="vm.userPreferences['navigation_paradigm'] == 'gm'" class="navbar-nav grouped">
|
||||
<li class="top-nav nav-item dropdown main-grouped" *ngFor="let item of navbar.menu">
|
||||
<span data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<a class="nav-link-grouped dropdown-toggle" data-toggle="dropdown" href="{{ item.link.url }}">
|
||||
<a class="nav-link-grouped dropdown-toggle" data-toggle="dropdown" href="{{ item.link.route }}">
|
||||
{{ item.link.label }}
|
||||
</a>
|
||||
</span>
|
||||
<ul class="dropdown-menu main" aria-labelledby="navbarDropdownMenuLink" [ngbCollapse]="subNavCollapse">
|
||||
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
|
||||
<a class="nav-link action-link" *ngIf="!sub.submenu.length" [href]="sub.link.url">
|
||||
<a class="nav-link action-link" *ngIf="!sub.submenu.length" href="{{sub.link.route}}">
|
||||
{{ sub.link.label }}
|
||||
</a>
|
||||
<a class="nav-link action-link dropdown-item dropdown-toggle" *ngIf="sub.submenu.length"
|
||||
|
@ -144,13 +146,13 @@
|
|||
</a>
|
||||
<ul *ngIf="sub.submenu.length" class="dropdown-menu submenu">
|
||||
<li *ngFor="let subitem of sub.submenu" class="nav-item">
|
||||
<a class="nav-link action-link" href="{{ subitem.link.url }}">
|
||||
<a class="nav-link action-link" href="#{{ subitem.link.route }}">
|
||||
<svg-icon *ngIf="subitem.icon" src="public/themes/suite8/images/{{ subitem.icon }}.svg">
|
||||
</svg-icon>
|
||||
{{ subitem.link.label }}
|
||||
</a>
|
||||
</li>
|
||||
<ng-template [ngIf]="sub.recentRecords.length">
|
||||
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
|
||||
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
|
||||
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
|
||||
<a class="nav-link action-link"
|
||||
|
@ -165,20 +167,20 @@
|
|||
|
||||
<!-- Navbar with non-grouped tabs -->
|
||||
|
||||
<ul *ngIf="!navbar.useGroupTabs" class="navbar-nav">
|
||||
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm'" class="navbar-nav">
|
||||
<li class="top-nav nav-item dropdown non-grouped" *ngFor="let item of navbar.menu">
|
||||
|
||||
<!-- TODO: Implement a cleaner solution than hard coded ngIf -->
|
||||
|
||||
<ng-template [ngIf]="item.link.label !== 'Home'">
|
||||
<span data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<a class="nav-link-nongrouped dropdown-toggle"
|
||||
[href]="item.link.url"
|
||||
[routerLink]="item.link.route"
|
||||
[queryParams]="item.link.params"
|
||||
routerLinkActive="active">
|
||||
{{ item.link.label }}
|
||||
</a>
|
||||
<a class="nav-link-nongrouped dropdown-toggle"
|
||||
[href]="item.link.url"
|
||||
[routerLink]="item.link.route"
|
||||
[queryParams]="item.link.params"
|
||||
routerLinkActive="active">
|
||||
{{ item.link.label }}
|
||||
</a>
|
||||
</span>
|
||||
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu submenu">
|
||||
<div class="nav-item" *ngFor="let sub of item.submenu">
|
||||
|
@ -201,7 +203,7 @@
|
|||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<ul *ngIf="!navbar.useGroupTabs && navbar.all.modules && navbar.all.modules.length > 0"
|
||||
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm' && navbar.all.modules && navbar.all.modules.length > 0"
|
||||
class="navbar-nav">
|
||||
<li class="top-nav nav-item dropdown non-grouped">
|
||||
<a class="nav-link-nongrouped dropdown-toggle">More</a>
|
||||
|
|
|
@ -10,6 +10,8 @@ import {NavigationFacade} from '@base/facades/navigation/navigation.facade';
|
|||
import {LanguageFacade} from '@base/facades/language/language.facade';
|
||||
import {navigationMock} from '@base/facades/navigation/navigation.facade.spec.mock';
|
||||
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
|
||||
import {UserPreferenceFacade} from "@base/facades/user-preference/user-preference.facade";
|
||||
import {userPreferenceFacadeMock} from "@base/facades/user-preference/user-preference.facade.spec.mock";
|
||||
|
||||
describe('NavbarUiComponent', () => {
|
||||
|
||||
|
@ -29,6 +31,7 @@ describe('NavbarUiComponent', () => {
|
|||
providers: [
|
||||
{provide: NavigationFacade, useValue: navigationMock},
|
||||
{provide: LanguageFacade, useValue: languageFacadeMock},
|
||||
{provide: UserPreferenceFacade, useValue: userPreferenceFacadeMock},
|
||||
],
|
||||
declarations: [NavbarUiComponent]
|
||||
}).compileComponents();
|
||||
|
|
|
@ -4,9 +4,13 @@ import {NavbarModel} from './navbar-model';
|
|||
import {NavbarAbstract} from './navbar.abstract';
|
||||
import {combineLatest, Observable} from 'rxjs';
|
||||
|
||||
import {NavbarModuleMap, NavigationFacade} from '@base/facades/navigation/navigation.facade';
|
||||
import {NavbarModuleMap, NavigationFacade, GroupedTab} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageFacade, LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
|
||||
import {UserPreferenceMap, UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
|
||||
|
||||
import {map} from 'rxjs/operators';
|
||||
import { exists } from 'fs';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-navbar-ui',
|
||||
|
@ -36,24 +40,49 @@ export class NavbarUiComponent implements OnInit {
|
|||
appStrings$: Observable<LanguageStringMap> = this.languageFacade.appStrings$;
|
||||
modStrings$: Observable<LanguageListStringMap> = this.languageFacade.modStrings$;
|
||||
appListStrings$: Observable<LanguageListStringMap> = this.languageFacade.appListStrings$;
|
||||
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceFacade.userPreferences$;
|
||||
groupedTabs$: Observable<any> = this.navigationFacade.groupedTabs$;
|
||||
|
||||
vm$ = combineLatest([this.tabs$, this.modules$, this.appStrings$, this.appListStrings$, this.modStrings$]).pipe(
|
||||
vm$ = combineLatest([
|
||||
this.tabs$,
|
||||
this.modules$,
|
||||
this.appStrings$,
|
||||
this.appListStrings$,
|
||||
this.modStrings$,
|
||||
this.userPreferences$,
|
||||
this.groupedTabs$
|
||||
]).pipe(
|
||||
map((
|
||||
[
|
||||
tabs,
|
||||
modules,
|
||||
appStrings,
|
||||
appListStrings,
|
||||
modStrings
|
||||
modStrings,
|
||||
userPreferences,
|
||||
groupedTabs
|
||||
]) => {
|
||||
|
||||
if (tabs && tabs.length > 0 &&
|
||||
modules && Object.keys(modules).length > 0 &&
|
||||
appStrings && Object.keys(appStrings).length > 0 &&
|
||||
modStrings && Object.keys(modStrings).length > 0 &&
|
||||
appListStrings && Object.keys(appListStrings).length > 0) {
|
||||
appListStrings && Object.keys(appListStrings).length > 0 &&
|
||||
userPreferences['navigation_paradigm'] && userPreferences['navigation_paradigm'].toString() == "m"
|
||||
) {
|
||||
this.navbar.resetMenu();
|
||||
this.navbar.buildMenu(tabs, modules, appStrings, modStrings, appListStrings, this.menuItemThreshold);
|
||||
this.navbar.buildTabMenu(tabs, modules, appStrings, modStrings, appListStrings, this.menuItemThreshold);
|
||||
}
|
||||
|
||||
if (tabs && tabs.length > 0 &&
|
||||
modules && Object.keys(modules).length > 0 &&
|
||||
appStrings && Object.keys(appStrings).length > 0 &&
|
||||
modStrings && Object.keys(modStrings).length > 0 &&
|
||||
appListStrings && Object.keys(appListStrings).length > 0 &&
|
||||
userPreferences['navigation_paradigm'] && userPreferences['navigation_paradigm'].toString() == "gm"
|
||||
) {
|
||||
this.navbar.resetMenu();
|
||||
this.navbar.buildGroupTabMenu(tabs, modules, appStrings, modStrings, appListStrings, this.menuItemThreshold, groupedTabs);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -61,7 +90,9 @@ export class NavbarUiComponent implements OnInit {
|
|||
modules,
|
||||
appStrings,
|
||||
appListStrings,
|
||||
modStrings
|
||||
modStrings,
|
||||
userPreferences,
|
||||
groupedTabs
|
||||
};
|
||||
})
|
||||
);
|
||||
|
@ -70,7 +101,8 @@ export class NavbarUiComponent implements OnInit {
|
|||
|
||||
constructor(protected navigationFacade: NavigationFacade,
|
||||
protected languageFacade: LanguageFacade,
|
||||
protected api: ApiService) {
|
||||
protected api: ApiService,
|
||||
protected userPreferenceFacade: UserPreferenceFacade) {
|
||||
const navbar = new NavbarAbstract();
|
||||
this.setNavbar(navbar);
|
||||
|
||||
|
@ -110,6 +142,7 @@ export class NavbarUiComponent implements OnInit {
|
|||
ngOnInit(): void {
|
||||
const navbar = new NavbarAbstract();
|
||||
this.setNavbar(navbar);
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {getTestBed, TestBed} from '@angular/core/testing';
|
||||
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
|
||||
import {appStateFacadeMock} from '@base/facades/app-state/app-state.facade.spec.mock';
|
||||
import {take} from "rxjs/operators";
|
||||
|
||||
describe('AppState Facade', () => {
|
||||
let injector: TestBed;
|
||||
|
@ -15,7 +16,7 @@ describe('AppState Facade', () => {
|
|||
it('#updateLoading',
|
||||
(done: DoneFn) => {
|
||||
service.updateLoading(true);
|
||||
service.loading$.subscribe(loading => {
|
||||
service.loading$.pipe(take(1)).subscribe(loading => {
|
||||
expect(loading).toEqual(true);
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -32,7 +32,6 @@ let cache$: Observable<any> = null;
|
|||
providedIn: 'root',
|
||||
})
|
||||
export class SystemConfigFacade {
|
||||
|
||||
protected store = new BehaviorSubject<SystemConfigs>(internalState);
|
||||
protected state$ = this.store.asObservable();
|
||||
protected resourceName = 'systemConfigs';
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import {Observable, of} from 'rxjs';
|
||||
import {shareReplay} from 'rxjs/operators';
|
||||
import {CollectionGQL} from '@services/api/graphql-api/api.collection.get';
|
||||
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
|
||||
|
||||
export const userPreferenceMockData = {
|
||||
userPreferences: {
|
||||
global: {
|
||||
id: '/api/user-preferences/global',
|
||||
_id: 'global',
|
||||
value: null,
|
||||
items: {
|
||||
calendar_publish_key: 'cc360e7b-c31c-5a12-4716-5e692263f2c8',
|
||||
swap_shortcuts: '',
|
||||
navigation_paradigm: 'gm',
|
||||
sort_modules_by_name: '',
|
||||
subpanel_tabs: '',
|
||||
count_collapsed_subpanels: '',
|
||||
module_favicon: '',
|
||||
no_opps: 'off',
|
||||
timezone: 'UTC',
|
||||
ut: '1',
|
||||
mail_smtpserver: '',
|
||||
mail_smtpport: '25',
|
||||
mail_smtpuser: '',
|
||||
mail_smtppass: '',
|
||||
use_real_names: 'off',
|
||||
mail_smtpauth_req: '',
|
||||
mail_smtpssl: 0,
|
||||
email_show_counts: 0,
|
||||
user_theme: 'SuiteP',
|
||||
editor_type: 'mozaik',
|
||||
reminder_time: '60',
|
||||
email_reminder_time: '60',
|
||||
reminder_checked: '0',
|
||||
email_reminder_checked: '0',
|
||||
currency: '-99',
|
||||
default_currency_significant_digits: '2',
|
||||
num_grp_sep: ',',
|
||||
dec_sep: '.',
|
||||
fdow: '0',
|
||||
datef: 'm/d/Y',
|
||||
timef: 'H:i',
|
||||
default_locale_name_format: 's f l',
|
||||
export_delimiter: ',',
|
||||
default_export_charset: 'UTF-8',
|
||||
email_link_type: 'sugar',
|
||||
subtheme: 'Dawn'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class UserPreferenceRecordGQLSpy extends CollectionGQL {
|
||||
|
||||
constructor() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public fetchAll(module: string, metadata: { fields: string[] }): Observable<any> {
|
||||
const data = {
|
||||
data: {
|
||||
userPreferences: {
|
||||
edges: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(userPreferenceMockData.userPreferences).forEach(key => {
|
||||
data.data.userPreferences.edges.push({
|
||||
node: userPreferenceMockData.userPreferences[key]
|
||||
});
|
||||
});
|
||||
|
||||
return of(data).pipe(shareReplay());
|
||||
}
|
||||
}
|
||||
|
||||
export const userPreferenceFacadeMock = new UserPreferenceFacade(new UserPreferenceRecordGQLSpy());
|
129
core/app/src/facades/user-preference/user-preference.facade.ts
Normal file
129
core/app/src/facades/user-preference/user-preference.facade.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {map, distinctUntilChanged, tap, shareReplay} from 'rxjs/operators';
|
||||
|
||||
import {CollectionGQL} from '@services/api/graphql-api/api.collection.get';
|
||||
|
||||
export interface UserPreference {
|
||||
id: string;
|
||||
_id: string;
|
||||
value: string;
|
||||
items: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface UserPreferenceMap {
|
||||
[key: string]: UserPreference;
|
||||
}
|
||||
|
||||
export interface UserPreferences {
|
||||
userPreferences: UserPreferenceMap;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
|
||||
let internalState: UserPreferences = {
|
||||
userPreferences: {},
|
||||
loading: false
|
||||
};
|
||||
|
||||
let cache$: Observable<any> = null;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UserPreferenceFacade {
|
||||
protected store = new BehaviorSubject<UserPreferences>(internalState);
|
||||
protected state$ = this.store.asObservable();
|
||||
protected resourceName = 'userPreferences';
|
||||
protected fieldsMetadata = {
|
||||
fields: [
|
||||
'id',
|
||||
'_id',
|
||||
'value',
|
||||
'items'
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Public long-lived observable streams
|
||||
*/
|
||||
userPreferences$ = this.state$.pipe(map(state => state.userPreferences), distinctUntilChanged());
|
||||
loading$ = this.state$.pipe(map(state => state.loading));
|
||||
|
||||
constructor(private collectionGQL: CollectionGQL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Api
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Initial UserPreferences load if not cached and update state.
|
||||
* Returns observable to be used in resolver if needed
|
||||
*
|
||||
* @returns Observable<any>
|
||||
*/
|
||||
public load(): Observable<any> {
|
||||
this.updateState({...internalState, loading: true});
|
||||
|
||||
return this.getUserPreferences().pipe(
|
||||
tap(userPreferences => {
|
||||
this.updateState({...internalState, userPreferences, loading: false});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update the state
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
protected updateState(state: UserPreferences) {
|
||||
this.store.next(internalState = state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UserPreferences cached Observable or call the backend
|
||||
*
|
||||
* @return Observable<any>
|
||||
*/
|
||||
protected getUserPreferences(): Observable<any> {
|
||||
|
||||
if (cache$ == null) {
|
||||
cache$ = this.fetch().pipe(
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
return cache$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the User Preferences from the backend
|
||||
*
|
||||
* @returns Observable<any>
|
||||
*/
|
||||
protected fetch(): Observable<any> {
|
||||
|
||||
return this.collectionGQL
|
||||
.fetchAll(this.resourceName, this.fieldsMetadata).pipe(map(({data}) => {
|
||||
const userPreferences: UserPreferenceMap = {};
|
||||
|
||||
if (data.userPreferences && data.userPreferences.edges) {
|
||||
data.userPreferences.edges.forEach((edge) => {
|
||||
for (const key in edge.node.items) {
|
||||
userPreferences[key] = edge.node.items[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return userPreferences;
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {
|
||||
Resolve,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot
|
||||
} from '@angular/router';
|
||||
|
||||
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
|
||||
import {flatMap} from 'rxjs/operators';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class UserPreferenceResolver implements Resolve<any> {
|
||||
|
||||
constructor(private userPreferenceFacade: UserPreferenceFacade) {
|
||||
}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
return this.userPreferenceFacade.load();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import {Observable, of} from 'rxjs';
|
||||
import {shareReplay} from 'rxjs/operators';
|
||||
import {RecordMutationGQL} from '@services/api/graphql-api/api.record.create';
|
||||
import {FetchResult} from 'apollo-link';
|
||||
import {ProcessService} from '@services/process/process.service';
|
||||
import {appStateFacadeMock} from '@base/facades/app-state/app-state.facade.spec.mock';
|
||||
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
|
||||
|
||||
export const recoverPasswordMockData = {
|
||||
data: {
|
||||
createProcess: {
|
||||
process: {
|
||||
_id: 'recover-password',
|
||||
status: 'success',
|
||||
async: false,
|
||||
type: 'recover-password',
|
||||
messages: [
|
||||
'LBL_RECOVER_PASSWORD_SUCCESS'
|
||||
]
|
||||
},
|
||||
clientMutationId: null
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RecoverPasswordRecordMutationGQLSpy extends RecordMutationGQL {
|
||||
constructor() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public create(graphqlModuleName: string,
|
||||
coreModuleName: string,
|
||||
input: { [key: string]: any },
|
||||
metadata: { fields: string[] }): Observable<FetchResult<any>> {
|
||||
|
||||
return of(recoverPasswordMockData).pipe(shareReplay());
|
||||
}
|
||||
}
|
||||
|
||||
const processServiceMock = new ProcessService(new RecoverPasswordRecordMutationGQLSpy());
|
||||
|
||||
export const recoverPasswordMock = new RecoverPasswordService(processServiceMock, appStateFacadeMock);
|
|
@ -0,0 +1,26 @@
|
|||
import {getTestBed, TestBed} from '@angular/core/testing';
|
||||
import {
|
||||
recoverPasswordMock,
|
||||
recoverPasswordMockData
|
||||
} from '@services/process/processes/recover-password/recover-passoword.spec.mock';
|
||||
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
|
||||
|
||||
describe('Reset Password service', () => {
|
||||
let injector: TestBed;
|
||||
const service: RecoverPasswordService = recoverPasswordMock;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
|
||||
injector = getTestBed();
|
||||
});
|
||||
|
||||
it('#run',
|
||||
(done: DoneFn) => {
|
||||
service.run('john.doe', 'jonh.doe@example.com').subscribe(data => {
|
||||
expect(data).toEqual(jasmine.objectContaining(recoverPasswordMockData.data.createProcess.process));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
}
|
||||
|
||||
.action-group .dropdown-menu li a:hover {
|
||||
background: none;
|
||||
background: green;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ ul.main li.nav-item,
|
|||
padding: 0em 1em;
|
||||
}
|
||||
|
||||
.dropdown-menu .nav-item:hover {
|
||||
.dropdown-menu .nav-item:hover, .dropdown-menu .nav-item .action-link:hover {
|
||||
background-color: $dull-orange;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,8 +73,8 @@ class NavbarHandler extends LegacyHandler
|
|||
$nameMap = $this->createFrontendNameMap($legacyTabNames);
|
||||
|
||||
$navbar->tabs = array_values($nameMap);
|
||||
$navbar->groupedTabs = $this->fetchGroupedNavTabs($nameMap);
|
||||
$navbar->modules = $this->buildModuleInfo($sugarView, $nameMap);
|
||||
$navbar->groupedTabs = $this->fetchGroupedNavTabs();
|
||||
$navbar->userActionMenu = $this->fetchUserActionMenu();
|
||||
|
||||
$this->close();
|
||||
|
@ -95,9 +95,10 @@ class NavbarHandler extends LegacyHandler
|
|||
|
||||
/**
|
||||
* Fetch Grouped Navigation tabs
|
||||
* @param array $nameMap
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchGroupedNavTabs(): array
|
||||
protected function fetchGroupedNavTabs(array &$nameMap): array
|
||||
{
|
||||
$output = [];
|
||||
global $current_language;
|
||||
|
@ -119,6 +120,11 @@ class NavbarHandler extends LegacyHandler
|
|||
|
||||
foreach ($subModules['modules'] as $submodule => $submoduleLabel) {
|
||||
$submoduleArray[] = $moduleNameMap[$submodule];
|
||||
|
||||
//Add missing module to the module info list
|
||||
if (empty($nameMap[$submodule])){
|
||||
$nameMap[$submodule] = $moduleNameMap[$submodule];
|
||||
}
|
||||
}
|
||||
|
||||
sort($submoduleArray);
|
||||
|
|
194
core/legacy/UserPreferenceHandler.php
Normal file
194
core/legacy/UserPreferenceHandler.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace SuiteCRM\Core\Legacy;
|
||||
|
||||
use ApiPlatform\Core\Exception\ItemNotFoundException;
|
||||
use App\Entity\UserPreference;
|
||||
use DBManagerFactory;
|
||||
|
||||
use Exception;
|
||||
|
||||
class UserPreferenceHandler extends LegacyHandler
|
||||
{
|
||||
protected const MSG_USER_PREFERENCE_NOT_FOUND = 'Not able to find user preference key: ';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $exposedUserPreferences = [];
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
protected $current_user = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $userPreference = [];
|
||||
|
||||
/**
|
||||
* UserPreferenceHandler constructor.
|
||||
* @param string $projectDir
|
||||
* @param string $legacyDir
|
||||
* @param string $legacySessionName
|
||||
* @param string $defaultSessionName
|
||||
* @param array $exposedUserPreferences
|
||||
*/
|
||||
public function __construct(
|
||||
string $projectDir,
|
||||
string $legacyDir,
|
||||
string $legacySessionName,
|
||||
string $defaultSessionName,
|
||||
array $exposedUserPreferences
|
||||
) {
|
||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName);
|
||||
|
||||
$this->init();
|
||||
|
||||
$this->exposedUserPreferences = $exposedUserPreferences;
|
||||
|
||||
global $current_user;
|
||||
get_sugar_config_defaults();
|
||||
|
||||
$user = new \User();
|
||||
$current_user = $user->retrieve(1);
|
||||
|
||||
$db = DBManagerFactory::getInstance();
|
||||
|
||||
$quotedUserId = $db->quoted($current_user->id);
|
||||
$result = $db->query("SELECT contents, category FROM user_preferences WHERE assigned_user_id=" . $quotedUserId . " AND deleted = 0", false, 'Failed to load user preferences');
|
||||
|
||||
while ($row = $db->fetchByAssoc($result)) {
|
||||
$category = $row['category'];
|
||||
$this->preferences[$category] = unserialize(base64_decode($row['contents']));
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all exposed user preferences
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUserPreferences(): array
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$userPreferences = [];
|
||||
|
||||
foreach ($this->exposedUserPreferences as $key => $value) {
|
||||
$userPreference = $this->loadUserPreference($key);
|
||||
if (!empty($userPreference)) {
|
||||
$userPreferences[] = $userPreference;
|
||||
}
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $userPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user preference
|
||||
* @param string $key
|
||||
* @return UserPreference|null
|
||||
*/
|
||||
public function getUserPreference(string $key): ?UserPreference
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$userPreference = $this->loadUserPreference($key);
|
||||
|
||||
$this->close();
|
||||
|
||||
return $userPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user preference with given $key
|
||||
* @param $key
|
||||
* @return UserPreference|null
|
||||
*/
|
||||
protected function loadUserPreference(string $key): ?UserPreference
|
||||
{
|
||||
global $current_user;
|
||||
|
||||
if (empty($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($current_user->id)) {
|
||||
throw new Exception('No user logged in.');
|
||||
}
|
||||
|
||||
|
||||
if (!isset($this->preferences[$key])) {
|
||||
throw new ItemNotFoundException(self::MSG_USER_PREFERENCE_NOT_FOUND . "'$key'");
|
||||
}
|
||||
|
||||
$userPreference = new UserPreference();
|
||||
$userPreference->setId($key);
|
||||
|
||||
if (!isset($this->preferences[$key])) {
|
||||
return $userPreference;
|
||||
}
|
||||
|
||||
if (is_array($this->preferences[$key])) {
|
||||
$items = $this->preferences[$key];
|
||||
|
||||
if (is_array($this->exposedUserPreferences[$key])) {
|
||||
$items = $this->filterItems($this->preferences[$key], $this->exposedUserPreferences[$key]);
|
||||
}
|
||||
|
||||
$userPreference->setItems($items);
|
||||
|
||||
return $userPreference;
|
||||
}
|
||||
|
||||
$userPreference->setValue($this->preferences[$key]);
|
||||
|
||||
return $userPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to retrieve only exposed items
|
||||
* @param array $allItems
|
||||
* @param array $exposed
|
||||
* @return array
|
||||
*/
|
||||
protected function filterItems(array $allItems, array $exposed): array
|
||||
{
|
||||
$items = [];
|
||||
|
||||
if (empty($exposed)) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
foreach ($allItems as $key => $value) {
|
||||
|
||||
if (!isset($exposed[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($allItems[$key])) {
|
||||
|
||||
$subItems = $allItems[$key];
|
||||
|
||||
if (is_array($exposed[$key])) {
|
||||
|
||||
$subItems = $this->filterItems($allItems[$key], $exposed[$key]);
|
||||
}
|
||||
|
||||
$items[$key] = $subItems;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$items[$key] = $value;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataProvider;
|
||||
|
||||
use ApiPlatform\Core\DataProvider\ArrayPaginator;
|
||||
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
|
||||
use ApiPlatform\Core\DataProvider\PaginatorInterface;
|
||||
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
|
||||
use App\Entity\UserPreference;
|
||||
use SuiteCRM\Core\Legacy\UserPreferenceHandler;
|
||||
|
||||
/**
|
||||
* Class UserPreferenceCollectionDataProvider
|
||||
*/
|
||||
class UserPreferenceCollectionDataProvider implements ContextAwareCollectionDataProviderInterface,
|
||||
RestrictedDataProviderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var UserPreferenceHandler
|
||||
*/
|
||||
private $userPreferenceHandler;
|
||||
|
||||
/**
|
||||
* UserPreferenceCollectionDataProvider constructor.
|
||||
* @param UserPreferenceHandler $userPreferenceHandler
|
||||
*/
|
||||
public function __construct(UserPreferenceHandler $userPreferenceHandler)
|
||||
{
|
||||
$this->userPreferenceHandler = $userPreferenceHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define supported Resource Classes
|
||||
* @param string $resourceClass
|
||||
* @param string|null $operationName
|
||||
* @param array $context
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
|
||||
{
|
||||
return UserPreference::class === $resourceClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollection(
|
||||
string $resourceClass,
|
||||
string $operationName = null,
|
||||
array $context = []
|
||||
): PaginatorInterface {
|
||||
$userPreferences = $this->userPreferenceHandler->getAllUserPreferences();
|
||||
|
||||
return new ArrayPaginator($userPreferences, 0, count($userPreferences));
|
||||
}
|
||||
}
|
58
core/src/DataProvider/UserPreferenceItemDataProvider.php
Normal file
58
core/src/DataProvider/UserPreferenceItemDataProvider.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataProvider;
|
||||
|
||||
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
|
||||
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
|
||||
use App\Entity\UserPreference;
|
||||
use SuiteCRM\Core\Legacy\UserPreferenceHandler;
|
||||
|
||||
/**
|
||||
* Class UserPreferenceItemDataProvider
|
||||
*/
|
||||
final class UserPreferenceItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var UserPreferenceHandler
|
||||
*/
|
||||
private $userPreferenceHandler;
|
||||
|
||||
/**
|
||||
* UserPreferenceItemDataProvider constructor.
|
||||
* @param UserPreferenceHandler $userPreferenceHandler
|
||||
*/
|
||||
public function __construct(UserPreferenceHandler $userPreferenceHandler)
|
||||
{
|
||||
$this->userPreferenceHandler = $userPreferenceHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined supported resources
|
||||
* @param string $resourceClass
|
||||
* @param string|null $operationName
|
||||
* @param array $context
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
|
||||
{
|
||||
return UserPreference::class === $resourceClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user preference
|
||||
* @param string $resourceClass
|
||||
* @param array|int|string $id
|
||||
* @param string|null $operationName
|
||||
* @param array $context
|
||||
* @return UserPreference|null
|
||||
*/
|
||||
public function getItem(
|
||||
string $resourceClass,
|
||||
$id,
|
||||
string $operationName = null,
|
||||
array $context = []
|
||||
): ?UserPreference {
|
||||
return $this->userPreferenceHandler->getUserPreference($id);
|
||||
}
|
||||
}
|
105
core/src/Entity/UserPreference.php
Normal file
105
core/src/Entity/UserPreference.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Core\Annotation\ApiResource;
|
||||
use ApiPlatform\Core\Annotation\ApiProperty;
|
||||
|
||||
/**
|
||||
* @ApiResource(
|
||||
* attributes={"security"="is_granted('ROLE_USER')"},
|
||||
* itemOperations={
|
||||
* "get"
|
||||
* },
|
||||
* collectionOperations={
|
||||
* "get"
|
||||
* },
|
||||
* graphql={
|
||||
* "item_query",
|
||||
* "collection_query"
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class UserPreference
|
||||
{
|
||||
/**
|
||||
* @ApiProperty(identifier=true)
|
||||
* @var string|null
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ApiProperty
|
||||
* @var string|null
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @ApiProperty
|
||||
* @var array
|
||||
*/
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* Get Id
|
||||
* @return string|null
|
||||
*/
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Id
|
||||
* @param string|null $id
|
||||
* @return UserPreference
|
||||
*/
|
||||
public function setId(?string $id): UserPreference
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value
|
||||
* @return string|null
|
||||
*/
|
||||
public function getValue(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value
|
||||
* @param string|null $value
|
||||
* @return UserPreference
|
||||
*/
|
||||
public function setValue(?string $value): UserPreference
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items
|
||||
* @return array
|
||||
*/
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Items
|
||||
* @param array $items
|
||||
* @return UserPreference
|
||||
*/
|
||||
public function setItems(array $items): UserPreference
|
||||
{
|
||||
$this->items = $items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ class AppListStringsHandlerTest extends Unit
|
|||
*/
|
||||
protected $handler;
|
||||
|
||||
protected function _before()
|
||||
protected function _before(): void
|
||||
{
|
||||
$projectDir = codecept_root_dir();
|
||||
$legacyDir = $projectDir . '/legacy';
|
||||
|
@ -31,7 +31,7 @@ class AppListStringsHandlerTest extends Unit
|
|||
/**
|
||||
* Test Invalid language handling in AppListStringsHandler
|
||||
*/
|
||||
public function testInvalidLanguageCheck()
|
||||
public function testInvalidLanguageCheck(): void
|
||||
{
|
||||
$this->expectException(ItemNotFoundException::class);
|
||||
$this->handler->getAppListStrings('invalid_lang');
|
||||
|
@ -40,7 +40,7 @@ class AppListStringsHandlerTest extends Unit
|
|||
/**
|
||||
* Test default language retrieval in AppListStringsHandler
|
||||
*/
|
||||
public function testDefaultLanguageKey()
|
||||
public function testDefaultLanguageKey(): void
|
||||
{
|
||||
$appListStrings = $this->handler->getAppListStrings('en_us');
|
||||
static::assertNotNull($appListStrings);
|
||||
|
@ -56,7 +56,7 @@ class AppListStringsHandlerTest extends Unit
|
|||
* @param string $labelKey
|
||||
* @param AppListStrings $appStrings
|
||||
*/
|
||||
protected function assertLanguageListKey(string $listKey, string $labelKey, AppListStrings $appStrings)
|
||||
protected function assertLanguageListKey(string $listKey, string $labelKey, AppListStrings $appStrings): void
|
||||
{
|
||||
static::assertArrayHasKey($listKey, $appStrings->getItems());
|
||||
static::assertNotEmpty($appStrings->getItems()[$listKey]);
|
||||
|
|
|
@ -17,7 +17,7 @@ class AppStringsHandlerTest extends Unit
|
|||
*/
|
||||
protected $handler;
|
||||
|
||||
protected function _before()
|
||||
protected function _before(): void
|
||||
{
|
||||
$projectDir = codecept_root_dir();
|
||||
$legacyDir = $projectDir . '/legacy';
|
||||
|
@ -31,7 +31,7 @@ class AppStringsHandlerTest extends Unit
|
|||
/**
|
||||
* Test Invalid language handling in AppStringsHandler
|
||||
*/
|
||||
public function testInvalidLanguageCheck()
|
||||
public function testInvalidLanguageCheck(): void
|
||||
{
|
||||
$this->expectException(ItemNotFoundException::class);
|
||||
$this->handler->getAppStrings('invalid_lang');
|
||||
|
@ -40,7 +40,7 @@ class AppStringsHandlerTest extends Unit
|
|||
/**
|
||||
* Test default language retrieval in AppStringsHandler
|
||||
*/
|
||||
public function testDefaultLanguageKey()
|
||||
public function testDefaultLanguageKey(): void
|
||||
{
|
||||
$appStrings = $this->handler->getAppStrings('en_us');
|
||||
static::assertNotNull($appStrings);
|
||||
|
@ -62,7 +62,7 @@ class AppStringsHandlerTest extends Unit
|
|||
* @param string $key
|
||||
* @param AppStrings $appStrings
|
||||
*/
|
||||
protected function assertLanguageKey($key, AppStrings $appStrings)
|
||||
protected function assertLanguageKey($key, AppStrings $appStrings): void
|
||||
{
|
||||
static::assertArrayHasKey($key, $appStrings->getItems());
|
||||
static::assertNotEmpty($appStrings->getItems()[$key]);
|
||||
|
|
|
@ -18,7 +18,7 @@ class ModStringsHandlerTest extends Unit
|
|||
*/
|
||||
protected $handler;
|
||||
|
||||
protected function _before()
|
||||
protected function _before(): void
|
||||
{
|
||||
$legacyModuleNameMap = [
|
||||
'Home' => [
|
||||
|
@ -366,7 +366,7 @@ class ModStringsHandlerTest extends Unit
|
|||
/**
|
||||
* Test Invalid language handling in ModStringsHandler
|
||||
*/
|
||||
public function testInvalidLanguageCheck()
|
||||
public function testInvalidLanguageCheck(): void
|
||||
{
|
||||
$this->expectException(ItemNotFoundException::class);
|
||||
$this->handler->getModStrings('invalid_lang');
|
||||
|
@ -375,7 +375,7 @@ class ModStringsHandlerTest extends Unit
|
|||
/**
|
||||
* Test default language retrieval in ModStringsHandler
|
||||
*/
|
||||
public function testDefaultLanguageKey()
|
||||
public function testDefaultLanguageKey(): void
|
||||
{
|
||||
$modStrings = $this->handler->getModStrings('en_us');
|
||||
static::assertNotNull($modStrings);
|
||||
|
@ -392,7 +392,7 @@ class ModStringsHandlerTest extends Unit
|
|||
* @param string $key
|
||||
* @param ModStrings $modStrings
|
||||
*/
|
||||
protected function assertLanguageKey($module, $key, ModStrings $modStrings)
|
||||
protected function assertLanguageKey($module, $key, ModStrings $modStrings): void
|
||||
{
|
||||
static::assertArrayHasKey($module, $modStrings->getItems());
|
||||
static::assertNotEmpty($modStrings->getItems()[$module]);
|
||||
|
|
|
@ -2146,6 +2146,45 @@ final class NavbarTest extends Unit
|
|||
"icon" => "view"
|
||||
]
|
||||
]
|
||||
],
|
||||
'bugs' => [
|
||||
'path' => 'bugs',
|
||||
'defaultRoute' => './#/bugs/index',
|
||||
'name' => 'bugs',
|
||||
'labelKey' => 'Bugs',
|
||||
'menu' => [
|
||||
0 => [
|
||||
'name' => 'Create',
|
||||
'labelKey' => 'LNK_NEW_BUG',
|
||||
'url' => './#/bugs/edit',
|
||||
'params' => [
|
||||
'return_module' => 'Bugs',
|
||||
'return_action' => 'DetailView',
|
||||
],
|
||||
'icon' => 'plus'
|
||||
],
|
||||
1 => [
|
||||
'name' => 'List',
|
||||
'labelKey' => 'LNK_BUG_LIST',
|
||||
'url' => './#/bugs/index',
|
||||
'params' => [
|
||||
'return_module' => 'Bugs',
|
||||
'return_action' => 'DetailView'
|
||||
],
|
||||
'icon' => 'view'
|
||||
],
|
||||
2 => [
|
||||
'name' => 'Import',
|
||||
'labelKey' => 'LNK_IMPORT_BUGS',
|
||||
'url' => './#/import/step1',
|
||||
'params' => [
|
||||
'import_module' => 'Bugs',
|
||||
'return_module' => 'Bugs',
|
||||
'return_action' => 'index'
|
||||
],
|
||||
'icon' => 'download'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class SystemConfigHandlerTest extends Unit
|
|||
*/
|
||||
protected $handler;
|
||||
|
||||
protected function _before()
|
||||
protected function _before(): void
|
||||
{
|
||||
$exposedSystemConfigs = [
|
||||
'default_language' => true,
|
||||
|
@ -46,7 +46,7 @@ class SystemConfigHandlerTest extends Unit
|
|||
/**
|
||||
* Test empty config key handling in SystemConfigHandler
|
||||
*/
|
||||
public function testEmptySystemConfigKeyCheck()
|
||||
public function testEmptySystemConfigKeyCheck(): void
|
||||
{
|
||||
$defaultLanguage = $this->handler->getSystemConfig('');
|
||||
static::assertNull($defaultLanguage);
|
||||
|
@ -55,7 +55,7 @@ class SystemConfigHandlerTest extends Unit
|
|||
/**
|
||||
* Test invalid config key handling in SystemConfigHandler
|
||||
*/
|
||||
public function testInvalidExposedSystemConfigCheck()
|
||||
public function testInvalidExposedSystemConfigCheck(): void
|
||||
{
|
||||
$this->expectException(ItemNotFoundException::class);
|
||||
$this->handler->getSystemConfig('dbconfig');
|
||||
|
@ -64,7 +64,7 @@ class SystemConfigHandlerTest extends Unit
|
|||
/**
|
||||
* Test retrieval of first level system config in SystemConfigHandler
|
||||
*/
|
||||
public function testGetValidOneLevelSystemConfig()
|
||||
public function testGetValidOneLevelSystemConfig(): void
|
||||
{
|
||||
$defaultLanguage = $this->handler->getSystemConfig('default_language');
|
||||
static::assertNotNull($defaultLanguage);
|
||||
|
@ -76,7 +76,7 @@ class SystemConfigHandlerTest extends Unit
|
|||
/**
|
||||
* Test retrieval of second level deep system config in SystemConfigHandler
|
||||
*/
|
||||
public function testGetValidTwoLevelSystemConfig()
|
||||
public function testGetValidTwoLevelSystemConfig(): void
|
||||
{
|
||||
$defaultLanguage = $this->handler->getSystemConfig('passwordsetting');
|
||||
static::assertNotNull($defaultLanguage);
|
||||
|
@ -97,7 +97,7 @@ class SystemConfigHandlerTest extends Unit
|
|||
/**
|
||||
* Test retrieval of third level deep system config in SystemConfigHandler
|
||||
*/
|
||||
public function testGetValidThreeLevelSystemConfig()
|
||||
public function testGetValidThreeLevelSystemConfig(): void
|
||||
{
|
||||
$defaultLanguage = $this->handler->getSystemConfig('search');
|
||||
static::assertNotNull($defaultLanguage);
|
||||
|
|
70
tests/unit/legacy/UserPreferencesHandlerTest.php
Normal file
70
tests/unit/legacy/UserPreferencesHandlerTest.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php namespace App\Tests;
|
||||
|
||||
use ApiPlatform\Core\Exception\ItemNotFoundException;
|
||||
use Codeception\Test\Unit;
|
||||
use SuiteCRM\Core\Legacy\UserPreferenceHandler;
|
||||
|
||||
class UserPreferencesHandlerTest extends Unit
|
||||
{
|
||||
/**
|
||||
* @var UnitTester
|
||||
*/
|
||||
protected $tester;
|
||||
|
||||
/**
|
||||
* @var UserPreferenceHandler
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
protected function _before(): void
|
||||
{
|
||||
$exposedUserPreferences = [
|
||||
'global' => [
|
||||
'timezone' => true
|
||||
]
|
||||
];
|
||||
|
||||
$projectDir = codecept_root_dir();
|
||||
$legacyDir = $projectDir . '/legacy';
|
||||
$legacySessionName = 'LEGACYSESSID';
|
||||
$defaultSessionName = 'PHPSESSID';
|
||||
|
||||
$this->handler = new UserPreferenceHandler($projectDir, $legacyDir, $legacySessionName, $defaultSessionName,
|
||||
$exposedUserPreferences);
|
||||
}
|
||||
|
||||
// tests
|
||||
|
||||
/**
|
||||
* Test empty config key handling in UserPreferenceHandler
|
||||
*/
|
||||
public function testEmptyUserPreferenceKeyCheck(): void
|
||||
{
|
||||
$noKeyUserPreference = $this->handler->getUserPreference('');
|
||||
static::assertNull($noKeyUserPreference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid config key handling in UserPreferenceHandler
|
||||
*/
|
||||
public function testInvalidExposedUserPreferenceCheck(): void
|
||||
{
|
||||
$this->expectException(ItemNotFoundException::class);
|
||||
$this->handler->getUserPreference('dbconfig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test retrieval of first level user preference config in UserPreferenceHandler
|
||||
*/
|
||||
public function testGetUserPreferenceTimezone(): void
|
||||
{
|
||||
$userPref = $this->handler->getUserPreference('global');
|
||||
|
||||
static::assertNotNull($userPref);
|
||||
static::assertEquals('global', $userPref->getId());
|
||||
|
||||
$userPreferences = $userPref->getItems();
|
||||
|
||||
static::assertEquals('UTC', $userPreferences['timezone']);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue