mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 11:00:40 +08:00
Implement record-view routing
Signed-off-by: Dillon-Brown <dillon.brown@salesagility.com>
This commit is contained in:
parent
573fed6d2b
commit
c5ee6b4f8e
17 changed files with 610 additions and 6 deletions
|
@ -17,6 +17,11 @@ if (is_array($env = @include dirname(__DIR__) . '/.env.local.php') && (!isset($e
|
||||||
(new Dotenv(false))->loadEnv(dirname(__DIR__) . '/.env');
|
(new Dotenv(false))->loadEnv(dirname(__DIR__) . '/.env');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global annotations to ignore
|
||||||
|
Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredName('query');
|
||||||
|
Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredName('fields_array');
|
||||||
|
Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredName('absrtact');
|
||||||
|
|
||||||
$_SERVER += $_ENV;
|
$_SERVER += $_ENV;
|
||||||
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
|
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
|
||||||
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
|
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {ListComponent} from '@views/list/list.component';
|
||||||
import {LoginAuthGuard} from '@services/auth/login-auth-guard.service';
|
import {LoginAuthGuard} from '@services/auth/login-auth-guard.service';
|
||||||
import {BaseListResolver} from '@services/metadata/base-list.resolver';
|
import {BaseListResolver} from '@services/metadata/base-list.resolver';
|
||||||
import {BaseModuleResolver} from '@base/services/metadata/base-module.resolver';
|
import {BaseModuleResolver} from '@base/services/metadata/base-module.resolver';
|
||||||
|
import {BaseRecordResolver} from '@services/metadata/base-record.resolver';
|
||||||
|
import {RecordComponent} from '@views/record/record.component';
|
||||||
|
import {RecordViewGuard} from '@services/record-view/record-view-guard.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {[]} segments of url
|
* @param {[]} segments of url
|
||||||
|
@ -140,11 +143,12 @@ const routes: Routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':module/:action/:record',
|
path: ':module/:action/:record',
|
||||||
component: ClassicViewUiComponent,
|
component: RecordComponent,
|
||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard, RecordViewGuard],
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
resolve: {
|
resolve: {
|
||||||
legacyUrl: ClassicViewResolver,
|
view: BaseModuleResolver,
|
||||||
|
metadata: BaseRecordResolver
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
reuseRoute: false,
|
reuseRoute: false,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {ModuleTitleModule} from '@components/module-title/module-title.module';
|
||||||
import {ListHeaderModule} from '@components/list-header/list-header.module';
|
import {ListHeaderModule} from '@components/list-header/list-header.module';
|
||||||
import {ListcontainerUiModule} from '@components/list-container/list-container.module';
|
import {ListcontainerUiModule} from '@components/list-container/list-container.module';
|
||||||
import {ListModule} from '@views/list/list.module';
|
import {ListModule} from '@views/list/list.module';
|
||||||
|
import {RecordModule} from '@views/record/record.module';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
import {ErrorInterceptor} from '@services/auth/error.interceptor';
|
import {ErrorInterceptor} from '@services/auth/error.interceptor';
|
||||||
|
@ -56,6 +57,7 @@ import {BnNgIdleService} from 'bn-ng-idle';
|
||||||
ClassicViewUiModule,
|
ClassicViewUiModule,
|
||||||
FilterUiModule,
|
FilterUiModule,
|
||||||
ListModule,
|
ListModule,
|
||||||
|
RecordModule,
|
||||||
WidgetUiModule,
|
WidgetUiModule,
|
||||||
TableUiModule,
|
TableUiModule,
|
||||||
ModuleTitleModule,
|
ModuleTitleModule,
|
||||||
|
|
47
core/app/src/services/metadata/base-record.resolver.ts
Normal file
47
core/app/src/services/metadata/base-record.resolver.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ActivatedRouteSnapshot} from '@angular/router';
|
||||||
|
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||||
|
import {ActionNameMapper} from '@services/navigation/action-name-mapper/action-name-mapper.service';
|
||||||
|
import {SystemConfigStore} from '@base/store/system-config/system-config.store';
|
||||||
|
import {LanguageStore} from '@base/store/language/language.store';
|
||||||
|
import {NavigationStore} from '@base/store/navigation/navigation.store';
|
||||||
|
import {UserPreferenceStore} from '@base/store/user-preference/user-preference.store';
|
||||||
|
import {ThemeImagesStore} from '@base/store/theme-images/theme-images.store';
|
||||||
|
import {AppStateStore} from '@base/store/app-state/app-state.store';
|
||||||
|
import {MetadataStore} from '@store/metadata/metadata.store.service';
|
||||||
|
import {BaseModuleResolver} from '@services/metadata/base-module.resolver';
|
||||||
|
import {forkJoin, Observable} from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
export class BaseRecordResolver extends BaseModuleResolver {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected systemConfigStore: SystemConfigStore,
|
||||||
|
protected languageStore: LanguageStore,
|
||||||
|
protected navigationStore: NavigationStore,
|
||||||
|
protected metadataStore: MetadataStore,
|
||||||
|
protected userPreferenceStore: UserPreferenceStore,
|
||||||
|
protected themeImagesStore: ThemeImagesStore,
|
||||||
|
protected moduleNameMapper: ModuleNameMapper,
|
||||||
|
protected actionNameMapper: ActionNameMapper,
|
||||||
|
protected appStateStore: AppStateStore,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
systemConfigStore,
|
||||||
|
languageStore,
|
||||||
|
navigationStore,
|
||||||
|
userPreferenceStore,
|
||||||
|
themeImagesStore,
|
||||||
|
moduleNameMapper,
|
||||||
|
actionNameMapper,
|
||||||
|
appStateStore
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
return forkJoin({
|
||||||
|
base: super.resolve(route),
|
||||||
|
metadata: this.metadataStore.load(route.params.module, this.metadataStore.getMetadataTypes()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ActivatedRouteSnapshot, CanActivate, Router} from '@angular/router';
|
||||||
|
import {Observable, throwError} from 'rxjs';
|
||||||
|
import {catchError, map} from 'rxjs/operators';
|
||||||
|
import {MessageService} from '@services/message/message.service';
|
||||||
|
import {RecordViewGQL} from '@store/record-view/api.record.get';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class RecordViewGuard implements CanActivate {
|
||||||
|
|
||||||
|
protected fieldsMetadata = {
|
||||||
|
fields: [
|
||||||
|
'_id',
|
||||||
|
'id',
|
||||||
|
'record'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected message: MessageService,
|
||||||
|
protected recordViewGQL: RecordViewGQL,
|
||||||
|
protected router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
|
||||||
|
return this.recordViewGQL.fetch(route.params.module, route.params.record, this.fieldsMetadata)
|
||||||
|
.pipe(
|
||||||
|
map(({data}) => {
|
||||||
|
const id = data.getRecordView.record.id;
|
||||||
|
if (id) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.message.addDangerMessageByKey('LBL_RECORD_DOES_NOT_EXIST');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(err => throwError(err)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
46
core/app/src/store/record-view/api.record.get.ts
Normal file
46
core/app/src/store/record-view/api.record.get.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Apollo} from 'apollo-angular';
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {ApolloQueryResult} from 'apollo-client';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class RecordViewGQL {
|
||||||
|
|
||||||
|
constructor(private apollo: Apollo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from backend
|
||||||
|
*
|
||||||
|
* @param {string} module name
|
||||||
|
* @param {string} record id
|
||||||
|
* @param {object} metadata with the fields to ask for
|
||||||
|
* @returns {object} Observable<ApolloQueryResult<any>>
|
||||||
|
*/
|
||||||
|
public fetch(
|
||||||
|
module: string,
|
||||||
|
record: string,
|
||||||
|
metadata: { fields: string[] }
|
||||||
|
): Observable<ApolloQueryResult<any>> {
|
||||||
|
const fields = metadata.fields;
|
||||||
|
|
||||||
|
const queryOptions = {
|
||||||
|
query: gql`
|
||||||
|
query recordView($module: String!, $record: String!) {
|
||||||
|
getRecordView(module: $module, record: $record) {
|
||||||
|
${fields.join('\n')}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
module,
|
||||||
|
record,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.apollo.query(queryOptions);
|
||||||
|
}
|
||||||
|
}
|
25
core/app/src/store/record-view/record-view.store.ts
Normal file
25
core/app/src/store/record-view/record-view.store.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {AppData, ViewStore} from '@store/view/view.store';
|
||||||
|
import {Metadata} from '@store/metadata/metadata.store.service';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
|
export interface RecordViewModel {
|
||||||
|
appData: AppData;
|
||||||
|
metadata: Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RecordViewStore extends ViewStore {
|
||||||
|
vm$: Observable<RecordViewModel>;
|
||||||
|
vm: RecordViewModel;
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean destroy
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
}
|
4
core/app/views/record/record.component.html
Normal file
4
core/app/views/record/record.component.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<!-- Start Record View Section -->
|
||||||
|
<div class="record-view" *ngIf="(vm$ | async) as vm">
|
||||||
|
</div>
|
||||||
|
<!-- End Record View Section -->
|
61
core/app/views/record/record.component.spec.ts
Normal file
61
core/app/views/record/record.component.spec.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {ApolloTestingModule} from 'apollo-angular/testing';
|
||||||
|
import {ImageModule} from '@components/image/image.module';
|
||||||
|
import {DynamicModule} from 'ng-dynamic-component';
|
||||||
|
import {FieldModule} from '@fields/field.module';
|
||||||
|
import {DropdownButtonModule} from '@components/dropdown-button/dropdown-button.module';
|
||||||
|
import {SortButtonModule} from '@components/sort-button/sort-button.module';
|
||||||
|
import {RecordViewStore} from '@store/record-view/record-view.store';
|
||||||
|
import {RecordComponent} from '@views/record/record.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'record-test-host-component',
|
||||||
|
template: '<scrm-record></scrm-record>'
|
||||||
|
})
|
||||||
|
class RecordTestHostComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('RecordComponent', () => {
|
||||||
|
let testHostComponent: RecordTestHostComponent;
|
||||||
|
let testHostFixture: ComponentFixture<RecordTestHostComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
/* eslint-disable camelcase, @typescript-eslint/camelcase */
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
ImageModule,
|
||||||
|
ApolloTestingModule,
|
||||||
|
DynamicModule,
|
||||||
|
FieldModule,
|
||||||
|
DropdownButtonModule,
|
||||||
|
DropdownButtonModule,
|
||||||
|
SortButtonModule
|
||||||
|
],
|
||||||
|
declarations: [RecordComponent, RecordTestHostComponent],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: RecordViewStore
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
/* eslint-enable camelcase, @typescript-eslint/camelcase */
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testHostFixture = TestBed.createComponent(RecordTestHostComponent);
|
||||||
|
testHostComponent = testHostFixture.componentInstance;
|
||||||
|
testHostFixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(testHostComponent).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
30
core/app/views/record/record.component.ts
Normal file
30
core/app/views/record/record.component.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {AppStateStore} from '@store/app-state/app-state.store';
|
||||||
|
import {Observable, Subscription} from 'rxjs';
|
||||||
|
import {RecordViewModel, RecordViewStore} from '@store/record-view/record-view.store';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'scrm-record',
|
||||||
|
templateUrl: './record.component.html',
|
||||||
|
styleUrls: [],
|
||||||
|
providers: [RecordViewStore]
|
||||||
|
})
|
||||||
|
export class RecordComponent implements OnInit, OnDestroy {
|
||||||
|
recordSub: Subscription;
|
||||||
|
vm$: Observable<RecordViewModel> = null;
|
||||||
|
|
||||||
|
constructor(protected appState: AppStateStore, protected recordStore: RecordViewStore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.vm$ = this.recordStore.vm$;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.recordSub) {
|
||||||
|
this.recordSub.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recordStore.destroy();
|
||||||
|
}
|
||||||
|
}
|
15
core/app/views/record/record.module.ts
Normal file
15
core/app/views/record/record.module.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {RecordComponent} from './record.component';
|
||||||
|
import {FieldModule} from '@fields/field.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RecordComponent],
|
||||||
|
exports: [RecordComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FieldModule
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class RecordModule {
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
// stub for record view
|
|
111
core/legacy/RecordViewHandler.php
Normal file
111
core/legacy/RecordViewHandler.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SuiteCRM\Core\Legacy;
|
||||||
|
|
||||||
|
use App\Entity\RecordView;
|
||||||
|
use App\Service\ModuleNameMapperInterface;
|
||||||
|
use App\Service\RecordViewProviderInterface;
|
||||||
|
use BeanFactory;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SugarBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RecordViewHandler
|
||||||
|
* @package SuiteCRM\Core\Legacy
|
||||||
|
*/
|
||||||
|
class RecordViewHandler extends LegacyHandler implements RecordViewProviderInterface
|
||||||
|
{
|
||||||
|
public const HANDLER_KEY = 'record';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ModuleNameMapperInterface
|
||||||
|
*/
|
||||||
|
private $moduleNameMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecordViewHandler constructor.
|
||||||
|
* @param string $projectDir
|
||||||
|
* @param string $legacyDir
|
||||||
|
* @param string $legacySessionName
|
||||||
|
* @param string $defaultSessionName
|
||||||
|
* @param LegacyScopeState $legacyScopeState
|
||||||
|
* @param ModuleNameMapperInterface $moduleNameMapper
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
string $projectDir,
|
||||||
|
string $legacyDir,
|
||||||
|
string $legacySessionName,
|
||||||
|
string $defaultSessionName,
|
||||||
|
LegacyScopeState $legacyScopeState,
|
||||||
|
ModuleNameMapperInterface $moduleNameMapper
|
||||||
|
) {
|
||||||
|
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
|
||||||
|
$this->moduleNameMapper = $moduleNameMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getHandlerKey(): string
|
||||||
|
{
|
||||||
|
return self::HANDLER_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $module
|
||||||
|
* @param string $id
|
||||||
|
* @return RecordView
|
||||||
|
*/
|
||||||
|
public function getRecord(string $module, string $id): RecordView
|
||||||
|
{
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
$recordView = new RecordView();
|
||||||
|
$moduleName = $this->validateModuleName($module);
|
||||||
|
$bean = BeanFactory::getBean($moduleName, $id);
|
||||||
|
|
||||||
|
if (!$bean) {
|
||||||
|
$bean = $this->newBeanSafe($moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$recordView->setId($id);
|
||||||
|
$recordView->setRecord((array)$bean);
|
||||||
|
|
||||||
|
$this->close();
|
||||||
|
|
||||||
|
return $recordView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $module
|
||||||
|
*
|
||||||
|
* @return SugarBean
|
||||||
|
* @throws InvalidArgumentException When the module is invalid.
|
||||||
|
*/
|
||||||
|
private function newBeanSafe($module): SugarBean
|
||||||
|
{
|
||||||
|
$bean = BeanFactory::newBean($module);
|
||||||
|
|
||||||
|
if (!$bean instanceof SugarBean) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Module %s does not exist', $module));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $moduleName
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function validateModuleName($moduleName): string
|
||||||
|
{
|
||||||
|
$moduleName = $this->moduleNameMapper->toLegacy($moduleName);
|
||||||
|
|
||||||
|
if (!$this->moduleNameMapper->isValidModule($moduleName)) {
|
||||||
|
throw new InvalidArgumentException('Invalid module name: ' . $moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $moduleName;
|
||||||
|
}
|
||||||
|
}
|
59
core/src/DataProvider/RecordViewItemDataProvider.php
Normal file
59
core/src/DataProvider/RecordViewItemDataProvider.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\DataProvider;
|
||||||
|
|
||||||
|
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
|
||||||
|
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
|
||||||
|
use App\Entity\RecordView;
|
||||||
|
use App\Service\RecordViewProviderInterface;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RecordViewItemDataProvider
|
||||||
|
*/
|
||||||
|
final class RecordViewItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var RecordViewProviderInterface
|
||||||
|
*/
|
||||||
|
private $recordViewHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecordViewItemDataProvider constructor.
|
||||||
|
* @param RecordViewProviderInterface $recordViewHandler
|
||||||
|
*/
|
||||||
|
public function __construct(RecordViewProviderInterface $recordViewHandler)
|
||||||
|
{
|
||||||
|
$this->recordViewHandler = $recordViewHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 RecordView::class === $resourceClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get get record by id
|
||||||
|
* @param string $resourceClass
|
||||||
|
* @param array|int|string $id
|
||||||
|
* @param string|null $operationName
|
||||||
|
* @param array $context
|
||||||
|
* @return RecordView|null
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function getItem(
|
||||||
|
string $resourceClass,
|
||||||
|
$id,
|
||||||
|
string $operationName = null,
|
||||||
|
array $context = []
|
||||||
|
): ?RecordView {
|
||||||
|
return $this->recordViewHandler->getRecord($id);
|
||||||
|
}
|
||||||
|
}
|
96
core/src/Entity/RecordView.php
Normal file
96
core/src/Entity/RecordView.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Core\Annotation\ApiResource;
|
||||||
|
use ApiPlatform\Core\Annotation\ApiProperty;
|
||||||
|
use App\Resolver\RecordViewResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ApiResource(
|
||||||
|
* attributes={"security"="is_granted('ROLE_USER')"},
|
||||||
|
* itemOperations={
|
||||||
|
* "get"={"path"="/record/{id}"}
|
||||||
|
* },
|
||||||
|
* collectionOperations={},
|
||||||
|
* graphql={
|
||||||
|
* "get"={
|
||||||
|
* "item_query"=RecordViewResolver::class,
|
||||||
|
* "args"={
|
||||||
|
* "module"={"type"="String!"},
|
||||||
|
* "record"={"type"="String!"},
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class RecordView
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The record ID
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*
|
||||||
|
* @ApiProperty(
|
||||||
|
* identifier=true,
|
||||||
|
* attributes={
|
||||||
|
* "openapi_context"={
|
||||||
|
* "type"="string",
|
||||||
|
* "description"="The record ID.",
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
*
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecordView data
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
* @ApiProperty(
|
||||||
|
* attributes={
|
||||||
|
* "openapi_context"={
|
||||||
|
* "type"="array",
|
||||||
|
* "description"="The record-view data",
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public $record;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $id
|
||||||
|
*/
|
||||||
|
public function setId($id): void
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get RecordView record
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRecord(): ?array
|
||||||
|
{
|
||||||
|
return $this->record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set RecordView record
|
||||||
|
* @param array $record
|
||||||
|
*/
|
||||||
|
public function setRecord(array $record): void
|
||||||
|
{
|
||||||
|
$this->record = $record;
|
||||||
|
}
|
||||||
|
}
|
41
core/src/Resolver/RecordViewResolver.php
Normal file
41
core/src/Resolver/RecordViewResolver.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Resolver;
|
||||||
|
|
||||||
|
use ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface;
|
||||||
|
use App\Entity\RecordView;
|
||||||
|
use Exception;
|
||||||
|
use SuiteCRM\Core\Legacy\RecordViewHandler;
|
||||||
|
|
||||||
|
class RecordViewResolver implements QueryItemResolverInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var RecordViewHandler
|
||||||
|
*/
|
||||||
|
protected $recordViewHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecordViewResolver constructor.
|
||||||
|
* @param RecordViewHandler $recordViewHandler
|
||||||
|
*/
|
||||||
|
public function __construct(RecordViewHandler $recordViewHandler)
|
||||||
|
{
|
||||||
|
$this->recordViewHandler = $recordViewHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param RecordView|null $item
|
||||||
|
*
|
||||||
|
* @param array $context
|
||||||
|
* @return RecordView
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function __invoke($item, array $context): RecordView
|
||||||
|
{
|
||||||
|
|
||||||
|
$module = $context['args']['module'] ?? '';
|
||||||
|
$record = $context['args']['record'] ?? '';
|
||||||
|
|
||||||
|
return $this->recordViewHandler->getRecord($module, $record);
|
||||||
|
}
|
||||||
|
}
|
18
core/src/Service/RecordViewProviderInterface.php
Normal file
18
core/src/Service/RecordViewProviderInterface.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\RecordView;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
interface RecordViewProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get record
|
||||||
|
* @param string $module
|
||||||
|
* @param string $id
|
||||||
|
* @return RecordView
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function getRecord(string $module, string $id): RecordView;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue