├── .php-cs-fixer.cache ├── LICENSE.md ├── README.md ├── SECURITY.md ├── composer.json ├── doc ├── 01_Installation.md ├── 02_Customize_Menu_Entry_List.md ├── 03_Upgrade.md └── img │ ├── custom_views.png │ └── perspectives.png └── src ├── Controller └── PerspectiveController.php ├── DependencyInjection ├── Configuration.php └── PimcorePerspectiveEditorExtension.php ├── Event └── ElementTree │ ├── IconEvents.php │ └── Model │ └── IconAddEvent.php ├── Installer.php ├── Migrations └── Version20211213110000.php ├── PimcorePerspectiveEditorBundle.php ├── Resources ├── config │ ├── doctrine_migrations.yml │ ├── pimcore │ │ └── routing.yml │ └── services.yml ├── public │ ├── css │ │ └── icons.css │ └── js │ │ └── pimcore │ │ └── perspective │ │ ├── common.js │ │ ├── events.js │ │ ├── menuItemPermissionHelper.js │ │ ├── perspective.js │ │ ├── startup.js │ │ └── view.js └── translations │ ├── admin.ca.yml │ ├── admin.cs.yml │ ├── admin.de.yml │ ├── admin.en.yml │ ├── admin.es.yml │ ├── admin.fr.yml │ ├── admin.hu.yml │ ├── admin.it.yml │ ├── admin.nl.yml │ ├── admin.pl.yml │ ├── admin.pt_br.yml │ ├── admin.ro.yml │ ├── admin.sk.yml │ ├── admin.sv.yml │ ├── admin.th.yml │ └── admin.zh_Hans.yml └── Services ├── AbstractAccessor.php ├── PerspectiveAccessor.php ├── TreeHelper.php └── ViewAccessor.php /.php-cs-fixer.cache: -------------------------------------------------------------------------------- 1 | {"php":"8.3.20","version":"3.75.0","indent":" ","lineEnding":"\n","rules":{"encoding":true,"full_opening_tag":true,"blank_line_after_namespace":true,"braces_position":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ignore"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","do","else","elseif","final","for","foreach","function","if","interface","namespace","private","protected","public","static","switch","trait","try","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"array_syntax":{"syntax":"short"},"header_comment":{"comment_type":"PHPDoc","header":"This source file is available under the terms of the\nPimcore Open Core License (POCL)\nFull copyright and license information is available in\nLICENSE.md which is distributed with this source code.\n\n @copyright Copyright (c) Pimcore GmbH (https:\/\/www.pimcore.com)\n @license Pimcore Open Core License (POCL)"},"blank_line_before_statement":true,"function_typehint_space":true,"single_line_comment_style":true,"lowercase_cast":true,"magic_constant_casing":true,"class_attributes_separation":true,"native_function_casing":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":true,"no_leading_import_slash":true,"no_leading_namespace_whitespace":true,"no_short_bool_cast":true,"no_spaces_around_offset":true,"no_unneeded_control_parentheses":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"no_whitespace_in_blank_line":true,"object_operator_without_whitespace":true,"ordered_imports":true,"phpdoc_indent":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"return_type_declaration":true,"self_accessor":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_quote":true,"space_after_semicolon":true,"standardize_not_equals":true,"ternary_operator_spaces":true,"whitespace_after_comma_in_array":true},"hashes":{"src\/Controller\/PerspectiveController.php":"02debd1a5aa25884efee93f2a668dee0","src\/PimcorePerspectiveEditorBundle.php":"1b3f08424ce2fd8c6a2a67ac99488f9b","src\/DependencyInjection\/Configuration.php":"27e986958cf12d14238c5bbe1d9dac7d","src\/DependencyInjection\/PimcorePerspectiveEditorExtension.php":"5feac9201fa7d68ad4c5413fc814185d","src\/Migrations\/Version20211213110000.php":"4c5d03242039caf7956fbd7ee1ec2e4c","src\/Event\/ElementTree\/IconEvents.php":"c41cbadbbbb96236b4dd743dd1f4495c","src\/Event\/ElementTree\/Model\/IconAddEvent.php":"b8c730dc0cee16521452bcba71dccc4a","src\/Installer.php":"ee71455dc9f033b3d99479bc7171f739","src\/Services\/TreeHelper.php":"18e1298fd9adedfed5ac927e8fe027cb","src\/Services\/AbstractAccessor.php":"74df2038e5f36786d8dbe56ed88d9847","src\/Services\/ViewAccessor.php":"3ff8ab713e6ab4aefa30fcd69ac29176","src\/Services\/PerspectiveAccessor.php":"dec8776b3543d46973e4ae15401e3877"}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Perspective Editor 3 | --- 4 | 5 | # Pimcore Perspective Editor 6 | 7 | This bundle provides an editor for Pimcore to manage custom views and perspectives. 8 | 9 | ## Features in a Nutshell 10 | - Possibility to add, remove or edit custom views. 11 | - Possibility to add, remove or edit perspectives. 12 | - Configuration directly in the user interface. 13 | 14 |
15 | 16 | ![Execution](./doc/img/perspectives.png) 17 | *Sample Perspective Editor* 18 | 19 |
20 | 21 | ![Execution](./doc/img/custom_views.png) 22 | *Sample View Editor* 23 | 24 | ## Documentation Overview 25 | - [Installation & Configuration](./doc/01_Installation.md) 26 | - [Customize Menu Entry List](./doc/02_Customize_Menu_Entry_List.md) 27 | - [Upgrade Notes](./doc/03_Upgrade.md) 28 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you think that you have found a security issue, 6 | don’t use the bug tracker and don’t publish it publicly. 7 | Instead, all security issues must be reported via a private vulnerability report. 8 | 9 | Please follow the [instructions](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) to submit a private report. 10 | 11 | 12 | ## Resolving Process 13 | Every submitted security issue is handled with top priority by following these steps: 14 | 15 | 1. Confirm the vulnerability 16 | 2. Determine the severity 17 | 3. Contact reporter 18 | 4. Work on a patch 19 | 5. Get a CVE identification number (may be done by the reporter or a security service provider) 20 | 6. Patch reviewing 21 | 7. Tagging a new release for supported versions 22 | 8. Publish security announcement 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pimcore/perspective-editor", 3 | "license": "proprietary", 4 | "type": "pimcore-bundle", 5 | "description": "Pimcore Perspective Editor", 6 | "config": { 7 | "sort-packages": true, 8 | "preferred-install": { 9 | "pimcore/pimcore": "source", 10 | "*": "dist" 11 | } 12 | }, 13 | "prefer-stable": true, 14 | "minimum-stability": "dev", 15 | "require": { 16 | "php": "~8.3.0 || ~8.4.0", 17 | "pimcore/compatibility-bridge-v10": "^2.0", 18 | "pimcore/admin-ui-classic-bundle": "^2.0", 19 | "pimcore/pimcore": "^12.0", 20 | "symfony/http-foundation": "^6.3" 21 | }, 22 | "require-dev": { 23 | "phpstan/phpstan": "^1.2", 24 | "phpunit/phpunit": "^9.3", 25 | "codeception/codeception": "^4.1.12 || ^5.0.3", 26 | "codeception/module-symfony":"^1.6.0 || ^3.1.0", 27 | "codeception/phpunit-wrapper": "^9", 28 | "codeception/module-asserts": "^2" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Pimcore\\Bundle\\PerspectiveEditorBundle\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Pimcore\\Bundle\\PerspectiveEditorBundle\\Tests\\": "tests" 38 | }, 39 | "files": [ 40 | "kernel/Kernel.php" 41 | ] 42 | }, 43 | "extra": { 44 | "pimcore": { 45 | "bundles": [ 46 | "Pimcore\\Bundle\\PerspectiveEditorBundle\\PimcorePerspectiveEditorBundle" 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /doc/01_Installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | ## Bundle Installation 3 | 4 | 1. Install the required dependencies: 5 | ```bash 6 | composer require pimcore/perspective-editor 7 | ``` 8 | 9 | 2. Make sure the bundle is enabled in the `config/bundles.php` file. The following lines should be added: 10 | 11 | ```php 12 | use Pimcore\Bundle\PerspectiveEditorBundle\PimcorePerspectiveEditorBundle; 13 | // ... 14 | 15 | return [ 16 | // ... 17 | PimcorePerspectiveEditorBundle::class => ['all' => true], 18 | // ... 19 | ]; 20 | ``` 21 | 22 | 3. Install the bundle: 23 | 24 | ```bash 25 | bin/console pimcore:bundle:install PimcorePerspectiveEditorBundle 26 | ``` 27 | 28 | Installation routine just adds an additional permission to `users_permission_definitions` table. 29 | 30 | Also, make sure, that `customviews.php` and `perspectives.php` files are writeable for php. 31 | They can be located at Pimcore default locations for config files: 32 | `PIMCORE_CUSTOM_CONFIGURATION_DIRECTORY` or `PIMCORE_CONFIGURATION_DIRECTORY`. 33 | 34 | If they don't exist, they are created at `PIMCORE_CONFIGURATION_DIRECTORY`. 35 | 36 | 37 | ## Configuration 38 | 39 | To make the bundle work, just make sure, users have the necessary permissions. 40 | - Perspective Editor: all users with `perspective_editor` permission have all access. 41 | - Custom Views Editor: all users with `perspective_editor` permission have read access, only admin users 42 | have write access (due to risk of potential security issues as SQL conditions can be configured in the UI). 43 | 44 | -------------------------------------------------------------------------------- /doc/02_Customize_Menu_Entry_List.md: -------------------------------------------------------------------------------- 1 | # Customize Menu Entry List 2 | 3 | Custom view and perspective settings also allow configuring the context menu and toolbar menu entries. 4 | It might be necessary to add additional entries due to bundles or application code. 5 | 6 | This is possible with following two events: 7 | 8 | ```javascript 9 | onPerspectiveEditorLoadStructureForPermissions 10 | onPerspectiveEditorLoadPermissions 11 | ``` 12 | 13 | Registering for `onPerspectiveEditorLoadStructureForPermissions` enables you to add a new menu structure, which will then be passed to the `onPerspectiveEditorLoadPermissions` event. 14 | 15 | Registering for `onPerspectiveEditorLoadPermissions` enables you to add custom visibility settings for your bundle either to one of the default built-in entries or to your own structure you may have created in the `onPerspectiveEditorLoadStructureForPermissions` event. 16 | 17 | Add one or both events to the `pimcoreReady` hook of your bundle. 18 | 19 | Example 1: Add a custom visibility setting to one of the built-in entries: 20 | ```javascript 21 | document.addEventListener(pimcore.events.onPerspectiveEditorLoadPermissions, (e) => { 22 | const context = e.detail.context; 23 | const menu = e.detail.menu; 24 | const permissions = e.detail.permissions; 25 | 26 | if(context == 'toolbar' && menu == 'search' && 27 | permissions[context][menu].indexOf('items.advancedObjectSearch') == -1) { 28 | permissions[context][menu].push('items.advancedObjectSearch'); 29 | } 30 | }); 31 | ``` 32 | 33 | Example 2: Add your custom structure with custom entries 34 | 35 | ```javascript 36 | document.addEventListener(pimcore.events.onPerspectiveEditorLoadStructureForPermissions, (e) => { 37 | if(e.detail.context == 'toolbar') { 38 | e.detail.structure['customEntry'] = {}; 39 | } 40 | }); 41 | 42 | document.addEventListener(pimcore.events.onPerspectiveEditorLoadPermissions, (e) => { 43 | const context = e.detail.context; 44 | const menu = e.detail.menu; 45 | const permissions = e.detail.permissions; 46 | 47 | if(context == 'toolbar' && menu == 'customEntry') { 48 | if(permissions[context][menu] == undefined) { 49 | permissions[context][menu] = []; 50 | } 51 | if(permissions[context][menu].indexOf('hidden') == -1) { 52 | permissions[context][menu].push('hidden'); 53 | ... 54 | } 55 | } 56 | }); 57 | 58 | ``` 59 | 60 | For all available built-in entries please have a look at [MenuItemPermissionHelper](https://github.com/pimcore/perspective-editor/blob/main/src/Resources/public/js/pimcore/perspective/menuItemPermissionHelper.js). 61 | -------------------------------------------------------------------------------- /doc/03_Upgrade.md: -------------------------------------------------------------------------------- 1 | # Update Notes 2 | 3 | ## Update to Version 1.8 4 | ### General 5 | - Dropped support of Pimcore 10, bumped minimum requirement of `pimcore/pimcore` to `^11.2`. Replaced all `$request->get()` with their explicit input source. -------------------------------------------------------------------------------- /doc/img/custom_views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimcore/perspective-editor/efe39e728878f13190f60fb356f95472008b4c38/doc/img/custom_views.png -------------------------------------------------------------------------------- /doc/img/perspectives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimcore/perspective-editor/efe39e728878f13190f60fb356f95472008b4c38/doc/img/perspectives.png -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 35 | 36 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 37 | $loader->load('services.yml'); 38 | } 39 | 40 | /** 41 | * @param ContainerBuilder $container 42 | */ 43 | public function prepend(ContainerBuilder $container): void 44 | { 45 | if ($container->hasExtension('doctrine_migrations')) { 46 | $loader = new Loader\YamlFileLoader( 47 | $container, 48 | new FileLocator(__DIR__ . '/../Resources/config') 49 | ); 50 | 51 | $loader->load('doctrine_migrations.yml'); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Event/ElementTree/IconEvents.php: -------------------------------------------------------------------------------- 1 | elementTrees; 25 | } 26 | 27 | public function setElementTreeIcons(array $elementTrees): void 28 | { 29 | $this->elementTrees = $elementTrees; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Installer.php: -------------------------------------------------------------------------------- 1 | addSql(sprintf("INSERT IGNORE INTO users_permission_definitions (`key`) VALUES('%s');", PimcorePerspectiveEditorBundle::PERMISSION_PERSPECTIVE_EDITOR_VIEW_EDIT)); 38 | } 39 | 40 | public function down(Schema $schema): void 41 | { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/PimcorePerspectiveEditorBundle.php: -------------------------------------------------------------------------------- 1 | addBundle(new PimcoreAdminBundle(), 60); 43 | } 44 | 45 | public function getJsPaths(): array 46 | { 47 | return [ 48 | '/bundles/pimcoreperspectiveeditor/js/pimcore/perspective/menuItemPermissionHelper.js', 49 | '/bundles/pimcoreperspectiveeditor/js/pimcore/perspective/perspective.js', 50 | '/bundles/pimcoreperspectiveeditor/js/pimcore/perspective/view.js', 51 | '/bundles/pimcoreperspectiveeditor/js/pimcore/perspective/common.js', 52 | '/bundles/pimcoreperspectiveeditor/js/pimcore/perspective/startup.js', 53 | '/bundles/pimcoreperspectiveeditor/js/pimcore/perspective/events.js', 54 | ]; 55 | } 56 | 57 | public function getCssPaths(): array 58 | { 59 | return [ 60 | '/bundles/pimcoreperspectiveeditor/css/icons.css' 61 | ]; 62 | } 63 | 64 | protected function getComposerPackageName(): string 65 | { 66 | return 'pimcore/perspective-editor'; 67 | } 68 | 69 | public function getInstaller(): InstallerInterface 70 | { 71 | return $this->container->get(Installer::class); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine_migrations.yml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | 'Pimcore\Bundle\PerspectiveEditorBundle\Migrations': '@PimcorePerspectiveEditorBundle/Migrations' -------------------------------------------------------------------------------- /src/Resources/config/pimcore/routing.yml: -------------------------------------------------------------------------------- 1 | admin: 2 | resource: "@PimcorePerspectiveEditorBundle/Controller/" 3 | type: attribute 4 | prefix: /admin/perspectives-views 5 | -------------------------------------------------------------------------------- /src/Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | bind: 7 | $configDirectory: "%kernel.project_dir%/config/pimcore/" 8 | 9 | Pimcore\Bundle\PerspectiveEditorBundle\Controller\: 10 | resource: '../../Controller' 11 | public: true 12 | tags: ['controller.service_arguments'] 13 | 14 | Pimcore\Bundle\PerspectiveEditorBundle\Services\: 15 | resource: '../../Services' 16 | 17 | 18 | Pimcore\Bundle\PerspectiveEditorBundle\Installer: 19 | public: true 20 | arguments: 21 | # fetch the bundle via expression language 22 | $bundle: "@=service('kernel').getBundle('PimcorePerspectiveEditorBundle')" -------------------------------------------------------------------------------- /src/Resources/public/css/icons.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is available under the terms of the 3 | * Pimcore Open Core License (POCL) 4 | * Full copyright and license information is available in 5 | * LICENSE.md which is distributed with this source code. 6 | * 7 | * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.com) 8 | * @license Pimcore Open Core License (POCL) 9 | */ 10 | 11 | 12 | .plugin_pimcore_perspective_editor_perspective { 13 | background: url(/bundles/pimcoreadmin/img/flat-color-icons/reading.svg) center center no-repeat !important; 14 | } 15 | 16 | .plugin_pimcore_perspective_editor_custom_view_tree_item { 17 | padding-bottom: 5px; 18 | } 19 | 20 | .plugin_pimcore_perspective_editor_custom_view_tree_item img { 21 | background-color: #393C3F; 22 | } -------------------------------------------------------------------------------- /src/Resources/public/js/pimcore/perspective/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is available under the terms of the 3 | * Pimcore Open Core License (POCL) 4 | * Full copyright and license information is available in 5 | * LICENSE.md which is distributed with this source code. 6 | * 7 | * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.com) 8 | * @license Pimcore Open Core License (POCL) 9 | */ 10 | 11 | 12 | pimcore.registerNS('pimcore.bundle.perspectiveeditor.PerspectiveViewHelper'); 13 | 14 | pimcore.bundle.perspectiveeditor.PerspectiveViewHelper = class { 15 | 16 | static routePrefix = '/admin/perspectives-views'; 17 | 18 | static generateUuid (){ 19 | return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/x/g, function(c) { 20 | const r = Math.random() * 16 | 0; 21 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 22 | }); 23 | } 24 | 25 | static reloadTreeNode (record){ 26 | record.parentNode.collapse(); 27 | record.parentNode.expand(); 28 | } 29 | 30 | static generateCheckbox (label, config, key, inverted = false, changeCallback = null, readOnly = false){ 31 | return new Ext.form.Checkbox({ 32 | boxLabel: label, 33 | checked: inverted ? !config[key] : config[key], 34 | readOnly: readOnly, 35 | listeners: { 36 | change: function(elem, newValue, oldValue){ 37 | config[key] = newValue; 38 | if(changeCallback) { 39 | changeCallback(); 40 | } 41 | }.bind(this) 42 | }, 43 | }); 44 | } 45 | 46 | static checkAndCreateDataStructure (config, structure){ 47 | for(let key in structure){ 48 | if(!(key in config)){ 49 | config[key] = {}; 50 | } 51 | 52 | //support shortcut definitions 53 | if(Number.isInteger(config[key]) || typeof config[key] === "boolean") { 54 | config[key] = { 55 | hidden: !config[key] 56 | }; 57 | } 58 | 59 | for(let i in structure[key]){ 60 | var keyPath = structure[key][i].split('.'); 61 | var c = config[key]; 62 | for(let j in keyPath){ 63 | if(!(keyPath[j] in c)){ 64 | let defaultValue = true; 65 | if(keyPath[j] == 'hidden') { 66 | defaultValue = false; 67 | } 68 | 69 | c[keyPath[j]] = j*1 + 1 === keyPath.length ? defaultValue : {}; 70 | } 71 | 72 | //support inline shortcut definitions 73 | if((Number.isInteger(c[keyPath[j]]) || typeof c[keyPath[j]] === "boolean") && j*1 + 1 < keyPath.length) { 74 | c[keyPath[j]] = { 75 | hidden: !c[keyPath[j]] 76 | }; 77 | } 78 | 79 | c = c[keyPath[j]]; 80 | } 81 | } 82 | } 83 | } 84 | 85 | static generateCheckboxesForStructure (configStructure, checkboxItems, callback, labelPrefix, readOnly) { 86 | const keys = Object.keys(configStructure); 87 | keys.sort(); 88 | 89 | for(let i = 0; i