├── .editorconfig ├── .gitignore ├── .php_cs.dist ├── .rmt.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bin └── pimcore.php ├── box.json.dist ├── composer.json ├── composer.lock ├── doc └── pimcore_5_migration.md ├── gpl-3.0.txt ├── phpunit.xml.dist ├── src ├── Cli │ ├── Command │ │ ├── AbstractCommand.php │ │ ├── Config │ │ │ └── DebugModeCommand.php │ │ ├── Pimcore5 │ │ │ ├── AbstractCsFixerCommand.php │ │ │ ├── CheckRequirementsCommand.php │ │ │ ├── FixConfigCommand.php │ │ │ ├── MigrateAreabrickCommand.php │ │ │ ├── MigrateFilesystemCommand.php │ │ │ ├── MigrationCheatsheetCommand.php │ │ │ ├── ProcessControllersCommand.php │ │ │ ├── ProcessViewsCommand.php │ │ │ ├── RenameViewsCommand.php │ │ │ ├── Traits │ │ │ │ └── RenameViewCommandTrait.php │ │ │ └── UpdateDbReferencesCommand.php │ │ ├── SelfUpdateCommand.php │ │ └── VersionCommand.php │ ├── Console │ │ ├── Application.php │ │ └── Style │ │ │ ├── PimcoreStyle.php │ │ │ ├── RequirementsFormatter.php │ │ │ └── VersionFormatter.php │ ├── Filesystem │ │ ├── CommandCollector │ │ │ ├── CommandCollectorFactory.php │ │ │ ├── CommandCollectorInterface.php │ │ │ ├── GitCommandCollector.php │ │ │ └── ShellCommandCollector.php │ │ └── DryRunFilesystem.php │ ├── Pimcore5 │ │ └── Pimcore5Requirements.php │ ├── SelfUpdate │ │ └── Updater.php │ ├── Traits │ │ ├── CommandCollectorCommandTrait.php │ │ ├── DryRunCommandTrait.php │ │ └── DryRunTrait.php │ └── Util │ │ ├── CodeGeneratorUtils.php │ │ ├── FileUtils.php │ │ ├── TextUtils.php │ │ └── VersionReader.php ├── Config │ └── System │ │ ├── Pimcore5ConfigProcessor.php │ │ └── SmtpNodeConfiguration.php └── CsFixer │ ├── Console │ └── ConfigurationResolver.php │ ├── Fixer │ ├── AbstractFunctionReferenceFixer.php │ ├── Controller │ │ ├── ActionRequestFixer.php │ │ ├── ControllerBaseClassFixer.php │ │ ├── ControllerGetParamFixer.php │ │ └── ControllerNamespaceFixer.php │ ├── Traits │ │ ├── FixerNameTrait.php │ │ ├── LoggingFixerTrait.php │ │ └── SupportsControllerTrait.php │ └── View │ │ ├── AbstractViewHelperTemplatePathFixer.php │ │ ├── LayoutContentFixer.php │ │ ├── NavigationFixer.php │ │ ├── NavigationRenderPartialHelperFixer.php │ │ ├── NavigationSetPartialHelperFixer.php │ │ ├── PartialHelperFixer.php │ │ ├── SetLayoutFixer.php │ │ ├── TemplateHelperFixer.php │ │ └── TypehintHeaderFixer.php │ ├── Log │ ├── FixerLogger.php │ ├── FixerLoggerInterface.php │ ├── LoggingFixerInterface.php │ └── Record.php │ ├── Tokenizer │ ├── Controller │ │ └── ActionAnalyzer.php │ ├── FunctionAnalyzer.php │ ├── ImportsModifier.php │ └── TokenInsertManipulator.php │ └── Util │ └── FixerResolver.php └── tests └── CsFixer ├── AbstractFixerTestCase.php └── Fixer ├── Controller ├── AbstractControllerFixerTestCase.php ├── ActionRequestFixerTest.php ├── ControllerBaseClassFixerTest.php ├── ControllerGetParamFixerTest.php └── ControllerNamespaceFixerTest.php └── View ├── AbstractViewFixerTestCase.php ├── LayoutContentFixerTest.php ├── NavigationFixerTest.php ├── NavigationRenderPartialHelperFixerTest.php ├── NavigationSetPartialHelperFixerTest.php ├── PartialHelperFixerTest.php ├── SetLayoutFixerTest.php ├── TemplateHelperFixerTest.php └── TypehintHeaderFixerTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.php] 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.yml] 17 | indent_size = 4 18 | 19 | [{*.json,*.json.dist}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # composer 2 | /vendor 3 | 4 | # code style fixer 5 | .php_cs 6 | .php_cs.cache 7 | .pimcore_cli.fix.cache 8 | 9 | # built phar 10 | build/pimcore.phar 11 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | in([__DIR__ . '/src']); 5 | 6 | return PhpCsFixer\Config::create() 7 | ->setRules([ 8 | '@PSR1' => true, 9 | '@PSR2' => true, 10 | 'array_syntax' => ['syntax' => 'short'], 11 | 12 | // keep aligned = and => operators as they are: do not force aligning, but do not remove it 13 | 'binary_operator_spaces' => ['align_double_arrow' => null, 'align_equals' => null], 14 | 15 | 'blank_line_before_return' => true, 16 | 'encoding' => true, 17 | 'function_typehint_space' => true, 18 | 'hash_to_slash_comment' => true, 19 | 'lowercase_cast' => true, 20 | 'magic_constant_casing' => true, 21 | 'method_separation' => true, 22 | 'method_argument_space' => ['ensure_fully_multiline' => false], 23 | 'native_function_casing' => true, 24 | 'no_blank_lines_after_class_opening' => true, 25 | 'no_blank_lines_after_phpdoc' => true, 26 | 'no_empty_comment' => true, 27 | 'no_empty_phpdoc' => true, 28 | 'no_empty_statement' => true, 29 | 'no_extra_consecutive_blank_lines' => true, 30 | 'no_leading_import_slash' => true, 31 | 'no_leading_namespace_whitespace' => true, 32 | 'no_short_bool_cast' => true, 33 | 'no_spaces_around_offset' => true, 34 | 'no_unneeded_control_parentheses' => true, 35 | 'no_unused_imports' => true, 36 | 'no_whitespace_before_comma_in_array' => true, 37 | 'no_whitespace_in_blank_line' => true, 38 | 'object_operator_without_whitespace' => true, 39 | 'ordered_imports' => true, 40 | 'phpdoc_indent' => true, 41 | 'phpdoc_no_useless_inheritdoc' => true, 42 | 'phpdoc_scalar' => true, 43 | 'phpdoc_separation' => true, 44 | 'phpdoc_single_line_var_spacing' => true, 45 | 'return_type_declaration' => true, 46 | 'self_accessor' => true, 47 | 'short_scalar_cast' => true, 48 | 'single_blank_line_before_namespace' => true, 49 | 'single_quote' => true, 50 | 'space_after_semicolon' => true, 51 | 'standardize_not_equals' => true, 52 | 'ternary_operator_spaces' => true, 53 | 'whitespace_after_comma_in_array' => true, 54 | ]) 55 | ->setFinder($finder); 56 | -------------------------------------------------------------------------------- /.rmt.yml: -------------------------------------------------------------------------------- 1 | _default: 2 | vcs: git 3 | prerequisites: 4 | - working-copy-check 5 | - display-last-changes 6 | - tests-check: 7 | command: vendor/bin/phpunit 8 | - command: 9 | cmd: vendor/bin/php-cs-fixer --diff --dry-run -v fix 10 | live_output: false 11 | version-generator: 12 | semantic: 13 | allow-label: true 14 | version-persister: 15 | vcs-tag: 16 | tag-prefix: "{branch-name}_" 17 | post-release-actions: 18 | vcs-publish: 19 | ask-confirmation: true 20 | 21 | master: 22 | version-persister: 23 | vcs-tag: 24 | tag-prefix: 'v' 25 | pre-release-actions: 26 | vcs-commit: 27 | commit-message: "Release v%version%" 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # this config is heavily inspired from https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/.travis.yml 2 | 3 | language: php 4 | sudo: false 5 | 6 | env: 7 | global: 8 | - DEFAULT_COMPOSER_FLAGS="--no-interaction --no-progress --optimize-autoloader" 9 | - TASK_TESTS=1 10 | - TASK_CS=1 11 | 12 | matrix: 13 | fast_finish: true 14 | include: 15 | - php: 7.1 16 | env: DEPLOY=yes 17 | # - php: 7.0 18 | 19 | cache: 20 | directories: 21 | - $HOME/.composer 22 | 23 | before_install: 24 | # turn off XDebug 25 | - phpenv config-rm xdebug.ini || return 0 26 | 27 | # display tasks configuration for a job 28 | - set | grep ^TASK | sort 29 | 30 | install: 31 | - travis_retry composer install $DEFAULT_COMPOSER_FLAGS $COMPOSER_FLAGS 32 | - composer info -D | sort 33 | 34 | script: 35 | - if [ $TASK_TESTS == 1 ]; then vendor/bin/phpunit --verbose; fi 36 | - if [ $TASK_CS == 1 ]; then vendor/bin/php-cs-fixer --diff --dry-run -v fix; fi 37 | 38 | before_deploy: 39 | # install box2 40 | - (mkdir -p $HOME/bin && cd $HOME/bin && curl -LSs http://box-project.github.io/box2/installer.php | php && mv box.phar box) 41 | - box --version 42 | 43 | # ensure that deps will work on lowest supported PHP version 44 | - composer config platform.php 2> /dev/null || composer config platform.php 7.0.0 45 | 46 | # update deps to highest possible for lowest supported PHP version 47 | - composer install $DEFAULT_COMPOSER_FLAGS --no-dev 48 | - composer info -D | sort 49 | 50 | # build phar file 51 | - mkdir -p build 52 | - php -d phar.readonly=false $HOME/bin/box build 53 | 54 | deploy: 55 | provider: releases 56 | file: build/pimcore.phar 57 | skip_cleanup: true 58 | api_key: 59 | secure: ewjj4rvz7VBx5sYbIBm6YKoIV8WdwVaZwcn79/YXPIg8h35pU9Goxg9I0yqkh02l4wph0C8qFK2tvz/y2X15l3oe9TCgXRIjjRji0xoggGiiP1Pw6Doil6hC5sJyT2XIBxWBvz3qTLWK4Zya3CWpWIFmKnWNqB6sdGXutlTH1HahiGfKsE+sm7MTH4Zo7LxpdCFRCgzFDRy1YjpxTMvC02qzlVmjmnXliJYnH8ab2sx7FYR488zWz3VlGBdrCissh6qGBy2+QZwrRwIUREwabpycZ6XMlxvnec7q7vc+SRe3ZmAjs/iotMg7MiO3AlSx7IjuJGvUd3HoCKPsv0+ZDkX8HKeLyY1U6BX4cGg/XNy6+xvwDdRvj8Zrfee6CdWUSJzZElvAoFjp4HZTcsc4I5QtyClnV8h6GgZwVIEm1/5lY6vC/lHPNP4IpmGkm5mB/MokD0Ky2lgQMjsCJqPOX8KGAkfcK/fC6nJ5jhYYwKlSrkEKzhT/k+lbO5kHxu1uAjTT2SWAM1S1isl1/a3OkeSesBQI8Q0RTnKysw7o+yjDd5OlVSjqksMQh/rpix8bINsR6YbHF1xu0qV8q/Vu1bvWxc3awz1n9stmhPnvfhU+FMZ4MoCXAx3P46kWSN6+Ci3eFOUrrmGXU1oezmEUs0JVIOTorzDAMkKnj5H3eDc= 60 | on: 61 | repo: pimcore/pimcore-cli 62 | tags: true 63 | condition: $DEPLOY = yes 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.8.0 5 | ----- 6 | 7 | Add a `--collect-commands` option to the following commands which can be used to generate a list of commands 8 | which can be executed as script (e.g. in combination with `--dry-run` option). 9 | 10 | * `pimcore5:migrate:filesystem` 11 | * `pimcore5:migrate:areabrick` 12 | * `pimcore5:views:rename` 13 | 14 | 0.7.0 15 | ----- 16 | 17 | Added possibility to exclude fixers by their name by passing the `--exclude-fixer` option. Fixer names can be listed with 18 | the `--list-fixers` option. 19 | 20 | 0.6.0 21 | ----- 22 | 23 | * Updated to PHP-CS-Fixer 2.6 and changed calls to work with immutable tokens 24 | 25 | Added controller fixers which are able to: 26 | 27 | * Add an `AppBundle\Controller` controller namespace 28 | * Change the controller parent class to `FrontendController` 29 | * Add a `Request $request` argument to controller actions 30 | * Change calls from `$this->getParam()` to `$request->get()` 31 | 32 | 0.5.0 33 | ----- 34 | 35 | * Added `self-update` command 36 | 37 | 0.4.0 38 | ----- 39 | 40 | * Move files by default and add pimcore5:views:update-db-references command 41 | 42 | 0.3.1 43 | ----- 44 | 45 | * Call pimcore5:config:fix implicitely when migrating filesystem 46 | 47 | 0.3.0 48 | ----- 49 | 50 | * Add pimcore5:config:fix command 51 | 52 | 0.2.0 53 | ----- 54 | 55 | * Refine command names 56 | * Add shell completion 57 | 58 | 0.1.0 59 | ----- 60 | 61 | * Bootstrap application 62 | * Implement `migration.sh` as command and add additional Pimcore 5 related commands 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Copyright (C) Pimcore GmbH 4 | 5 | This software is available under two different licenses: 6 | * GNU General Public License version 3 (GPLv3) as Pimcore Community Edition 7 | * Pimcore Enterprise License (PEL) 8 | 9 | The default Pimcore license, without a valid Pimcore Enterprise License agreement, is the Open-Source GPLv3 license. 10 | 11 | ## GNU General Public License version 3 (GPLv3) 12 | If you decide to choose the GPLv3 license, you must comply with the following terms: 13 | 14 | This program is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | This program is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | 27 | * [GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - gpl-3.0.txt](gpl-3.0.txt) 28 | 29 | ## Pimcore Enterprise License (PEL) 30 | 31 | Alternatively, commercial and supported versions of the program - also known as 32 | Enterprise Distributions - must be used in accordance with the terms and conditions 33 | contained in a separate written agreement between you and Pimcore GmbH. For more information about the Pimcore Enterprise License (PEL) please contact info@pimcore.com. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pimcore v4 to v5 Migration Tools 2 | 3 | [![Build Status](https://travis-ci.org/pimcore/pimcore-cli.svg?branch=master)](https://travis-ci.org/pimcore/pimcore-cli) 4 | 5 | ## Download 6 | 7 | You can download the latest release as PHAR from the [Releases Page](https://github.com/pimcore/pimcore-cli/releases). 8 | 9 | ## Documentation 10 | 11 | * [Pimcore 5 Migration](./doc/pimcore_5_migration.md) 12 | 13 | ## Shell completion 14 | 15 | The package is using [stecman/symfony-console-completion](https://github.com/stecman/symfony-console-completion) to provide 16 | shell completion. To have your shell complete commands, arguments and options, you need to run the following in your 17 | shell: 18 | 19 | ``` 20 | # BASH ~4.x, ZSH 21 | source <(pimcore.phar _completion --generate-hook) 22 | 23 | # BASH ~3.x, ZSH 24 | pimcore.phar _completion --generate-hook | source /dev/stdin 25 | 26 | # BASH (any version) 27 | eval $(pimcore.phar _completion --generate-hook) 28 | ``` 29 | -------------------------------------------------------------------------------- /bin/pimcore.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | = 7.1 21 | $requiredVersion = '7.1'; 22 | if (version_compare(PHP_VERSION, $requiredVersion, '<')) { 23 | file_put_contents('php://stderr', sprintf( 24 | "Pimcore CLI Tools require PHP 7.1 version or higher. Your current PHP version is: %s\n", 25 | PHP_VERSION 26 | )); 27 | 28 | exit(1); 29 | } 30 | 31 | // CLI has no memory/time limits 32 | @ini_set('memory_limit', -1); 33 | @ini_set('max_execution_time', -1); 34 | @ini_set('max_input_time', -1); 35 | 36 | require_once __DIR__ . '/../vendor/autoload.php'; 37 | 38 | $application = new Application('Pimcore CLI Tools'); 39 | $application->addCommands([ 40 | new CompletionCommand(), 41 | new Command\VersionCommand(), 42 | new Command\SelfUpdateCommand(), 43 | new Command\Config\DebugModeCommand(), 44 | new Command\Pimcore5\MigrationCheatsheetCommand(), 45 | new Command\Pimcore5\CheckRequirementsCommand(), 46 | new Command\Pimcore5\MigrateFilesystemCommand(), 47 | new Command\Pimcore5\MigrateAreabrickCommand(), 48 | new Command\Pimcore5\UpdateDbReferencesCommand(), 49 | new Command\Pimcore5\RenameViewsCommand(), 50 | new Command\Pimcore5\ProcessViewsCommand(), 51 | new Command\Pimcore5\ProcessControllersCommand(), 52 | new Command\Pimcore5\FixConfigCommand(), 53 | ]); 54 | 55 | $application->run(); 56 | -------------------------------------------------------------------------------- /box.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "directories": [ 3 | "doc", 4 | "src" 5 | ], 6 | "finder": [ 7 | { 8 | "name": "*.php", 9 | "exclude": [ 10 | ".gitignore", 11 | ".md", 12 | "Tester", 13 | "Tests", 14 | "tests" 15 | ], 16 | "in": "vendor" 17 | } 18 | ], 19 | "compactors": [ 20 | "Herrera\\Box\\Compactor\\Json", 21 | "Herrera\\Box\\Compactor\\Php" 22 | ], 23 | "compression": "GZ", 24 | "git-version": "package_version", 25 | "main": "bin/pimcore.php", 26 | "output": "build/pimcore.phar", 27 | "stub": true, 28 | "chmod": "0755" 29 | } 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pimcore/pimcore-cli", 3 | "description": "Pimcore CLI Tools", 4 | "license": "GPL-3.0", 5 | "require": { 6 | "composer/semver": "^1.4", 7 | "doctrine/common": "^2.7", 8 | "doctrine/dbal": "^2.5", 9 | "friendsofphp/php-cs-fixer": "2.6.*", 10 | "padraic/phar-updater": "^1.0", 11 | "raulfraile/distill": "^0.9.10", 12 | "riimu/kit-phpencoder": "^2.1", 13 | "stecman/symfony-console-completion": "^0.7.0", 14 | "symfony/config": "^3.2", 15 | "symfony/console": "^3.2", 16 | "symfony/filesystem": "^3.2", 17 | "symfony/finder": "^3.2", 18 | "symfony/intl": "^3.2", 19 | "symfony/process": "^3.2", 20 | "symfony/requirements-checker": "dev-master", 21 | "zendframework/zend-code": "^3.1" 22 | }, 23 | "require-dev": { 24 | "gecko-packages/gecko-php-unit": "^2.0", 25 | "phpunit/phpunit": "^5.0", 26 | "symfony/phpunit-bridge": "^3.2" 27 | }, 28 | "config": { 29 | "sort-packages": true 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Pimcore\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Pimcore\\Tests\\": "tests/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /doc/pimcore_5_migration.md: -------------------------------------------------------------------------------- 1 | # Pimcore 5 Migration scripts 2 | 3 | The Pimcore CLI ships with commands which ease the migration to Pimcore 5. 4 | 5 | > Before running any migration tasks please make sure you have a proper backup! 6 | 7 | | Command | Description | 8 | |---------------------------------------|-------------| 9 | | `pimcore5:check-requirements` | Checks if your environment matches the requirements for Pimcore 5. | 10 | | `pimcore5:migrate:filesystem` | Migrates your filesystem to Pimcore 5. Downloads & unpacks ZIP of [Skeleton](https://github.com/pimcore/skeleton) and moves stuff into place. | 11 | | `pimcore5:config:fix` | Updates `system.php` to match Pimcore 5 requirements. | 12 | | `pimcore5:controllers:process` | Rewrites controllers with common changes (e.g. adds a `Request $request` parameter to actions). Path to the controllers folder must be passed. | 13 | | `pimcore5:views:rename` | Migrates views to new naming conventions for PHP templating engine (changes extension from `.php` to `.html.php` and changes filenames from dashed-case to camelCase). | 14 | | `pimcore5:views:update-db-references` | Updates DB references to view files (updates documents setting a custom template). | 15 | | `pimcore5:views:process` | Rewrites templates with common changes needed for Pimcore 5 templating (e.g. changes `setLayout()` to `extend()`). | 16 | | `pimcore5:migrate:areabrick` | Migrates a Pimcore 4 areabrick (XML format) to Pimcore 5 format (Areabrick class). | 17 | 18 | A typical migration scenario could look like the following. Several commands make assumptions regarding file naming which 19 | may not fit your needs. Please check what has been done and revert what you don't need. This may get more flexible/configurable 20 | in the future. 21 | 22 | To introspect what is done by the commands you can use the following options: 23 | 24 | * Every command comes with a `--dry-run` option which lets you inspect what the command would do 25 | * The `process` commands support a `--diff` option which can be used to display the processed changes 26 | * Commands doing filesystem operations support a `--collect-commands` option which can either be set to `shell` or to `git`. 27 | If the option is passed, a list of filesystem operations will be printed with the selected format. `git` will output the 28 | commands as git commands if applicable (e.g. a `mv` would be printed as `git mv`). 29 | 30 | 31 | --- 32 | 33 | ``` 34 | # assuming pimcore.phar is on your $PATH 35 | $ cd 36 | 37 | # migrate filesystem 38 | $ pimcore.phar pimcore5:migrate:filesystem . 39 | 40 | # the config:fix command could update the system.php to match pimcore 5 requirements but 41 | # there is no need to call it after a filesystem migration as it is implicitely called 42 | $ pimcore.phar pimcore5:config:fix var/config/system.php 43 | 44 | # generate an AppBundle via PimcoreGeneratorBundle 45 | $ bin/console pimcore:generate:bundle --namespace AppBundle 46 | 47 | # controller files need to be moved manually, but there is no name changing involved as with views 48 | $ rm src/AppBundle/Controller/* && mv legacy/website/controllers/* src/AppBundle/Controller 49 | 50 | # process controllers (make sure you check what has been changed!) 51 | $ pimcore.phar pimcore5:controllers:process src/AppBundle/Controller 52 | 53 | # rename view scripts (pass -c option to copy files instead of moving them) 54 | $ pimcore.phar pimcore5:views:rename legacy/website/views/scripts app/Resources/views 55 | 56 | # rename layouts - there is no dedicated layouts directory anymore, you can put the layout wherever you want 57 | $ pimcore.phar pimcore5:views:rename legacy/website/views/layouts app/Resources/views 58 | 59 | # migrate a legacy areabrick to new format (make sure to read any warnings returned by the command!) 60 | $ pimcore.phar pimcore5:migrate:areabrick legacy/website/views/areas/gallery/area.xml app src 61 | 62 | # migrate all areabricks in a loop 63 | for i in legacy/website/views/areas/*; do 64 | pimcore.phar pimcore5:migrate:areabrick -v $i/area.xml app src 65 | done 66 | 67 | # process views (make sure you check what has been changed!). this command can be applied to any view directory (e.g. views 68 | # inside a bundle) 69 | $ pimcore.phar pimcore5:views:process app/Resources/views 70 | 71 | # update db view references (documents setting a custom template) to match new camelCased naming scheme 72 | $ pimcore.phar pimcore5:views:update-db-references 73 | ``` 74 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | 21 | 22 | ./src 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Cli/Command/AbstractCommand.php: -------------------------------------------------------------------------------- 1 | io = new PimcoreStyle($input, $output); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Cli/Command/Config/DebugModeCommand.php: -------------------------------------------------------------------------------- 1 | isValidConfigDir($configDir)) { 46 | $fs = new Filesystem(); 47 | 48 | $configDir = $fs->makePathRelative($configDir, getcwd()); 49 | $configDir = rtrim($configDir, '/'); 50 | } else { 51 | $configDir = null; 52 | } 53 | 54 | $this 55 | ->setName('config:debug-mode') 56 | ->setDescription('Sets debug mode') 57 | ->addArgument( 58 | 'config-dir', 59 | null === $configDir ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 60 | 'Path to config directory var/config', 61 | $configDir 62 | ) 63 | ->addOption( 64 | 'disable', 'd', 65 | InputOption::VALUE_NONE, 66 | 'Disable debug mode' 67 | ) 68 | ->addOption( 69 | 'ip', 'i', 70 | InputOption::VALUE_REQUIRED, 71 | 'Only enable debug mode for the given IP' 72 | ); 73 | 74 | $this->configureDryRunOption(); 75 | } 76 | 77 | private function isValidConfigDir($path): bool 78 | { 79 | $pathValid = file_exists($path) && is_dir($path) && is_writable($path); 80 | 81 | if ($pathValid) { 82 | $file = $path . '/' . $this->filename; 83 | if (file_exists($file)) { 84 | $pathValid = $pathValid && is_writable($file); 85 | } 86 | } 87 | 88 | return $pathValid; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | protected function execute(InputInterface $input, OutputInterface $output) 95 | { 96 | $dir = $input->getArgument('config-dir'); 97 | if (!$this->isValidConfigDir($dir)) { 98 | $this->io->error(sprintf('Config directory "%s" is invalid', $dir)); 99 | 100 | return 1; 101 | } 102 | 103 | if ($ip = $input->getOption('ip')) { 104 | if (!filter_var($ip, FILTER_VALIDATE_IP)) { 105 | $this->io->error(sprintf('The IP address "%s" is not valid', $ip)); 106 | 107 | return 2; 108 | } 109 | } 110 | 111 | $file = $dir . '/' . $this->filename; 112 | 113 | $config = $this->dumpConfig([ 114 | 'active' => $input->getOption('disable') ? false : true, 115 | 'ip' => !empty($ip) ? $ip : '' 116 | ]); 117 | 118 | try { 119 | if (!$this->isDryRun()) { 120 | $fs = new Filesystem(); 121 | $fs->dumpFile($file, $config); 122 | } 123 | 124 | $this->io->writeln($this->dryRunMessage(sprintf('File "%s" was successfully written', $file))); 125 | } catch (\Exception $e) { 126 | $this->io->error($e->getMessage()); 127 | 128 | return 3; 129 | } 130 | } 131 | 132 | private function dumpConfig(array $config): string 133 | { 134 | $encoder = new PHPEncoder(); 135 | $encoded = $encoder->encode($config, [ 136 | 'array.inline' => false, 137 | 'array.omit' => false, 138 | ]); 139 | 140 | $result = 'setName('pimcore5:check-requirements') 32 | ->setDescription('Checks if the current environment is able to run Pimcore 5'); 33 | } 34 | 35 | protected function execute(InputInterface $input, OutputInterface $output) 36 | { 37 | $requirements = new Pimcore5Requirements(); 38 | 39 | $this->io->title('Pimcore 5 Requirements Checker'); 40 | 41 | $this->outputIniPath($requirements); 42 | 43 | $this->io->text('> Checking Pimcore 5 requirements:'); 44 | 45 | $formatter = new RequirementsFormatter($this->io); 46 | $result = $formatter->checkRequirements($requirements); 47 | 48 | if (!$result) { 49 | return 1; 50 | } 51 | } 52 | 53 | private function outputIniPath(Pimcore5Requirements $requirements) 54 | { 55 | $iniPath = $requirements->getPhpIniPath(); 56 | 57 | $this->io->text('> PHP is using the following php.ini file:'); 58 | 59 | if ($iniPath) { 60 | $this->io->writeln(sprintf(' %s', $iniPath)); 61 | } else { 62 | $this->io->writeln(sprintf(' %s', 'WARNING: No configuration file (php.ini) used by PHP!')); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/FixConfigCommand.php: -------------------------------------------------------------------------------- 1 | setName('pimcore5:config:fix') 39 | ->setDescription('Updates system.php to match Pimcore 5 structure') 40 | ->addArgument('config-file', InputArgument::REQUIRED, 'Path to system.php'); 41 | 42 | $this->configureDryRunOption(); 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | protected function execute(InputInterface $input, OutputInterface $output) 49 | { 50 | $file = $input->getArgument('config-file'); 51 | 52 | $this->io->comment(sprintf('Updating config file "%s" to match Pimcore 5 requirements', $file)); 53 | 54 | $processor = new Pimcore5ConfigProcessor(); 55 | 56 | try { 57 | $config = $processor->readConfig($file); 58 | $config = $processor->processConfig($config); 59 | $result = $processor->dumpConfig($config); 60 | 61 | $this->io->writeln($this->dryRunMessage(sprintf('Writing processed config to %s', $file))); 62 | 63 | if (!$this->isDryRun()) { 64 | $fs = new Filesystem(); 65 | $fs->dumpFile($file, $result); 66 | } 67 | 68 | $this->io->writeln($this->dryRunMessage(sprintf('File "%s" was successfully processed', $file))); 69 | } catch (\Exception $e) { 70 | $this->io->error($e->getMessage()); 71 | 72 | return 1; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/MigrationCheatsheetCommand.php: -------------------------------------------------------------------------------- 1 | setName('help:pimcore5:migration-cheatsheet') 31 | ->setDescription('Shows migration cheatsheet from the documentation'); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $code = $this->getCodeContent(); 37 | if (null === $code) { 38 | return 1; 39 | } 40 | 41 | $this->io->block('Migration Cheatsheet', null, 'fg=black;bg=cyan', ' ', true); 42 | 43 | $this->io->writeln(<<io->newLine(); 53 | $this->io->listing([ 54 | 'Every command comes with a --dry-run option which lets you inspect what the command would do', 55 | 'The process commands support a --diff option which can be used to display the processed changes', 56 | 'Commands doing filesystem operations support a --collect-commands option which can either be set to `shell` or to `git`.' . PHP_EOL . ' If the option is passed, a list of filesystem operations will be printed with the selected format.', 57 | ]); 58 | 59 | $this->io->writeln(str_repeat('-', 120)); 60 | $this->io->newLine(); 61 | 62 | $this->io->writeln($code); 63 | $this->io->newLine(); 64 | } 65 | 66 | private function getFileContents() 67 | { 68 | /** @var Application $application */ 69 | $application = $this->getApplication(); 70 | 71 | $path = $application->getFilePath('doc/pimcore_5_migration.md'); 72 | 73 | return file_get_contents($path); 74 | } 75 | 76 | private function getCodeContent() 77 | { 78 | $lines = explode("\n", $this->getFileContents()); 79 | 80 | $code = []; 81 | 82 | $hitSeparator = false; 83 | $hitCodeblock = false; 84 | 85 | foreach ($lines as $line) { 86 | $line = rtrim($line); 87 | 88 | if (!$hitSeparator || !$hitCodeblock) { 89 | if ('---' === $line) { 90 | $hitSeparator = true; 91 | } elseif ('```' === $line) { 92 | $hitCodeblock = true; 93 | } 94 | 95 | continue; 96 | } 97 | 98 | if ($hitCodeblock) { 99 | if ('```' === $line) { 100 | break; 101 | } 102 | 103 | $code[] = $this->processCodeLine($line); 104 | } 105 | } 106 | 107 | if (empty($code)) { 108 | $this->io->error('Failed to load cheatsheet code from documentation.'); 109 | 110 | return null; 111 | } 112 | 113 | return implode("\n", $code); 114 | } 115 | 116 | private function processCodeLine($line) 117 | { 118 | if (0 === strpos($line, '#')) { 119 | $line = sprintf('%s', $line); 120 | } 121 | 122 | return $line; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/ProcessControllersCommand.php: -------------------------------------------------------------------------------- 1 | setName('pimcore5:controllers:process') 32 | ->setAliases(['pimcore5:controllers:fix']) 33 | ->setDescription('Changes common migration patterns in controllers'); 34 | 35 | parent::configure(); 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | protected function getCustomFixers(FixerLoggerInterface $logger): array 42 | { 43 | return FixerResolver::getCustomFixers($logger, 'Controller'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/ProcessViewsCommand.php: -------------------------------------------------------------------------------- 1 | setName('pimcore5:views:process') 32 | ->setAliases(['pimcore5:views:fix']) 33 | ->setDescription('Changes common migration patterns in view files (e.g. strips leading slashes in template() calls)'); 34 | 35 | parent::configure(); 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | protected function getCustomFixers(FixerLoggerInterface $logger): array 42 | { 43 | return FixerResolver::getCustomFixers($logger, 'View'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/RenameViewsCommand.php: -------------------------------------------------------------------------------- 1 | setName('pimcore5:views:rename') 41 | ->setDescription('Rename view files (change extension and file casing)') 42 | ->addArgument('sourceDir', InputArgument::REQUIRED) 43 | ->addArgument('targetDir', InputArgument::REQUIRED) 44 | ->addOption( 45 | 'copy', 'c', 46 | InputOption::VALUE_NONE, 47 | 'Copy files instead of moving them' 48 | ) 49 | ->configureCollectCommandsOption() 50 | ->configureViewRenameOptions() 51 | ->configureDryRunOption(); 52 | } 53 | 54 | protected function execute(InputInterface $input, OutputInterface $output) 55 | { 56 | $sourceDir = $input->getArgument('sourceDir'); 57 | $targetDir = $input->getArgument('targetDir'); 58 | 59 | $sourceDir = $sourceDir ? realpath($sourceDir) : null; 60 | if (!($sourceDir && file_exists($sourceDir) && is_dir($sourceDir))) { 61 | throw new \InvalidArgumentException('Invalid source directory'); 62 | } 63 | 64 | $sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR); 65 | $targetDir = rtrim($targetDir, DIRECTORY_SEPARATOR); 66 | 67 | $finder = new Finder(); 68 | $finder 69 | ->files() 70 | ->in($sourceDir) 71 | ->name('*.php'); 72 | 73 | $collector = $this->createCommandCollector(); 74 | $fs = new DryRunFilesystem($this->io, $this->isDryRun(), false, $collector); 75 | 76 | $createdDirs = []; 77 | 78 | $fs->mkdir($targetDir); 79 | $createdDirs[] = $targetDir; 80 | 81 | foreach ($finder as $file) { 82 | $relativePath = str_replace($sourceDir . DIRECTORY_SEPARATOR, '', $file->getRealPath()); 83 | $updatedPath = $this->processPath($input, $relativePath); 84 | $targetPath = $targetDir . DIRECTORY_SEPARATOR . $updatedPath; 85 | 86 | if ($fs->exists($targetPath)) { 87 | $this->io->writeln(sprintf('WARNING: File %s already exists, skipping...', $targetPath)); 88 | continue; 89 | } 90 | 91 | $dirToCreate = dirname($targetPath); 92 | if (!in_array($dirToCreate, $createdDirs)) { 93 | $fs->mkdir($dirToCreate); 94 | $createdDirs[] = $dirToCreate; 95 | } 96 | 97 | if ($input->getOption('copy')) { 98 | $fs->copy($file->getRealPath(), $targetPath); 99 | } else { 100 | $fs->rename($file->getRealPath(), $targetPath); 101 | } 102 | 103 | if ($this->io->isVerbose()) { 104 | $this->io->newLine(); 105 | } 106 | } 107 | 108 | if (null !== $collector) { 109 | $this->printCollectedCommands($collector); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/Traits/RenameViewCommandTrait.php: -------------------------------------------------------------------------------- 1 | addOption( 32 | 'no-rename-filename', 'R', 33 | InputOption::VALUE_NONE, 34 | 'Do not convert filenames from dashed-case to camelCase' 35 | ) 36 | ->addOption( 37 | 'no-rename-first-directory', 'D', 38 | InputOption::VALUE_NONE, 39 | 'Do not rename first directory to uppercase' 40 | ); 41 | 42 | return $this; 43 | } 44 | 45 | protected function processPath(InputInterface $input, string $path): string 46 | { 47 | $path = str_replace('\\', DIRECTORY_SEPARATOR, $path); 48 | $pathParts = explode(DIRECTORY_SEPARATOR, $path); 49 | 50 | if (!$input->getOption('no-rename-first-directory') && count($pathParts) > 1) { 51 | $pathParts[0] = TextUtils::dashesToCamelCase($pathParts[0], true); 52 | } 53 | 54 | $filename = array_pop($pathParts); 55 | $filename = $this->processFilenameExtension($input, $filename); 56 | 57 | $pathParts[] = $filename; 58 | 59 | $path = implode(DIRECTORY_SEPARATOR, $pathParts); 60 | 61 | return $path; 62 | } 63 | 64 | private function processFilenameExtension(InputInterface $input, string $filename): string 65 | { 66 | // normalize extension to html.php if not already done 67 | $filename = preg_replace('/(?getOption('no-rename-filename')) { 73 | $filename = TextUtils::dashesToCamelCase($filename); 74 | } 75 | 76 | // re-add extension 77 | $filename = $filename . '.html.php'; 78 | 79 | return $filename; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Cli/Command/Pimcore5/UpdateDbReferencesCommand.php: -------------------------------------------------------------------------------- 1 | setName('pimcore5:views:update-db-references') 51 | ->setDescription('Update template references in database (change extension and file casing)') 52 | ->addOption( 53 | 'config-file', 'c', 54 | InputOption::VALUE_REQUIRED, 55 | 'Path to system.php', 56 | getcwd() . '/var/config/system.php' 57 | ) 58 | ->configureViewRenameOptions() 59 | ->configureDryRunOption(); 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | protected function execute(InputInterface $input, OutputInterface $output) 66 | { 67 | $db = $this->getDbConnection($input); 68 | foreach ($this->tables as $table) { 69 | $this->processTable($input, $db, $table); 70 | } 71 | } 72 | 73 | private function processTable(InputInterface $input, Connection $db, string $table) 74 | { 75 | $selectStmt = $db->executeQuery('SELECT id, template FROM ' . $table . ' WHERE template IS NOT NULL AND template <> ""'); 76 | $rows = $selectStmt->fetchAll(); 77 | 78 | if (count($rows) === 0) { 79 | return; 80 | } 81 | 82 | $this->io->comment(sprintf('Processing table %s with %d rows', $table, count($rows))); 83 | 84 | $updateStmt = $db->prepare('UPDATE ' . $table . ' SET template = :template WHERE id = :id'); 85 | 86 | foreach ($rows as $row) { 87 | $template = $row['template']; 88 | $template = ltrim($template, '/'); 89 | $template = $this->processPath($input, $template); 90 | 91 | if ($template === $row['template']) { 92 | continue; 93 | } 94 | 95 | $this->io->getOutput()->writeln($this->dryRunMessage(sprintf( 96 | 'Updating template ID %d from template %s to %s', 97 | $row['id'], 98 | $row['template'], 99 | $template 100 | ))); 101 | 102 | if (!$this->isDryRun()) { 103 | $result = $updateStmt->execute([ 104 | 'id' => $row['id'], 105 | 'template' => $template, 106 | ]); 107 | 108 | if (!$result) { 109 | $this->io->error(sprintf('Failed to update template for %s %d', $table, $row['id'])); 110 | } 111 | } 112 | } 113 | 114 | $this->io->writeln(''); 115 | } 116 | 117 | /** 118 | * @param InputInterface $input 119 | * 120 | * @return Connection 121 | */ 122 | private function getDbConnection(InputInterface $input): Connection 123 | { 124 | $processor = new Pimcore5ConfigProcessor(); 125 | $config = $processor->readConfig($input->getOption('config-file')); 126 | $dbConfig = $config['database']['params']; 127 | 128 | $params = [ 129 | 'dbname' => $dbConfig['dbname'], 130 | 'user' => $dbConfig['username'], 131 | 'password' => $dbConfig['password'], 132 | 'host' => $dbConfig['host'], 133 | 'port' => $dbConfig['port'], 134 | 'driver' => 'pdo_mysql', 135 | ]; 136 | 137 | $conn = DriverManager::getConnection($params, new Configuration()); 138 | 139 | return $conn; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Cli/Command/SelfUpdateCommand.php: -------------------------------------------------------------------------------- 1 | setName('self-update') 32 | ->setDescription('Updates the pimcore-cli PHAR to the latest GitHub release') 33 | ->addOption( 34 | 'check-only', 'c', InputOption::VALUE_NONE, 35 | 'Just check if a new version exists, do not actually update' 36 | ); 37 | } 38 | 39 | protected function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $updater = $this->buildUpdater(); 42 | 43 | if ($input->getOption('check-only')) { 44 | return $this->checkForUpdate($updater); 45 | } else { 46 | return $this->update($updater); 47 | } 48 | } 49 | 50 | private function checkForUpdate(Updater $updater): int 51 | { 52 | $result = $updater->hasUpdate(); 53 | 54 | if ($result) { 55 | $this->io->writeln(sprintf( 56 | 'Update available! The current stable build available is: %s. Current version: %s', 57 | $updater->getNewVersion(), 58 | $this->getApplication()->getVersion() 59 | )); 60 | } elseif (false === $updater->getNewVersion()) { 61 | $this->io->writeln('There are no stable builds available.'); 62 | } else { 63 | $this->io->writeln(sprintf( 64 | 'You are already using the latest version %s', 65 | $this->getApplication()->getVersion() 66 | )); 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | private function update(Updater $updater): int 73 | { 74 | if (!$updater->hasUpdate()) { 75 | $this->io->writeln(sprintf( 76 | 'You are already using the latest version %s', 77 | $this->getApplication()->getVersion() 78 | )); 79 | 80 | return 0; 81 | } 82 | 83 | $currentVersion = $this->getApplication()->getVersion(); 84 | $newVersion = $updater->getNewVersion(); 85 | 86 | try { 87 | if ($updater->update()) { 88 | $this->io->success(sprintf('Successfully updated to version %s', $newVersion)); 89 | 90 | return 0; 91 | } else { 92 | $this->io->error(sprintf('Failed to update to version %s', $newVersion)); 93 | 94 | return 1; 95 | } 96 | } catch (\Exception $e) { 97 | $this->io->error(sprintf('Update failed: %s', $e->getMessage())); 98 | 99 | if ($updater->rollback()) { 100 | $this->io->warning(sprintf('Rolled back to version %s', $currentVersion)); 101 | } else { 102 | $this->io->error(sprintf('Failed to roll back to version %s', $currentVersion)); 103 | } 104 | 105 | return 2; 106 | } 107 | } 108 | 109 | private function buildUpdater(): Updater 110 | { 111 | $updater = new Updater(null, false, Updater::STRATEGY_GITHUB); 112 | $currentVersion = $this->getApplication()->getVersion(); 113 | 114 | $this->io->isVerbose() && $this->io->writeln(sprintf( 115 | 'Current version is %s', 116 | $currentVersion 117 | )); 118 | 119 | /** @var GithubStrategy $strategy */ 120 | $strategy = $updater->getStrategy(); 121 | $strategy->setPackageName('pimcore/pimcore-cli'); 122 | $strategy->setPharName('pimcore.phar'); 123 | $strategy->setCurrentLocalVersion($currentVersion); 124 | 125 | return $updater; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Cli/Command/VersionCommand.php: -------------------------------------------------------------------------------- 1 | setName('info:version') 30 | ->addArgument( 31 | 'path', InputArgument::REQUIRED, 32 | 'Path to Pimcore installation' 33 | ); 34 | } 35 | 36 | protected function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $fs = new Filesystem(); 39 | 40 | $path = $input->getArgument('path'); 41 | if (!$fs->exists($path)) { 42 | $this->io->error(sprintf('Given path %s does not exist', $path)); 43 | 44 | return 1; 45 | } 46 | 47 | $versionReader = new VersionReader($path); 48 | 49 | $this->io->title(sprintf('Version info for installation %s', realpath($versionReader->getPath()))); 50 | 51 | $formatter = new VersionFormatter($this->io); 52 | $formatter->formatVersions($versionReader); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Cli/Console/Application.php: -------------------------------------------------------------------------------- 1 | fetchVersion()); 32 | } 33 | 34 | /** 35 | * Get current version from git describe if not in phar context 36 | * 37 | * @param bool $readFromPhar 38 | * 39 | * @return string 40 | */ 41 | public function fetchVersion(bool $readFromPhar = false): string 42 | { 43 | $packageVersion = '@package_version@'; 44 | 45 | if ($this->isPhar()) { 46 | if ($readFromPhar) { 47 | return $this->getPharVersion(true); 48 | } 49 | } else { 50 | $gitDir = __DIR__ . '/../../../.git'; 51 | 52 | if (file_exists($gitDir)) { 53 | $process = new Process(sprintf('git describe --tags')); 54 | $process->setEnv([ 55 | 'GIT_DIR' => $gitDir 56 | ]); 57 | 58 | $process->run(); 59 | if ($process->isSuccessful()) { 60 | $packageVersion = $process->getOutput(); 61 | } 62 | } 63 | } 64 | 65 | if (!empty($packageVersion)) { 66 | $packageVersion = trim($packageVersion); 67 | } 68 | 69 | return $packageVersion; 70 | } 71 | 72 | /** 73 | * Check if we're running in phar mode 74 | * 75 | * @return bool 76 | */ 77 | public function isPhar(): bool 78 | { 79 | $pharPath = \Phar::running(); 80 | 81 | return !empty($pharPath); 82 | } 83 | 84 | /** 85 | * @param bool $spawnProcess 86 | * 87 | * @return string 88 | */ 89 | public function getPharVersion(bool $spawnProcess = false): string 90 | { 91 | if (!$this->isPhar()) { 92 | throw new \RuntimeException('Can only be called from a PHAR file.'); 93 | } 94 | 95 | if (!$spawnProcess) { 96 | return $this->getVersion(); 97 | } else { 98 | $process = $this->runPharCommand('--version'); 99 | if (!$process->isSuccessful()) { 100 | throw new \RuntimeException($process->getErrorOutput()); 101 | } 102 | 103 | $output = trim($process->getOutput()); 104 | $matches = []; 105 | 106 | if (preg_match('/version (v.*)$/', $output, $matches)) { 107 | return $matches[1]; 108 | } else { 109 | return 'UNKNOWN'; 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * @param string $command 116 | * @param array $args 117 | * 118 | * @return Process 119 | */ 120 | public function runPharCommand(string $command, array $args = []): Process 121 | { 122 | if (!$this->isPhar()) { 123 | throw new \RuntimeException('Can only be called from a PHAR file.'); 124 | } 125 | 126 | $pharPath = \Phar::running(false); 127 | 128 | $processArgs = array_merge([$pharPath, $command], $args); 129 | $process = (new ProcessBuilder($processArgs))->getProcess(); 130 | $process->run(); 131 | 132 | return $process; 133 | } 134 | 135 | /** 136 | * Resolves file path on filesystem or inside PHAR 137 | * 138 | * @param string $path 139 | * 140 | * @return string 141 | */ 142 | public function getFilePath($path) 143 | { 144 | if ($this->isPhar()) { 145 | $phar = new \Phar(\Phar::running()); 146 | 147 | if (!isset($phar[$path])) { 148 | throw new \InvalidArgumentException(sprintf('Path "%s" does not exist inside PHAR file', $path)); 149 | } 150 | 151 | return $phar[$path]->getPathName(); 152 | } else { 153 | $realPath = realpath(__DIR__ . '/../../../' . ltrim($path, '/')); 154 | 155 | if (!$realPath || !file_exists($realPath)) { 156 | throw new \InvalidArgumentException(sprintf('Path "%s" does not exist on filesystem', $path)); 157 | } 158 | 159 | return $realPath; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Cli/Console/Style/PimcoreStyle.php: -------------------------------------------------------------------------------- 1 | input = $input; 43 | $this->output = $output; 44 | 45 | parent::__construct($input, $output); 46 | } 47 | 48 | /** 49 | * @return InputInterface 50 | */ 51 | public function getInput(): InputInterface 52 | { 53 | return $this->input; 54 | } 55 | 56 | /** 57 | * @return OutputInterface 58 | */ 59 | public function getOutput(): OutputInterface 60 | { 61 | return $this->output; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Cli/Console/Style/RequirementsFormatter.php: -------------------------------------------------------------------------------- 1 | io = $io; 37 | } 38 | 39 | public function checkRequirements(RequirementCollection $requirements): bool 40 | { 41 | $io = $this->io; 42 | $error = false; 43 | 44 | $messages = []; 45 | $messages = $this->checkCollection($requirements->getRequirements(), $messages, 'error', 'E', 'error'); 46 | $messages = $this->checkCollection($requirements->getRecommendations(), $messages, 'warning', 'W', 'comment'); 47 | 48 | if (empty($messages['error'])) { 49 | $error = true; 50 | $io->success('Your system is ready to run Pimcore 5 projects!'); 51 | } else { 52 | $io->error('Your system is not ready to run Pimcore 5 projects'); 53 | 54 | $io->section('Fix the following mandatory requirements'); 55 | $io->listing($messages['error']); 56 | } 57 | 58 | if (!empty($messages['warning'])) { 59 | $io->section('Optional recommendations to improve your setup'); 60 | $io->listing($messages['warning']); 61 | } 62 | 63 | return $error; 64 | } 65 | 66 | /** 67 | * @param Requirement[] $requirements 68 | * @param array $messages 69 | * @param string $messageKey 70 | * @param string $errorChar 71 | * @param string $style 72 | * 73 | * @return array 74 | */ 75 | private function checkCollection(array $requirements, array $messages, string $messageKey, string $errorChar, string $style): array 76 | { 77 | foreach ($requirements as $requirement) { 78 | if ($requirement->isFulfilled()) { 79 | $this->io->write('.'); 80 | } else { 81 | $this->io->write(sprintf('<%1$s>%2$s', $style, $errorChar)); 82 | $messages[$messageKey][] = $this->getErrorMessage($requirement); 83 | } 84 | } 85 | 86 | return $messages; 87 | } 88 | 89 | /** 90 | * @param Requirement $requirement 91 | * @param int $lineSize 92 | * 93 | * @return string 94 | */ 95 | private function getErrorMessage(Requirement $requirement, int $lineSize = 70): string 96 | { 97 | if ($requirement->isFulfilled()) { 98 | return ''; 99 | } 100 | 101 | $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL . ' ') . PHP_EOL; 102 | $errorMessage .= ' > ' . wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL . ' > ') . PHP_EOL; 103 | 104 | return $errorMessage; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Cli/Console/Style/VersionFormatter.php: -------------------------------------------------------------------------------- 1 | io = $io; 36 | } 37 | 38 | /** 39 | * @param VersionReader $versionReader 40 | */ 41 | public function formatVersions(VersionReader $versionReader) 42 | { 43 | $entries = [ 44 | sprintf('Version: %s', $versionReader->getVersion()), 45 | sprintf('Revision: %d', $versionReader->getRevision()), 46 | ]; 47 | 48 | $this->io->listing($entries); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Cli/Filesystem/CommandCollector/CommandCollectorFactory.php: -------------------------------------------------------------------------------- 1 | ShellCommandCollector::class, 24 | 'git' => GitCommandCollector::class 25 | ]; 26 | 27 | public static function create(string $type): CommandCollectorInterface 28 | { 29 | if (!isset(self::$types[$type])) { 30 | throw new \InvalidArgumentException(sprintf('Collector for type "%s" does not exist', $type)); 31 | } 32 | 33 | $class = self::$types[$type]; 34 | 35 | return new $class; 36 | } 37 | 38 | public static function getValidTypes(): array 39 | { 40 | return array_keys(static::$types); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Cli/Filesystem/CommandCollector/CommandCollectorInterface.php: -------------------------------------------------------------------------------- 1 | collector = $collector; 42 | } 43 | 44 | public function collect(string $command) 45 | { 46 | foreach ($this->gitCommands as $gitCommand) { 47 | if (0 === strpos($command, $gitCommand)) { 48 | $command = 'git ' . $command; 49 | break; 50 | } 51 | } 52 | 53 | $this->collector->collect($command); 54 | } 55 | 56 | public function getCommands(): array 57 | { 58 | return $this->collector->getCommands(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Cli/Filesystem/CommandCollector/ShellCommandCollector.php: -------------------------------------------------------------------------------- 1 | blacklist as $entry) { 37 | if (0 === strpos($command, $entry)) { 38 | return; 39 | } 40 | } 41 | 42 | $this->commands[] = $command; 43 | } 44 | 45 | public function getCommands(): array 46 | { 47 | return $this->commands; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Cli/Pimcore5/Pimcore5Requirements.php: -------------------------------------------------------------------------------- 1 | addRequirement( 29 | version_compare(phpversion(), static::REQUIRED_PHP_VERSION, '>='), 30 | sprintf('PHP version must be at least %s (%s installed)', static::REQUIRED_PHP_VERSION, $installedPhpVersion), 31 | sprintf('You are running PHP version "%s", but Pimcore 5 needs at least PHP "%s" to run. 32 | Before using Pimcore 5, upgrade your PHP installation, preferably to the latest version.', 33 | $installedPhpVersion, static::REQUIRED_PHP_VERSION), 34 | sprintf('Install PHP %s or newer (installed version is %s)', static::REQUIRED_PHP_VERSION, $installedPhpVersion) 35 | ); 36 | 37 | $this->addRequirement( 38 | extension_loaded('intl'), 39 | 'intl extension needs to be available', 40 | 'Install and enable the intl extension.' 41 | ); 42 | 43 | $this->addIntlRequirements(); 44 | 45 | $this->addRequirement( 46 | function_exists('iconv'), 47 | 'iconv() must be available', 48 | 'Install and enable the iconv extension.' 49 | ); 50 | 51 | $this->addRequirement( 52 | function_exists('json_encode'), 53 | 'json_encode() must be available', 54 | 'Install and enable the JSON extension.' 55 | ); 56 | 57 | $this->addRequirement( 58 | function_exists('session_start'), 59 | 'session_start() must be available', 60 | 'Install and enable the session extension.' 61 | ); 62 | 63 | $this->addRequirement( 64 | function_exists('ctype_alpha'), 65 | 'ctype_alpha() must be available', 66 | 'Install and enable the ctype extension.' 67 | ); 68 | 69 | $this->addRequirement( 70 | function_exists('token_get_all'), 71 | 'token_get_all() must be available', 72 | 'Install and enable the Tokenizer extension.' 73 | ); 74 | 75 | $this->addRequirement( 76 | function_exists('simplexml_import_dom'), 77 | 'simplexml_import_dom() must be available', 78 | 'Install and enable the SimpleXML extension.' 79 | ); 80 | 81 | if (function_exists('apc_store') && ini_get('apc.enabled')) { 82 | if (version_compare($installedPhpVersion, '5.4.0', '>=')) { 83 | $this->addRequirement( 84 | version_compare(phpversion('apc'), '3.1.13', '>='), 85 | 'APC version must be at least 3.1.13 when using PHP 5.4', 86 | 'Upgrade your APC extension (3.1.13+).' 87 | ); 88 | } else { 89 | $this->addRequirement( 90 | version_compare(phpversion('apc'), '3.0.17', '>='), 91 | 'APC version must be at least 3.0.17', 92 | 'Upgrade your APC extension (3.0.17+).' 93 | ); 94 | } 95 | } 96 | 97 | $this->addPhpConfigRequirement('detect_unicode', false); 98 | 99 | if (extension_loaded('suhosin')) { 100 | $this->addPhpConfigRequirement( 101 | 'suhosin.executor.include.whitelist', 102 | create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'), 103 | false, 104 | 'suhosin.executor.include.whitelist must be configured correctly in php.ini', 105 | 'Add "phar" to suhosin.executor.include.whitelist in php.ini*.' 106 | ); 107 | } 108 | 109 | if (extension_loaded('xdebug')) { 110 | $this->addPhpConfigRequirement( 111 | 'xdebug.show_exception_trace', false, true 112 | ); 113 | 114 | $this->addPhpConfigRequirement( 115 | 'xdebug.scream', false, true 116 | ); 117 | 118 | $this->addPhpConfigRequirement( 119 | 'xdebug.max_nesting_level', 120 | create_function('$cfgValue', 'return $cfgValue > 100;'), 121 | true, 122 | 'xdebug.max_nesting_level should be above 100 in php.ini', 123 | 'Set "xdebug.max_nesting_level" to e.g. "250" in php.ini* to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.' 124 | ); 125 | } 126 | 127 | $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null; 128 | 129 | $this->addRequirement( 130 | null !== $pcreVersion, 131 | 'PCRE extension must be available', 132 | 'Install the PCRE extension (version 8.0+).' 133 | ); 134 | 135 | if (null !== $pcreVersion) { 136 | $this->addRecommendation( 137 | $pcreVersion >= 8.0, 138 | sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion), 139 | 'PCRE 8.0+ is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Pimcore probably works anyway but it is recommended to upgrade your PCRE extension.' 140 | ); 141 | } 142 | 143 | if (extension_loaded('mbstring')) { 144 | $this->addPhpConfigRequirement( 145 | 'mbstring.func_overload', 146 | create_function('$cfgValue', 'return (int) $cfgValue === 0;'), 147 | true, 148 | 'string functions should not be overloaded', 149 | 'Set "mbstring.func_overload" to 0 in php.ini* to disable function overloading by the mbstring extension.' 150 | ); 151 | } 152 | 153 | $this->addRecommendation( 154 | class_exists('DomDocument'), 155 | 'PHP-DOM and PHP-XML modules should be installed', 156 | 'Install and enable the PHP-DOM and the PHP-XML modules.' 157 | ); 158 | 159 | $this->addRecommendation( 160 | function_exists('mb_strlen'), 161 | 'mb_strlen() should be available', 162 | 'Install and enable the mbstring extension.' 163 | ); 164 | 165 | $this->addRecommendation( 166 | function_exists('iconv'), 167 | 'iconv() should be available', 168 | 'Install and enable the iconv extension.' 169 | ); 170 | 171 | $this->addRecommendation( 172 | function_exists('utf8_decode'), 173 | 'utf8_decode() should be available', 174 | 'Install and enable the XML extension.' 175 | ); 176 | 177 | $this->addRecommendation( 178 | function_exists('filter_var'), 179 | 'filter_var() should be available', 180 | 'Install and enable the filter extension.' 181 | ); 182 | 183 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 184 | $this->addRecommendation( 185 | function_exists('posix_isatty'), 186 | 'posix_isatty() should be available', 187 | 'Install and enable the php_posix extension (used to colorize the CLI output).' 188 | ); 189 | } 190 | 191 | $accelerator = 192 | (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) 193 | || 194 | (extension_loaded('apc') && ini_get('apc.enabled')) 195 | || 196 | (extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable')) 197 | || 198 | (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) 199 | || 200 | (extension_loaded('xcache') && ini_get('xcache.cacher')) 201 | || 202 | (extension_loaded('wincache') && ini_get('wincache.ocenabled')) 203 | ; 204 | 205 | $this->addRecommendation( 206 | $accelerator, 207 | 'a PHP accelerator should be installed', 208 | 'Install and/or enable a PHP accelerator (highly recommended).' 209 | ); 210 | 211 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 212 | $this->addRecommendation( 213 | $this->getRealpathCacheSize() >= 5 * 1024 * 1024, 214 | 'realpath_cache_size should be at least 5M in php.ini', 215 | 'Setting "realpath_cache_size" to e.g. "5242880" or "5M" in php.ini* may improve performance on Windows significantly in some cases.' 216 | ); 217 | } 218 | 219 | $this->addPhpConfigRequirement('short_open_tag', false); 220 | 221 | $this->addPhpConfigRequirement('magic_quotes_gpc', false, true); 222 | 223 | $this->addPhpConfigRequirement('register_globals', false, true); 224 | 225 | $this->addPhpConfigRequirement('session.auto_start', false); 226 | 227 | $this->addRecommendation( 228 | class_exists('PDO'), 229 | 'PDO should be installed', 230 | 'Install PDO (mandatory for Doctrine).' 231 | ); 232 | 233 | if (class_exists('PDO')) { 234 | $drivers = \PDO::getAvailableDrivers(); 235 | $this->addRecommendation( 236 | count($drivers) > 0, 237 | sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'), 238 | 'Install PDO drivers (mandatory for Doctrine).' 239 | ); 240 | } 241 | } 242 | 243 | protected function addIntlRequirements() 244 | { 245 | // in some WAMP server installations, new Collator() returns null 246 | $this->addRecommendation( 247 | null !== new \Collator('fr_FR'), 248 | 'intl extension should be correctly configured', 249 | 'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.' 250 | ); 251 | 252 | // check for compatible ICU versions (only done when you have the intl extension) 253 | if (defined('INTL_ICU_VERSION')) { 254 | $version = INTL_ICU_VERSION; 255 | } else { 256 | $reflector = new \ReflectionExtension('intl'); 257 | 258 | ob_start(); 259 | $reflector->info(); 260 | $output = strip_tags(ob_get_clean()); 261 | 262 | preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches); 263 | $version = $matches[1]; 264 | } 265 | 266 | $this->addRecommendation( 267 | version_compare($version, '4.0', '>='), 268 | 'intl ICU version should be at least 4+', 269 | 'Upgrade your intl extension with a newer ICU version (4+).' 270 | ); 271 | 272 | /* 273 | $this->addRecommendation( 274 | Intl::getIcuDataVersion() <= Intl::getIcuVersion(), 275 | sprintf('intl ICU version installed on your system is outdated (%s) and does not match the ICU data bundled with Symfony (%s)', Intl::getIcuVersion(), Intl::getIcuDataVersion()), 276 | 'To get the latest internationalization data upgrade the ICU system package and the intl PHP extension.' 277 | ); 278 | 279 | if (Intl::getIcuDataVersion() <= Intl::getIcuVersion()) { 280 | $this->addRecommendation( 281 | Intl::getIcuDataVersion() === Intl::getIcuVersion(), 282 | sprintf('intl ICU version installed on your system (%s) does not match the ICU data bundled with Symfony (%s)', Intl::getIcuVersion(), Intl::getIcuDataVersion()), 283 | 'To avoid internationalization data inconsistencies upgrade the symfony/intl component.' 284 | ); 285 | } 286 | */ 287 | 288 | $this->addPhpConfigRecommendation( 289 | 'intl.error_level', 290 | create_function('$cfgValue', 'return (int) $cfgValue === 0;'), 291 | true, 292 | 'intl.error_level should be 0 in php.ini', 293 | 'Set "intl.error_level" to "0" in php.ini* to inhibit the messages when an error occurs in ICU functions.' 294 | ); 295 | } 296 | 297 | /** 298 | * Loads realpath_cache_size from php.ini and converts it to int. 299 | * 300 | * (e.g. 16k is converted to 16384 int) 301 | * 302 | * @return int 303 | */ 304 | protected function getRealpathCacheSize() 305 | { 306 | $size = ini_get('realpath_cache_size'); 307 | $size = trim($size); 308 | $unit = ''; 309 | if (!ctype_digit($size)) { 310 | $unit = strtolower(substr($size, -1, 1)); 311 | $size = (int) substr($size, 0, -1); 312 | } 313 | switch ($unit) { 314 | case 'g': 315 | return $size * 1024 * 1024 * 1024; 316 | case 'm': 317 | return $size * 1024 * 1024; 318 | case 'k': 319 | return $size * 1024; 320 | default: 321 | return (int) $size; 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/Cli/SelfUpdate/Updater.php: -------------------------------------------------------------------------------- 1 | newVersion = $this->strategy->getCurrentRemoteVersion($this); 27 | $this->oldVersion = $this->strategy->getCurrentLocalVersion($this); 28 | 29 | if (!empty($this->oldVersion) && !empty($this->newVersion) && ($this->newVersion !== $this->oldVersion)) { 30 | return Comparator::greaterThan($this->newVersion, $this->oldVersion); 31 | } 32 | 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Cli/Traits/CommandCollectorCommandTrait.php: -------------------------------------------------------------------------------- 1 | %s', $type); 31 | }, CommandCollectorFactory::getValidTypes()); 32 | 33 | $description = sprintf( 34 | 'Collect and output commands which can used as script. Valid values: %s.', 35 | implode(', ', $types) 36 | ); 37 | 38 | /** @var $this AbstractCommand */ 39 | $this->addOption( 40 | 'collect-commands', null, 41 | InputOption::VALUE_REQUIRED, 42 | $description 43 | ); 44 | 45 | return $this; 46 | } 47 | 48 | protected function createCommandCollector() 49 | { 50 | /** @var $this AbstractCommand */ 51 | $collect = $this->io->getInput()->getOption('collect-commands'); 52 | 53 | if ($collect && !empty($collect)) { 54 | return CommandCollectorFactory::create($collect); 55 | } 56 | } 57 | 58 | protected function printCollectedCommands(CommandCollectorInterface $collector) 59 | { 60 | /** @var $this AbstractCommand */ 61 | $this->io->section('The following commands were collected during the migration'); 62 | 63 | foreach ($collector->getCommands() as $command) { 64 | $this->io->writeln($command); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Cli/Traits/DryRunCommandTrait.php: -------------------------------------------------------------------------------- 1 | addOption( 41 | 'dry-run', 'N', InputOption::VALUE_NONE, 42 | $description 43 | ); 44 | 45 | return $this; 46 | } 47 | 48 | protected function isDryRun(): bool 49 | { 50 | /** @var AbstractCommand $this */ 51 | return (bool)$this->io->getInput()->getOption('dry-run'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Cli/Traits/DryRunTrait.php: -------------------------------------------------------------------------------- 1 | isDryRun()) { 33 | $message = $this->prefixDryRun($message, $prefix); 34 | } 35 | 36 | return $message; 37 | } 38 | 39 | /** 40 | * Prefix message with DRY-RUN 41 | * 42 | * @param $message 43 | * @param string $prefix 44 | * 45 | * @return string 46 | */ 47 | protected function prefixDryRun(string $message, string $prefix = 'DRY-RUN'): string 48 | { 49 | return sprintf( 50 | '[%s] %s', 51 | $prefix, 52 | $message 53 | ); 54 | } 55 | 56 | abstract protected function isDryRun(): bool; 57 | } 58 | -------------------------------------------------------------------------------- /src/Cli/Util/CodeGeneratorUtils.php: -------------------------------------------------------------------------------- 1 | generate(); 41 | 42 | // remove superfluous blank lines after class opening and before class closing 43 | $code = preg_replace('/^(\{)(\n+)/m', "{\n", $code); 44 | $code = preg_replace('/^(\n+)(\})/m', '}', $code); 45 | 46 | return $code; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Cli/Util/FileUtils.php: -------------------------------------------------------------------------------- 1 | exists($path) || !is_file($path)) { 73 | throw new \InvalidArgumentException(sprintf('File %s does not exist', $path)); 74 | } 75 | 76 | $relativePath = $fs->makePathRelative($path, getcwd()); 77 | $info = new SplFileInfo($path, dirname($relativePath), $relativePath); 78 | 79 | return $info->getContents(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Cli/Util/TextUtils.php: -------------------------------------------------------------------------------- 1 | path = $path; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getPath(): string 47 | { 48 | return $this->path; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getVersion(): string 55 | { 56 | $versionData = $this->getVersionData(); 57 | 58 | return (string)$versionData['version']; 59 | } 60 | 61 | /** 62 | * @return int 63 | */ 64 | public function getRevision(): int 65 | { 66 | $versionData = $this->getVersionData(); 67 | 68 | return (int)$versionData['revision']; 69 | } 70 | 71 | /** 72 | * @return array 73 | */ 74 | public function getVersionData(): array 75 | { 76 | if (null !== $this->data) { 77 | return $this->data; 78 | } 79 | 80 | $this->checkPrerequisites(); 81 | 82 | $process = $this->getProcess(); 83 | $process->run(); 84 | 85 | if (!$process->isSuccessful()) { 86 | throw new \RuntimeException(sprintf('Failed to run version process: %s', $process->getErrorOutput())); 87 | } 88 | 89 | $version = $process->getOutput(); 90 | $data = json_decode($version, true); 91 | 92 | if (JSON_ERROR_NONE !== json_last_error()) { 93 | throw new \RuntimeException(sprintf('Failed to parse JSON version data: %s', json_last_error_msg())); 94 | } 95 | 96 | $this->data = $data; 97 | 98 | return $data; 99 | } 100 | 101 | /** 102 | * Reset cached data 103 | */ 104 | public function reset() 105 | { 106 | $this->data = null; 107 | } 108 | 109 | private function checkPrerequisites() 110 | { 111 | $fs = new Filesystem(); 112 | if (!$fs->exists($this->path)) { 113 | throw new \InvalidArgumentException(sprintf('Invalid path: %s', $this->path)); 114 | } 115 | 116 | if (!$fs->exists($this->path . '/vendor/autoload.php')) { 117 | throw new \InvalidArgumentException('vendor/autoload.php not found. Is the installation set up properly?'); 118 | } 119 | } 120 | 121 | private function getProcess(): PhpProcess 122 | { 123 | $code = <<<'EOF' 124 | \Pimcore\Version::getVersion(), 129 | 'revision' => \Pimcore\Version::getRevision() 130 | ]; 131 | 132 | echo json_encode($data); 133 | EOF; 134 | 135 | return new PhpProcess($code, $this->path); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Config/System/Pimcore5ConfigProcessor.php: -------------------------------------------------------------------------------- 1 | getExtension() !== 'php') { 34 | throw new \InvalidArgumentException('File must be a PHP file'); 35 | } 36 | 37 | $config = include $file->getRealPath(); 38 | if (false === $config || !is_array($config)) { 39 | throw new \RuntimeException('Failed to read config file'); 40 | } 41 | 42 | return $config; 43 | } 44 | 45 | public function dumpConfig(array $config): string 46 | { 47 | $encoder = new PHPEncoder(); 48 | $encoded = $encoder->encode($config, [ 49 | 'array.inline' => false, 50 | 'array.omit' => false, 51 | ]); 52 | 53 | $result = 'processSmtpConfig($processor, $configuration, $config, 'email'); 64 | $config = $this->processSmtpConfig($processor, $configuration, $config, 'newsletter'); 65 | 66 | return $config; 67 | } 68 | 69 | private function processSmtpConfig(Processor $processor, ConfigurationInterface $configuration, array $config, string $key): array 70 | { 71 | $existingConfig = isset($config[$key]) && isset($config[$key]['smtp']) ? $config[$key]['smtp'] : []; 72 | 73 | $toProcess = [ 74 | $existingConfig 75 | ]; 76 | 77 | $processed = $processor->processConfiguration( 78 | $configuration, 79 | $toProcess 80 | ); 81 | 82 | $config[$key]['smtp'] = $processed; 83 | 84 | return $config; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Config/System/SmtpNodeConfiguration.php: -------------------------------------------------------------------------------- 1 | root('smtp'); 33 | $rootNode 34 | ->addDefaultsIfNotSet() 35 | ->beforeNormalization() 36 | ->ifTrue(function ($v) { 37 | return is_array($v) && array_key_exists('name', $v); 38 | }) 39 | ->then(function ($v) { 40 | @trigger_error('The SMTP configuration "name" is not used anymore', E_USER_DEPRECATED); 41 | unset($v['name']); 42 | 43 | return $v; 44 | }) 45 | ->end() 46 | ->children() 47 | ->scalarNode('host') 48 | ->defaultValue('') 49 | ->end() 50 | ->scalarNode('port') 51 | ->defaultValue('') 52 | ->end() 53 | ->scalarNode('name')->end() 54 | ->scalarNode('ssl') 55 | ->defaultNull() 56 | ->beforeNormalization() 57 | ->ifEmpty() 58 | ->then(function ($v) { 59 | return null; 60 | }) 61 | ->end() 62 | ->end() 63 | ->arrayNode('auth') 64 | ->addDefaultsIfNotSet() 65 | ->children() 66 | ->scalarNode('method') 67 | ->defaultNull() 68 | ->beforeNormalization() 69 | ->ifEmpty() 70 | ->then(function ($v) { 71 | return null; 72 | }) 73 | ->end() 74 | ->end() 75 | ->scalarNode('username') 76 | ->defaultValue('') 77 | ->end() 78 | ->scalarNode('password') 79 | ->defaultValue('') 80 | ->end() 81 | ->end() 82 | ->end() 83 | ->end(); 84 | 85 | return $treeBuilder; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/AbstractFunctionReferenceFixer.php: -------------------------------------------------------------------------------- 1 | findFunctionCallCandidates($tokens, $sequence); 37 | } 38 | 39 | /** 40 | * Extracts tokens for a given argument 41 | * 42 | * @param Tokens $tokens 43 | * @param array $arguments The result of getArguments() 44 | * @param int $argumentIndex The index of the argument 45 | * @param bool $keepIndex 46 | * 47 | * @return array|Token[] 48 | * 49 | * @deprecated use FunctionAnalyzer instead 50 | */ 51 | protected function extractArgumentTokens(Tokens $tokens, array $arguments, int $argumentIndex, bool $keepIndex = false): array 52 | { 53 | return (new FunctionAnalyzer())->extractArgumentTokens($tokens, $arguments, $argumentIndex, $keepIndex); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/Controller/ActionRequestFixer.php: -------------------------------------------------------------------------------- 1 | isTokenKindFound(T_CLASS); 39 | } 40 | 41 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 42 | { 43 | $tokensAnalyzer = new TokensAnalyzer($tokens); 44 | 45 | for ($index = $tokens->getSize() - 1; $index > 0; --$index) { 46 | if (!$tokens[$index]->isGivenKind(T_CLASS)) { 47 | continue; 48 | } 49 | 50 | $className = $tokens->getNextMeaningfulToken($index); 51 | if (!$className || !$this->isValidControllerName($tokens[$className]->getContent())) { 52 | continue; 53 | } 54 | 55 | // figure out where the classy starts 56 | $classStart = $tokens->getNextTokenOfKind($index, ['{']); 57 | 58 | // figure out where the classy ends 59 | $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); 60 | 61 | $updated = $this->updateActions($tokens, $tokensAnalyzer, $classStart, $classEnd); 62 | 63 | if ($updated) { 64 | $importsModifier = new ImportsModifier($tokens); 65 | $importsModifier->addImport($classStart, 'Symfony\\Component\\HttpFoundation\\Request'); 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Finds all public methods which end in Action and adds a Request $request argument if not already existing. Returns 72 | * if an action was updated to further add the namespace import. 73 | * 74 | * @param Tokens $tokens 75 | * @param TokensAnalyzer $tokensAnalyzer 76 | * @param int $classStart 77 | * @param int $classEnd 78 | * 79 | * @return bool 80 | */ 81 | private function updateActions(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $classStart, int $classEnd): bool 82 | { 83 | $argumentsAnalyzer = new ArgumentsAnalyzer(); 84 | $actionAnalyzer = new ActionAnalyzer(); 85 | 86 | $updated = false; 87 | for ($index = $classEnd; $index > $classStart; --$index) { 88 | if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { 89 | continue; 90 | } 91 | 92 | if (!$actionAnalyzer->isValidAction($tokens, $tokensAnalyzer, $index)) { 93 | continue; 94 | } 95 | 96 | $methodNameToken = $tokens->getNextMeaningfulToken($index); 97 | if (null === $methodNameToken) { 98 | continue; 99 | } 100 | 101 | $openParenthesis = $tokens->getNextTokenOfKind($methodNameToken, ['(']); 102 | $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); 103 | $arguments = $argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis); 104 | 105 | $requestNeeded = true; 106 | foreach ($arguments as $argument) { 107 | if ($tokens[$argument]->getContent() === '$request') { 108 | $requestNeeded = false; 109 | } 110 | } 111 | 112 | if ($requestNeeded) { 113 | $requestArgument = [ 114 | new Token([T_STRING, 'Request']), 115 | new Token([T_WHITESPACE, ' ']), 116 | new Token([T_VARIABLE, '$request']) 117 | ]; 118 | 119 | if (count($arguments) > 0) { 120 | $requestArgument[] = new Token(','); 121 | $requestArgument[] = new Token([T_WHITESPACE, ' ']); 122 | } 123 | 124 | $tokens->insertAt($openParenthesis + 1, $requestArgument); 125 | 126 | $updated = true; 127 | } 128 | } 129 | 130 | return $updated; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/Controller/ControllerBaseClassFixer.php: -------------------------------------------------------------------------------- 1 | isTokenKindFound(T_CLASS); 36 | } 37 | 38 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 39 | { 40 | $importsModifier = new ImportsModifier($tokens); 41 | 42 | $classIndexes = []; 43 | for ($index = $tokens->getSize() - 1; $index > 0; --$index) { 44 | if ($tokens[$index]->isGivenKind(T_CLASS)) { 45 | $classIndexes[] = $index; 46 | } 47 | } 48 | 49 | foreach ($classIndexes as $index) { 50 | $className = $tokens->getNextMeaningfulToken($index); 51 | if (!$className || !$this->isValidControllerName($tokens[$className]->getContent())) { 52 | continue; 53 | } 54 | 55 | // figure out where the classy starts 56 | $classStart = $tokens->getNextTokenOfKind($index, ['{']); 57 | 58 | // back up class definition to keep a reference to any class hierarchies 59 | $backup = $this->backupClassDefinition($tokens, $index, $classStart); 60 | 61 | if ($this->updateClassDefinition($tokens, $className, $classStart)) { 62 | // insert backup as comment 63 | $tokens->insertAt($backup[0], [ 64 | new Token([T_COMMENT, $backup[1]]), 65 | new Token([T_WHITESPACE, "\n"]) 66 | ]); 67 | 68 | // make sure the FrontendController is imported 69 | $importsModifier->addImport($classStart, 'Pimcore\Controller\FrontendController'); 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * Change extends clause to FrontendController or add an extend if not existing yet 76 | * 77 | * @param Tokens $tokens 78 | * @param int $className 79 | * @param int $classStart 80 | * 81 | * @return bool 82 | */ 83 | private function updateClassDefinition(Tokens $tokens, int $className, int $classStart) 84 | { 85 | $extends = null; 86 | for ($i = $className; $i < $classStart; ++$i) { 87 | if ($tokens[$i]->isGivenKind(T_EXTENDS)) { 88 | $extends = $i; 89 | break; 90 | } 91 | } 92 | 93 | if (null !== $extends) { 94 | $parentClass = $tokens->getNextMeaningfulToken($extends); 95 | 96 | // already set - abort 97 | if ($tokens[$parentClass]->getContent() === 'FrontendController') { 98 | return false; 99 | } 100 | 101 | $tokens->offsetSet($parentClass, new Token([T_STRING, 'FrontendController'])); 102 | } else { 103 | $tokens->insertAt($className + 1, [ 104 | new Token([T_WHITESPACE, ' ']), 105 | new Token([T_EXTENDS, 'extends']), 106 | new Token([T_STRING, 'FrontendController']) 107 | ]); 108 | } 109 | 110 | return true; 111 | } 112 | 113 | /** 114 | * Backup class definition as comment 115 | * 116 | * @param Tokens $tokens 117 | * @param int $classIndex 118 | * @param int $classStart 119 | * 120 | * @return array 121 | */ 122 | private function backupClassDefinition(Tokens $tokens, int $classIndex, int $classStart) 123 | { 124 | $startIndex = $classIndex; 125 | 126 | $previousToken = $tokens->getPrevMeaningfulToken($classIndex); 127 | if ($tokens[$previousToken]->isGivenKind(T_FINAL)) { 128 | $startIndex = $previousToken; 129 | } 130 | 131 | if (!$this->isNewlineToken($tokens[$startIndex - 1])) { 132 | // no newline before [final] class Foo - abort here 133 | throw new \UnexpectedValueException(sprintf( 134 | 'Expected a newline before class definition but got "%s"', 135 | $tokens[$startIndex - 1]->getName() 136 | )); 137 | } 138 | 139 | $comment = '// '; 140 | for ($k = $startIndex; $k <= $classStart - 1; $k++) { 141 | $comment .= str_replace("\n", ' ', $tokens[$k]->getContent()); 142 | } 143 | 144 | // remove extra whitespace 145 | $comment = preg_replace('/ +/', ' ', $comment); 146 | $comment = trim($comment); 147 | 148 | return [ 149 | $startIndex, 150 | $comment 151 | ]; 152 | } 153 | 154 | private function isNewlineToken(Token $token): bool 155 | { 156 | return $token->isWhitespace() && false !== strpos($token->getContent(), "\n"); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/Controller/ControllerGetParamFixer.php: -------------------------------------------------------------------------------- 1 | getParam() to $request->get()', 28 | [new CodeSample('getParam()')] 29 | ); 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function isCandidate(Tokens $tokens) 36 | { 37 | return 38 | $tokens->isTokenKindFound(T_CLASS) 39 | && $tokens->isTokenKindFound(T_VARIABLE) 40 | && $tokens->isTokenKindFound(T_OBJECT_OPERATOR) 41 | && $tokens->isTokenKindFound(T_CONSTANT_ENCAPSED_STRING); 42 | } 43 | 44 | protected function getSequence(): array 45 | { 46 | return [ 47 | [T_VARIABLE, '$this'], 48 | [T_OBJECT_OPERATOR, '->'], 49 | [T_STRING, 'getParam'], 50 | '(' 51 | ]; 52 | } 53 | 54 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 55 | { 56 | $tokensAnalyzer = new TokensAnalyzer($tokens); 57 | 58 | for ($index = $tokens->getSize() - 1; $index > 0; --$index) { 59 | if (!$tokens[$index]->isGivenKind(T_CLASS)) { 60 | continue; 61 | } 62 | 63 | $className = $tokens->getNextMeaningfulToken($index); 64 | if (!$className || !$this->isValidControllerName($tokens[$className]->getContent())) { 65 | continue; 66 | } 67 | 68 | $classStart = $tokens->getNextTokenOfKind($index, ['{']); 69 | $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); 70 | 71 | $this->processClass($tokens, $tokensAnalyzer, $classStart, $classEnd); 72 | } 73 | } 74 | 75 | private function processClass(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $classStart, int $classEnd) 76 | { 77 | $actionAnalyzer = new ActionAnalyzer(); 78 | 79 | for ($index = $classEnd; $index > $classStart; --$index) { 80 | if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { 81 | continue; 82 | } 83 | 84 | if (!$actionAnalyzer->isValidAction($tokens, $tokensAnalyzer, $index)) { 85 | continue; 86 | } 87 | 88 | $methodNameToken = $tokens->getNextMeaningfulToken($index); 89 | if (null === $methodNameToken) { 90 | continue; 91 | } 92 | 93 | $methodStart = $tokens->getNextTokenOfKind($methodNameToken, ['{']); 94 | $methodEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStart); 95 | 96 | if (null === $methodStart || null === $methodEnd) { 97 | throw new \UnexpectedValueException(sprintf('Failed to find method end for method "%s"', $tokens[$methodNameToken]->getContent())); 98 | } 99 | 100 | $this->processAction($tokens, $methodStart, $methodEnd); 101 | } 102 | } 103 | 104 | private function processAction(Tokens $tokens, int $methodStart, int $methodEnd) 105 | { 106 | $functionAnalyzer = new FunctionAnalyzer(); 107 | 108 | $sequence = $this->getSequence(); 109 | $candidates = $functionAnalyzer->findFunctionCallCandidates($tokens, $sequence, $methodStart, $methodEnd); 110 | 111 | foreach ($candidates as $candidate) { 112 | list($match, $openParenthesis, $closeParenthesis) = $candidate; 113 | 114 | $this->processCandidate($tokens, $match, $openParenthesis, $closeParenthesis); 115 | } 116 | } 117 | 118 | /** 119 | * @param Tokens $tokens 120 | * @param Token[] $match 121 | * @param int $openParenthesis 122 | * @param int $closeParenthesis 123 | */ 124 | private function processCandidate(Tokens $tokens, array $match, int $openParenthesis, int $closeParenthesis) 125 | { 126 | $indexes = array_keys($match); 127 | 128 | // replace $this->getParam() with $request->get() 129 | // we assume the $request was already added to the action method in ActionRequestFixer 130 | $tokens->offsetSet($indexes[0], new Token([T_VARIABLE, '$request'])); 131 | $tokens->offsetSet($indexes[2], new Token([T_STRING, 'get'])); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/Controller/ControllerNamespaceFixer.php: -------------------------------------------------------------------------------- 1 | isTokenKindFound(T_CLASS) && !$tokens->isTokenKindFound(T_NAMESPACE); 39 | } 40 | 41 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 42 | { 43 | $newNamespaceIndex = null; 44 | for ($index = 0; $index < $tokens->getSize() - 1; $index++) { 45 | if ($tokens[$index]->isGivenKind(T_USE) || $tokens[$index]->isGivenKind(Token::getClassyTokenKinds())) { 46 | $newNamespaceIndex = $index - 1; 47 | break; 48 | } 49 | } 50 | 51 | if (null === $newNamespaceIndex) { 52 | return; 53 | } 54 | 55 | $this->getLogger()->info($file, 'Adding namespace {namespace}', ['namespace' => 'AppBundle\\Controller']); 56 | 57 | $namespace = [ 58 | new Token([T_NAMESPACE, 'namespace']), 59 | new Token([T_WHITESPACE, ' ']), 60 | new Token([T_STRING, 'AppBundle']), 61 | new Token([T_NS_SEPARATOR, '\\']), 62 | new Token([T_STRING, 'Controller']), 63 | new Token(';'), 64 | ]; 65 | 66 | $manipulator = new TokenInsertManipulator($tokens); 67 | $manipulator->insertAtIndex($newNamespaceIndex, $namespace, 1, 1); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/Traits/FixerNameTrait.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 33 | } 34 | 35 | protected function getLogger(): FixerLoggerInterface 36 | { 37 | if (null === $this->logger) { 38 | $this->logger = new FixerLogger(); 39 | } 40 | 41 | return $this->logger; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/Traits/SupportsControllerTrait.php: -------------------------------------------------------------------------------- 1 | getFilename(), -strlen($expectedExtension)) === $expectedExtension) { 30 | return false; 31 | } 32 | 33 | $baseName = $file->getBasename($expectedExtension); 34 | if (!$this->isValidControllerName($baseName)) { 35 | return false; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | public function isValidControllerName(string $name): bool 42 | { 43 | return (bool) preg_match('/Controller$/', $name); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/AbstractViewHelperTemplatePathFixer.php: -------------------------------------------------------------------------------- 1 | getFilename(), -strlen($expectedExtension)) === $expectedExtension; 47 | } 48 | 49 | /** 50 | * @inheritDoc 51 | */ 52 | public function isCandidate(Tokens $tokens) 53 | { 54 | return $tokens->isTokenKindFound(T_VARIABLE) 55 | && $tokens->isTokenKindFound(T_OBJECT_OPERATOR) 56 | && $tokens->isTokenKindFound(T_CONSTANT_ENCAPSED_STRING); 57 | } 58 | 59 | /** 60 | * The sequence to look for. Must end in opening parenthesis! 61 | * 62 | * @return array 63 | */ 64 | abstract protected function getSequence(): array; 65 | 66 | /** 67 | * Determine if helper result should be echoed 68 | * 69 | * @return bool 70 | */ 71 | protected function needsEchoOutput(): bool 72 | { 73 | return true; 74 | } 75 | 76 | /** 77 | * Get argument index of the path argument 78 | * 79 | * @return int 80 | */ 81 | protected function getPathArgumentIndex(): int 82 | { 83 | return 0; 84 | } 85 | 86 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 87 | { 88 | $sequence = $this->getSequence(); 89 | $candidates = $this->findFunctionCallCandidates($tokens, $sequence); 90 | 91 | foreach ($candidates as $candidate) { 92 | list($match, $openParenthesis, $closeParenthesis) = $candidate; 93 | 94 | $this->processCandidate($tokens, $match, $openParenthesis, $closeParenthesis); 95 | } 96 | } 97 | 98 | /** 99 | * @param Tokens $tokens 100 | * @param Token[] $match 101 | * @param int $openParenthesis 102 | * @param int $closeParenthesis 103 | */ 104 | protected function processCandidate(Tokens $tokens, array $match, int $openParenthesis, int $closeParenthesis) 105 | { 106 | $this->processPathArgument($tokens, $match, $openParenthesis, $closeParenthesis); 107 | 108 | if ($this->needsEchoOutput()) { 109 | $this->processEchoOutput($tokens, $match); 110 | } 111 | } 112 | 113 | /** 114 | * Make sure the output is echoed 115 | * 116 | * @param Tokens $tokens 117 | * @param array $match 118 | */ 119 | protected function processEchoOutput(Tokens $tokens, array $match) 120 | { 121 | $indexes = array_keys($match); 122 | $prev = $tokens->getPrevMeaningfulToken($indexes[0]); 123 | 124 | if (null === $prev) { 125 | return; 126 | } 127 | 128 | $prevToken = $tokens[$prev]; 129 | 130 | // we're ok 131 | if ($prevToken->isGivenKind([T_OPEN_TAG_WITH_ECHO, T_ECHO])) { 132 | return; 133 | } 134 | 135 | // template() -> template() 136 | if ($prevToken->isGivenKind(T_OPEN_TAG)) { 137 | $tokens->offsetSet($prev, new Token([T_OPEN_TAG_WITH_ECHO, 'insertAt($prev + 1, new Token([T_WHITESPACE, ' '])); 139 | } else { 140 | // template() -> template() 141 | $tokens->insertAt($prev + 1, [ 142 | new Token([T_WHITESPACE, ' ']), 143 | new Token([T_ECHO, 'echo']), 144 | ]); 145 | } 146 | } 147 | 148 | /** 149 | * Extract the path argument at the given index and try to apply the follwing normalizations: 150 | * 151 | * - Strip leading slash 152 | * - Change first path segment to CamelCase (first char uppercase) 153 | * - Change filename to camelCase and change extension to .html.php 154 | * 155 | * @param Tokens $tokens 156 | * @param Token[] $match 157 | * @param int $openParenthesis 158 | * @param int $closeParenthesis 159 | */ 160 | protected function processPathArgument(Tokens $tokens, array $match, int $openParenthesis, int $closeParenthesis) 161 | { 162 | $analyzer = new ArgumentsAnalyzer(); 163 | $arguments = $analyzer->getArguments($tokens, $openParenthesis, $closeParenthesis); 164 | $argumentTokens = $this->extractArgumentTokens($tokens, $arguments, $this->getPathArgumentIndex(), true); 165 | $argumentIndexes = array_keys($argumentTokens); 166 | 167 | /** @var int $pathCasingTokenIndex */ 168 | $pathCasingTokenIndex = null; 169 | 170 | /** @var int $filenameTokenIndex */ 171 | $filenameTokenIndex = null; 172 | 173 | if (count($argumentTokens) === 1) { 174 | $firstTokenIndex = $argumentIndexes[0]; 175 | $firstToken = $argumentTokens[$firstTokenIndex]; 176 | 177 | // easiest scenario - we have a single string argument 178 | if ($firstToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { 179 | $pathCasingTokenIndex = $firstTokenIndex; 180 | $filenameTokenIndex = $firstTokenIndex; 181 | } 182 | 183 | // no string argument -> we skip this candidate as we don't know what to do 184 | // TODO trigger warning? 185 | } elseif (count($argumentTokens) > 1) { 186 | $firstTokenIndex = $argumentIndexes[0]; 187 | $firstToken = $argumentTokens[$firstTokenIndex]; 188 | 189 | $lastTokenIndex = $argumentIndexes[count($argumentIndexes) - 1]; 190 | $lastToken = $argumentTokens[$lastTokenIndex]; 191 | 192 | // multiple tokens in first argument (e.g. concatenated strings or method call 193 | // handle first token if it is a string 194 | if ($firstToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { 195 | $pathCasingTokenIndex = $firstTokenIndex; 196 | } 197 | 198 | // handle last argument if it is a string 199 | if ($lastToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { 200 | $filenameTokenIndex = $lastTokenIndex; 201 | } 202 | } 203 | 204 | if (null !== $pathCasingTokenIndex) { 205 | $pathCasingToken = $tokens->offsetGet($pathCasingTokenIndex); 206 | 207 | list($path, $quote) = TextUtils::extractQuotedString($pathCasingToken->getContent()); 208 | 209 | $path = $this->normalizeFirstTemplatePathSegment($path); 210 | $path = TextUtils::quoteString($path, $quote); 211 | 212 | $tokens->offsetSet( 213 | $pathCasingTokenIndex, 214 | new Token([$pathCasingToken->getId(), $path]) 215 | ); 216 | } 217 | 218 | if (null != $filenameTokenIndex) { 219 | $filenameToken = $tokens->offsetGet($filenameTokenIndex); 220 | 221 | list($path, $quote) = TextUtils::extractQuotedString($filenameToken->getContent()); 222 | 223 | $path = $this->normalizeTemplatePathFilename($path); 224 | $path = TextUtils::quoteString($path, $quote); 225 | 226 | $tokens->offsetSet( 227 | $filenameTokenIndex, 228 | new Token([$filenameToken->getId(), $path]) 229 | ); 230 | } 231 | } 232 | 233 | /** 234 | * Changes the first segment of the path to CamelCase 235 | * 236 | * @param string $string 237 | * 238 | * @return string 239 | */ 240 | protected function normalizeFirstTemplatePathSegment(string $string): string 241 | { 242 | $string = ltrim($string, '/'); 243 | $parts = explode('/', $string); 244 | 245 | // first part of path to uppercase CamelCase 246 | if (count($parts) > 1) { 247 | $parts[0] = TextUtils::dashesToCamelCase($parts[0], true); 248 | } 249 | 250 | $string = implode('/', $parts); 251 | 252 | return $string; 253 | } 254 | 255 | /** 256 | * Changes the filename to camelCase.html.php 257 | * 258 | * @param string $string 259 | * 260 | * @return string 261 | */ 262 | protected function normalizeTemplatePathFilename(string $string): string 263 | { 264 | $parts = explode('/', $string); 265 | 266 | // filename to camelCase 267 | $filename = array_pop($parts); 268 | 269 | // normalize extension to html.php if not already done 270 | $filename = preg_replace('/(?layout()->content with slots helper', 23 | [new CodeSample('layout()->content ?>')] 24 | ); 25 | } 26 | 27 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 28 | { 29 | $sequence = [ 30 | [T_VARIABLE, '$this'], 31 | [T_OBJECT_OPERATOR, '->'], 32 | [T_STRING, 'layout'], 33 | '(', ')', 34 | [T_OBJECT_OPERATOR, '->'], 35 | [T_STRING, 'content'], 36 | ]; 37 | 38 | $currIndex = 0; 39 | while (null !== $currIndex) { 40 | $match = $tokens->findSequence($sequence, $currIndex, $tokens->count() - 1); 41 | 42 | // stop looping if didn't find any new matches 43 | if (null === $match) { 44 | break; 45 | } 46 | 47 | $indexes = array_keys($match); 48 | $lastIndex = array_pop($indexes); 49 | $currIndex = $lastIndex + 1; 50 | 51 | if ($currIndex >= count($tokens)) { 52 | break; 53 | } 54 | 55 | $this->processMatch($tokens, $match); 56 | } 57 | } 58 | 59 | /** 60 | * @param Tokens $tokens 61 | * @param Token[] $match 62 | */ 63 | private function processMatch(Tokens $tokens, array $match) 64 | { 65 | $indexes = array_keys($match); 66 | 67 | $replacement = [ 68 | new Token([T_VARIABLE, '$this']), 69 | new Token([T_OBJECT_OPERATOR, '->']), 70 | new Token([T_STRING, 'slots']), 71 | new Token('('), 72 | new Token(')'), 73 | new Token([T_OBJECT_OPERATOR, '->']), 74 | new Token([T_STRING, 'output']), 75 | new Token('('), 76 | new Token([T_CONSTANT_ENCAPSED_STRING, "'_content'"]), 77 | new Token(')'), 78 | ]; 79 | 80 | $tokens->overrideRange($indexes[0], $indexes[count($indexes) - 1], $replacement); 81 | 82 | $prev = $tokens->getPrevMeaningfulToken($indexes[0]); 83 | $prevToken = $tokens[$prev]; 84 | 85 | if ($prevToken->isGivenKind(T_OPEN_TAG_WITH_ECHO)) { 86 | $tokens->offsetSet($prev, new Token([T_OPEN_TAG, 'removeTrailingWhitespace($prev); 88 | } elseif ($prevToken->isGivenKind(T_ECHO)) { 89 | $tokens->clearAt($prev); 90 | $tokens->removeTrailingWhitespace($prev); 91 | } 92 | } 93 | 94 | /** 95 | * @inheritDoc 96 | */ 97 | public function supports(\SplFileInfo $file) 98 | { 99 | $expectedExtension = '.html.php'; 100 | 101 | return substr($file->getFilename(), -strlen($expectedExtension)) === $expectedExtension; 102 | } 103 | 104 | /** 105 | * @inheritDoc 106 | */ 107 | public function isCandidate(Tokens $tokens) 108 | { 109 | return $tokens->isTokenKindFound(T_VARIABLE) && $tokens->isTokenKindFound(T_OBJECT_OPERATOR); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/NavigationFixer.php: -------------------------------------------------------------------------------- 1 | pimcoreNavigation() with $this->navigation()', 25 | [new CodeSample('pimcoreNavigation() ?>')] 26 | ); 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | public function isRisky() 33 | { 34 | return false; 35 | } 36 | 37 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 38 | { 39 | $sequence = [ 40 | [T_VARIABLE, '$this'], 41 | [T_OBJECT_OPERATOR, '->'], 42 | [T_STRING, 'pimcoreNavigation'], 43 | '(' 44 | ]; 45 | 46 | $candidates = $this->findFunctionCallCandidates($tokens, $sequence); 47 | 48 | foreach ($candidates as $candidate) { 49 | /** @var Token[] $match */ 50 | list($match, $openParenthesis, $closeParenthesis) = $candidate; 51 | 52 | $indexes = array_keys($match); 53 | $tokens->offsetSet($indexes[2], new Token([$match[$indexes[2]]->getId(), 'navigation'])); 54 | } 55 | } 56 | 57 | /** 58 | * @inheritDoc 59 | */ 60 | public function supports(\SplFileInfo $file) 61 | { 62 | $expectedExtension = '.html.php'; 63 | 64 | return substr($file->getFilename(), -strlen($expectedExtension)) === $expectedExtension; 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | public function isCandidate(Tokens $tokens) 71 | { 72 | return $tokens->isTokenKindFound(T_VARIABLE) && $tokens->isTokenKindFound(T_OBJECT_OPERATOR); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/NavigationRenderPartialHelperFixer.php: -------------------------------------------------------------------------------- 1 | navigation()->renderPartial() to use .html.php templates and correct casing', 21 | [new CodeSample('navigation()->renderPartial($mainNav, \'includes/navigation.php\' ?>')] 22 | ); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | protected function getSequence(): array 29 | { 30 | return [ 31 | [T_OBJECT_OPERATOR, '->'], 32 | [T_STRING, 'renderPartial'], 33 | '(' 34 | ]; 35 | } 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | protected function needsEchoOutput(): bool 41 | { 42 | return false; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | protected function getPathArgumentIndex(): int 49 | { 50 | return 1; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/NavigationSetPartialHelperFixer.php: -------------------------------------------------------------------------------- 1 | navigation()->setPartial() to use .html.php templates and correct casing', 21 | [new CodeSample('navigation()->setPartial(\'includes/navigation.php\' ?>')] 22 | ); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | protected function getSequence(): array 29 | { 30 | return [ 31 | [T_OBJECT_OPERATOR, '->'], 32 | [T_STRING, 'setPartial'], 33 | '(' 34 | ]; 35 | } 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | protected function needsEchoOutput(): bool 41 | { 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/PartialHelperFixer.php: -------------------------------------------------------------------------------- 1 | partial() to use $this->render() instead and to use .html.php templates, correct casing and to echo the output', 21 | [new CodeSample('partial(\'includes/gallery.php\' ?>')] 22 | ); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | protected function getSequence(): array 29 | { 30 | return [ 31 | [T_VARIABLE, '$this'], 32 | [T_OBJECT_OPERATOR, '->'], 33 | [T_STRING, 'partial'], 34 | '(' 35 | ]; 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | protected function processCandidate(Tokens $tokens, array $match, int $openParenthesis, int $closeParenthesis) 42 | { 43 | /** @var Token $token */ 44 | foreach ($match as $i => $token) { 45 | // change call from partial() to render() 46 | if ($token->isGivenKind(T_STRING) && $token->getContent() === 'partial') { 47 | $tokens->offsetSet($i, new Token([$token->getId(), 'render'])); 48 | } 49 | } 50 | 51 | parent::processCandidate($tokens, $match, $openParenthesis, $closeParenthesis); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/SetLayoutFixer.php: -------------------------------------------------------------------------------- 1 | layout()->setLayout(\'layout\') with calls to $this->extend()', 24 | [new CodeSample('layout()->setLayout(\'layout\') ?>')] 25 | ); 26 | } 27 | 28 | /** 29 | * @inheritDoc 30 | */ 31 | public function isRisky() 32 | { 33 | return false; 34 | } 35 | 36 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 37 | { 38 | $sequence = [ 39 | [T_VARIABLE, '$this'], 40 | [T_OBJECT_OPERATOR, '->'], 41 | [T_STRING, 'layout'], 42 | '(', ')', 43 | [T_OBJECT_OPERATOR, '->'], 44 | [T_STRING, 'setLayout'], 45 | '(' 46 | ]; 47 | 48 | $candidates = $this->findFunctionCallCandidates($tokens, $sequence); 49 | 50 | foreach ($candidates as $candidate) { 51 | list($match, $openParenthesis, $closeParenthesis) = $candidate; 52 | 53 | $this->processMatch($tokens, $match, $openParenthesis, $closeParenthesis); 54 | } 55 | } 56 | 57 | /** 58 | * @param Tokens $tokens 59 | * @param Token[] $match 60 | */ 61 | private function processMatch(Tokens $tokens, array $match, $openParenthesis, $closeParenthesis) 62 | { 63 | $replacement = [ 64 | new Token([T_VARIABLE, '$this']), 65 | new Token([T_OBJECT_OPERATOR, '->']), 66 | new Token([T_STRING, 'extend']), 67 | new Token('(') 68 | ]; 69 | 70 | $argument = $this->processArguments($tokens, $openParenthesis, $closeParenthesis); 71 | foreach ($argument as $token) { 72 | $replacement[] = $token; 73 | } 74 | 75 | $indexes = array_keys($match); 76 | $tokens->overrideRange($indexes[0], $closeParenthesis - 1, $replacement); 77 | } 78 | 79 | /** 80 | * Finds arguments, adds .html.php to the first one and drops the rest 81 | * 82 | * @param Tokens $tokens 83 | * @param int $openParenthesis 84 | * @param int $closeParenthesis 85 | * 86 | * @return Token[] 87 | */ 88 | private function processArguments(Tokens $tokens, $openParenthesis, $closeParenthesis) 89 | { 90 | $analyzer = new ArgumentsAnalyzer(); 91 | $arguments = $analyzer->getArguments($tokens, $openParenthesis, $closeParenthesis); 92 | $indexes = array_keys($arguments); 93 | 94 | // arguments is an array indexed by start -> end 95 | $startIndex = $indexes[0]; 96 | $endIndex = $arguments[$startIndex]; 97 | 98 | // we just use the first argument and drop the rest, so we just need tokens of the first argument 99 | /** @var Token[] $argument */ 100 | $argument = []; 101 | 102 | // first argument is a simple string -> just alter the string and add our file extension 103 | if ($startIndex === $endIndex && $tokens[$startIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { 104 | $chars = str_split($tokens[$startIndex]->getContent()); 105 | $quoteChar = array_pop($chars); 106 | 107 | $tokens[$startIndex] = new Token([$tokens[$startIndex]->getId(), implode('', $chars) . '.html.php' . $quoteChar]); 108 | $argument[] = $tokens[$startIndex]; 109 | } else { 110 | // add all argument tokens and concat the file extension 111 | for ($i = $startIndex; $i <= $endIndex; $i++) { 112 | $argument[] = $tokens[$i]; 113 | } 114 | 115 | $argument[] = new Token([T_WHITESPACE, ' ']); 116 | $argument[] = new Token('.'); 117 | $argument[] = new Token([T_WHITESPACE, ' ']); 118 | $argument[] = new Token([T_CONSTANT_ENCAPSED_STRING, "'.html.php'"]); 119 | } 120 | 121 | return $argument; 122 | } 123 | 124 | /** 125 | * @inheritDoc 126 | */ 127 | public function supports(\SplFileInfo $file) 128 | { 129 | $expectedExtension = '.html.php'; 130 | 131 | return substr($file->getFilename(), -strlen($expectedExtension)) === $expectedExtension; 132 | } 133 | 134 | /** 135 | * @inheritDoc 136 | */ 137 | public function isCandidate(Tokens $tokens) 138 | { 139 | return $tokens->isTokenKindFound(T_VARIABLE) && $tokens->isTokenKindFound(T_OBJECT_OPERATOR); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/TemplateHelperFixer.php: -------------------------------------------------------------------------------- 1 | template() to use .html.php templates, correct casing and to echo the output', 19 | [new CodeSample('template(\'includes/gallery.php\' ?>')] 20 | ); 21 | } 22 | 23 | /** 24 | * @inheritDoc 25 | */ 26 | protected function getSequence(): array 27 | { 28 | return [ 29 | [T_VARIABLE, '$this'], 30 | [T_OBJECT_OPERATOR, '->'], 31 | [T_STRING, 'template'], 32 | '(' 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/CsFixer/Fixer/View/TypehintHeaderFixer.php: -------------------------------------------------------------------------------- 1 | getFilename(), -strlen($expectedExtension)) === $expectedExtension; 62 | } 63 | 64 | protected function applyFix(\SplFileInfo $file, Tokens $tokens) 65 | { 66 | $blocks = array_values($tokens->findGivenKind(T_DOC_COMMENT)); 67 | 68 | /** @var Token $block */ 69 | foreach ($blocks as $block) { 70 | if ($block->getContent() === $this->comment) { 71 | // docblock was already found - abort 72 | return; 73 | } 74 | } 75 | 76 | $insert = [ 77 | new Token([T_OPEN_TAG, "comment]), 79 | new Token([T_WHITESPACE, "\n"]), 80 | new Token([T_CLOSE_TAG, "?>\n"]), 81 | ]; 82 | 83 | $tokens->insertAt(0, $insert); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/CsFixer/Log/FixerLogger.php: -------------------------------------------------------------------------------- 1 | levels)) { 51 | throw new \InvalidArgumentException(sprintf('The log level "%s" is invalid', $level)); 52 | } 53 | 54 | if (null === $this->timezone) { 55 | $this->timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); 56 | } 57 | 58 | $dateTime = new \DateTimeImmutable('now', $this->timezone); 59 | $dateTime->setTimezone($this->timezone); 60 | 61 | $record = [ 62 | 'message' => $message, 63 | 'context' => $context, 64 | 'level' => $level, 65 | 'datetime' => $dateTime, 66 | ]; 67 | 68 | $recordValue = new Record($dateTime, $level, $message, $context); 69 | 70 | $fileIdentifier = $this->getFileIdentifier($file); 71 | if (!isset($this->records[$fileIdentifier])) { 72 | $this->records[$fileIdentifier] = []; 73 | } 74 | 75 | $this->records[$fileIdentifier][] = $recordValue; 76 | } 77 | 78 | private function getFileIdentifier(\SplFileInfo $file) 79 | { 80 | return $file->getRealPath(); 81 | } 82 | 83 | public function hasRecords(): bool 84 | { 85 | return count($this->records) > 0; 86 | } 87 | 88 | public function getRecords(\SplFileInfo $file = null): array 89 | { 90 | if (null === $file) { 91 | return $this->records; 92 | } 93 | 94 | $fileIdentifier = $this->getFileIdentifier($file); 95 | if (isset($this->records[$fileIdentifier])) { 96 | return $this->records[$fileIdentifier]; 97 | } 98 | 99 | return []; 100 | } 101 | 102 | public function log(\SplFileInfo $file, string $level, string $message, array $context = []) 103 | { 104 | $this->addRecord($file, $level, $message, $context); 105 | } 106 | 107 | public function emergency(\SplFileInfo $file, string $message, array $context = []) 108 | { 109 | $this->addRecord($file, LogLevel::EMERGENCY, $message, $context); 110 | } 111 | 112 | public function alert(\SplFileInfo $file, string $message, array $context = []) 113 | { 114 | $this->addRecord($file, LogLevel::ALERT, $message, $context); 115 | } 116 | 117 | public function critical(\SplFileInfo $file, string $message, array $context = []) 118 | { 119 | $this->addRecord($file, LogLevel::CRITICAL, $message, $context); 120 | } 121 | 122 | public function error(\SplFileInfo $file, string $message, array $context = []) 123 | { 124 | $this->addRecord($file, LogLevel::ERROR, $message, $context); 125 | } 126 | 127 | public function warning(\SplFileInfo $file, string $message, array $context = []) 128 | { 129 | $this->addRecord($file, LogLevel::WARNING, $message, $context); 130 | } 131 | 132 | public function notice(\SplFileInfo $file, string $message, array $context = []) 133 | { 134 | $this->addRecord($file, LogLevel::NOTICE, $message, $context); 135 | } 136 | 137 | public function info(\SplFileInfo $file, string $message, array $context = []) 138 | { 139 | $this->addRecord($file, LogLevel::INFO, $message, $context); 140 | } 141 | 142 | public function debug(\SplFileInfo $file, string $message, array $context = []) 143 | { 144 | $this->addRecord($file, LogLevel::DEBUG, $message, $context); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/CsFixer/Log/FixerLoggerInterface.php: -------------------------------------------------------------------------------- 1 | dateTime = $dateTime; 47 | $this->level = $level; 48 | $this->message = $message; 49 | $this->context = $context; 50 | } 51 | 52 | public function log(LoggerInterface $logger) 53 | { 54 | $logger->log($this->level, $this->message, $this->context); 55 | } 56 | 57 | public function getDateTime(): \DateTimeImmutable 58 | { 59 | return $this->dateTime; 60 | } 61 | 62 | public function getLevel(): string 63 | { 64 | return $this->level; 65 | } 66 | 67 | public function getMessage(): string 68 | { 69 | return $this->message; 70 | } 71 | 72 | public function getContext(): array 73 | { 74 | return $this->context; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/CsFixer/Tokenizer/Controller/ActionAnalyzer.php: -------------------------------------------------------------------------------- 1 | getNextMeaningfulToken($index); 28 | if (null === $methodNameToken) { 29 | return false; 30 | } 31 | 32 | $methodName = $tokens[$methodNameToken]; 33 | 34 | // only update methods ending in Action 35 | if (!$methodName->isGivenKind(T_STRING) || !preg_match('/Action$/', $methodName->getContent())) { 36 | return false; 37 | } 38 | 39 | $attributes = $tokensAnalyzer->getMethodAttributes($index); 40 | 41 | // only update public methods 42 | if (!(null === $attributes['visibility'] || T_PUBLIC === $attributes['visibility'])) { 43 | return false; 44 | } 45 | 46 | // do not touch abstract methods 47 | if (!$allowAbstractMethods && true === $attributes['abstract']) { 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CsFixer/Tokenizer/FunctionAnalyzer.php: -------------------------------------------------------------------------------- 1 | findSequence($sequence, $currIndex, $endIndex); 58 | 59 | // stop looping if didn't find any new matches 60 | if (null === $match) { 61 | break; 62 | } 63 | 64 | $indexes = array_keys($match); 65 | $openParenthesis = array_pop($indexes); 66 | 67 | $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); 68 | 69 | $candidates[] = [$match, $openParenthesis, $closeParenthesis]; 70 | 71 | $currIndex = $openParenthesis + 1; 72 | if ($currIndex >= $endIndex) { 73 | break; 74 | } 75 | } 76 | 77 | return array_reverse($candidates); 78 | } 79 | 80 | /** 81 | * Extracts tokens for a given argument 82 | * 83 | * @param Tokens $tokens 84 | * @param array $arguments The result of getArguments() 85 | * @param int $argumentIndex The index of the argument 86 | * @param bool $keepIndex 87 | * 88 | * @return array 89 | */ 90 | public function extractArgumentTokens(Tokens $tokens, array $arguments, int $argumentIndex, bool $keepIndex = false): array 91 | { 92 | $indexes = array_keys($arguments); 93 | 94 | if (!isset($indexes[$argumentIndex])) { 95 | throw new \InvalidArgumentException(sprintf('Argument at index %d does not exist', $argumentIndex)); 96 | } 97 | 98 | // arguments is an array indexed by start -> end 99 | $startIndex = $indexes[$argumentIndex]; 100 | $endIndex = $arguments[$startIndex]; 101 | 102 | $argumentTokens = []; 103 | for ($i = $startIndex; $i <= $endIndex; $i++) { 104 | // ignore leading whitespace tokens 105 | if (empty($argumentTokens) && $tokens[$i]->isGivenKind(T_WHITESPACE)) { 106 | continue; 107 | } 108 | 109 | if ($keepIndex) { 110 | $argumentTokens[$i] = $tokens[$i]; 111 | } else { 112 | $argumentTokens[] = $tokens[$i]; 113 | } 114 | } 115 | 116 | return $argumentTokens; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/CsFixer/Tokenizer/ImportsModifier.php: -------------------------------------------------------------------------------- 1 | tokens = $tokens; 33 | } 34 | 35 | /** 36 | * Add a class name import. classStart is used to find the best position inside the class' namespace 37 | * 38 | * @param int $classStart 39 | * @param string $className 40 | */ 41 | public function addImport(int $classStart, string $className) 42 | { 43 | $hasNamespace = true; 44 | 45 | $namespaceStart = $this->getNamespaceStart($classStart); 46 | if (null === $namespaceStart) { 47 | $namespaceStart = $this->getOpenTagStart($classStart); 48 | $hasNamespace = false; 49 | 50 | if (null === $namespaceStart) { 51 | return; 52 | } 53 | } 54 | 55 | $manipulator = new TokenInsertManipulator($this->tokens); 56 | $importTokens = $this->createImportSequence($className); 57 | 58 | $imports = $this->getImports($namespaceStart, $classStart); 59 | if (0 === count($imports)) { 60 | $leadingNewlines = $hasNamespace ? 2 : 1; 61 | $trailingNewlines = $hasNamespace ? 0 : 1; 62 | 63 | $manipulator->insertAtIndex($namespaceStart, $importTokens, $leadingNewlines, $trailingNewlines); 64 | 65 | return; 66 | } 67 | 68 | $newImportString = $this->stringifyTokenSequence($importTokens); 69 | 70 | $hasImport = false; 71 | $insertPosition = $namespaceStart; 72 | 73 | // try to find ordered insert position by comparing sequence string to existing imports 74 | foreach ($imports as $import) { 75 | $importString = $this->stringifyTokenSequence($import['import']); 76 | 77 | // simple check if class was already imported. this will fail for group imports or other 78 | // more sophicsticated notations, but handling all cases is overkill 79 | if (false !== strpos($importString, $className)) { 80 | $hasImport = true; 81 | break; 82 | } 83 | 84 | $cmp = strcmp($newImportString, $importString); 85 | 86 | if ($cmp >= 0) { 87 | $insertPosition = $import['end'] + 1; 88 | } elseif ($cmp < 0) { 89 | break; 90 | } 91 | } 92 | 93 | if (!$hasImport) { 94 | $manipulator->insertAtIndex($insertPosition, $importTokens, 1, 0); 95 | } 96 | } 97 | 98 | /** 99 | * Generate token use sequence for a class name 100 | * 101 | * @param string $className 102 | * 103 | * @return Token[] 104 | */ 105 | private function createImportSequence(string $className): array 106 | { 107 | $tokens = Tokens::fromCode('= 0; $i--) { 129 | if ($this->tokens[$i]->isGivenKind(T_NAMESPACE)) { 130 | $nextTokenIndex = $this->tokens->getNextTokenOfKind($i, [';', '{']); 131 | if (null !== $nextTokenIndex) { 132 | $namespaceStart = $nextTokenIndex + 1; 133 | } 134 | 135 | break; 136 | } 137 | } 138 | 139 | return $namespaceStart; 140 | } 141 | 142 | /** 143 | * PHP open tag is used as fallback start when no namespace declaration was found 144 | * 145 | * @param int $classStart 146 | * 147 | * @return int|null 148 | */ 149 | private function getOpenTagStart(int $classStart) 150 | { 151 | $openTagStart = null; 152 | for ($i = $classStart; $i >= 0; $i--) { 153 | if ($this->tokens[$i]->isGivenKind(T_OPEN_TAG)) { 154 | $openTagStart = $i + 1; 155 | break; 156 | } 157 | } 158 | 159 | return $openTagStart; 160 | } 161 | 162 | /** 163 | * Loads all import statements between namespace/php block start and class definition 164 | * 165 | * @param int $namespaceStart 166 | * @param int $classStart 167 | * 168 | * @return array 169 | * 170 | * @internal param Tokens $tokens 171 | */ 172 | private function getImports(int $namespaceStart, int $classStart): array 173 | { 174 | $imports = []; 175 | for ($index = $namespaceStart; $index <= $classStart; ++$index) { 176 | $token = $this->tokens[$index]; 177 | 178 | if ($token->isGivenKind(T_USE)) { 179 | $importEnd = $this->tokens->getNextTokenOfKind($index, [';']); 180 | 181 | $import = []; 182 | for ($i = $index; $i <= $importEnd; $i++) { 183 | $import[] = $this->tokens[$i]; 184 | } 185 | 186 | $imports[] = [ 187 | 'start' => $index, 188 | 'end' => $importEnd, 189 | 'import' => $import, 190 | ]; 191 | } 192 | } 193 | 194 | return $imports; 195 | } 196 | 197 | /** 198 | * Converts token sequence to string which will be used to determine sort order 199 | * 200 | * @param Token[] $tokens 201 | * 202 | * @return string 203 | */ 204 | private function stringifyTokenSequence(array $tokens): string 205 | { 206 | $string = ''; 207 | foreach ($tokens as $token) { 208 | if ($token instanceof Token) { 209 | $string .= $token->getContent(); 210 | } 211 | } 212 | 213 | return trim($string); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/CsFixer/Tokenizer/TokenInsertManipulator.php: -------------------------------------------------------------------------------- 1 | tokens = $tokens; 33 | } 34 | 35 | /** 36 | * Inserts sequence at given position and handles adding new lines before and after inserting. 37 | * 38 | * The difficulty is that we can't just add a \n token before after the sequence, but we need to edit an existing 39 | * whitespace token if it is the previous/next one as consequent whitespace tokens lead to errors. 40 | * 41 | * @param int $index 42 | * @param array $tokens 43 | * @param int $leadingNewlines 44 | * @param int $trailingNewlines 45 | */ 46 | public function insertAtIndex(int $index, array $tokens, int $leadingNewlines = 0, int $trailingNewlines = 0) 47 | { 48 | $this->tokens->insertAt($index, $tokens); 49 | 50 | if ($leadingNewlines > 0) { 51 | $leadingContent = str_repeat("\n", $leadingNewlines); 52 | 53 | $previousToken = $this->tokens[$index - 1]; 54 | 55 | if ($previousToken->isWhitespace()) { 56 | $this->tokens->offsetSet( 57 | $index - 1, 58 | new Token([$previousToken->getId(), $previousToken->getContent() . $leadingContent]) 59 | ); 60 | } else { 61 | $this->tokens->insertAt($index, new Token([T_WHITESPACE, $leadingContent])); 62 | } 63 | } 64 | 65 | if ($trailingNewlines > 0) { 66 | $trailingContent = str_repeat("\n", $trailingNewlines); 67 | 68 | $endIndex = $index + count($tokens); 69 | $nextToken = $this->tokens[$endIndex + 1]; 70 | 71 | if ($nextToken->isWhitespace()) { 72 | $this->tokens->offsetSet( 73 | $endIndex + 1, 74 | new Token([$nextToken->getId(), $trailingContent . $nextToken->getContent()]) 75 | ); 76 | } else { 77 | $this->tokens->insertAt($endIndex + 1, new Token([T_WHITESPACE, $trailingContent])); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/CsFixer/Util/FixerResolver.php: -------------------------------------------------------------------------------- 1 | setLogger($logger); 40 | } 41 | 42 | $fixers[] = $fixer; 43 | } 44 | 45 | return $fixers; 46 | } 47 | 48 | /** 49 | * @param string|null $subNamespace 50 | * 51 | * @return array 52 | */ 53 | public static function getCustomFixerClasses(string $subNamespace = null): array 54 | { 55 | static $customFixers = null; 56 | 57 | if (null === $customFixers) { 58 | $customFixers = []; 59 | } 60 | 61 | $key = $subNamespace; 62 | if (null === $key) { 63 | $key = '_all'; 64 | } 65 | 66 | if (!isset($customFixers[$key])) { 67 | $customFixers[$key] = []; 68 | 69 | $dir = __DIR__ . '/../Fixer'; 70 | $baseNamespace = 'Pimcore\\CsFixer\Fixer\\'; 71 | 72 | if (null !== $subNamespace) { 73 | $dir = $dir . '/' . $subNamespace; 74 | $baseNamespace .= str_replace('/', '\\', $subNamespace) . '\\'; 75 | } 76 | 77 | /** @var SplFileInfo $file */ 78 | foreach (Finder::create()->files()->in($dir) as $file) { 79 | $relativeNamespace = $file->getRelativePath(); 80 | $fixerClass = $baseNamespace . ($relativeNamespace ? $relativeNamespace . '\\' : '') . $file->getBasename('.php'); 81 | 82 | if ('Fixer' === substr($fixerClass, -5)) { 83 | $reflector = new \ReflectionClass($fixerClass); 84 | if (!$reflector->isInstantiable()) { 85 | continue; 86 | } 87 | 88 | $customFixers[$key][] = $fixerClass; 89 | } 90 | } 91 | } 92 | 93 | return $customFixers[$key]; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/CsFixer/AbstractFixerTestCase.php: -------------------------------------------------------------------------------- 1 | registerCustomFixers(FixerResolver::getCustomFixers($logger)); 41 | 42 | return $factory; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/Controller/AbstractControllerFixerTestCase.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 21 | } 22 | 23 | public function provideFixCases() 24 | { 25 | $emptyActionInput = <<<'EOF' 26 | doTest($expected, $input); 21 | } 22 | 23 | public function provideFixCases() 24 | { 25 | $simpleControllerInput = <<<'EOF' 26 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | $insideActionInput = <<<'EOF' 23 | getParam('foo'); 34 | $bar = $this->getParam('bar', 'default value'); 35 | $baz = $this->getParam('baz', $anotherValue); 36 | $inga = $this->getParam('inga', $this->getAnotherValue()); 37 | } 38 | 39 | private function getAnotherValue() 40 | { 41 | return 'bazinga'; 42 | } 43 | } 44 | EOF; 45 | 46 | $insideActionExpected = <<<'EOF' 47 | get('foo'); 58 | $bar = $request->get('bar', 'default value'); 59 | $baz = $request->get('baz', $anotherValue); 60 | $inga = $request->get('inga', $this->getAnotherValue()); 61 | } 62 | 63 | private function getAnotherValue() 64 | { 65 | return 'bazinga'; 66 | } 67 | } 68 | EOF; 69 | 70 | $alreadyMigratedIgnored = $insideActionExpected; 71 | 72 | $insideOtherMethodIgnored = <<<'EOF' 73 | getParam('foo'); 82 | } 83 | 84 | private function getAnotherValue() 85 | { 86 | $bar = $this->getParam('bar'); 87 | } 88 | } 89 | EOF; 90 | 91 | $insideInitMethodIgnored = <<<'EOF' 92 | getParam('foo'); 101 | } 102 | } 103 | EOF; 104 | 105 | $outsideClassScopeIgnored = <<<'EOF' 106 | getParam('foo'); 109 | EOF; 110 | 111 | return [ 112 | [ 113 | $insideActionExpected, 114 | $insideActionInput 115 | ], 116 | [ 117 | $alreadyMigratedIgnored, 118 | null 119 | ], 120 | [ 121 | $insideOtherMethodIgnored, 122 | null 123 | ], 124 | [ 125 | $insideInitMethodIgnored, 126 | null 127 | ], 128 | [ 129 | $outsideClassScopeIgnored, 130 | null 131 | ] 132 | ]; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/Controller/ControllerNamespaceFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 21 | } 22 | 23 | public function provideFixCases() 24 | { 25 | $simpleControllerInput = <<<'EOF' 26 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'slots()->output(\'_content\'); ?>', 25 | 'layout()->content; ?>', 26 | ], 27 | [ 28 | 'slots()->output(\'_content\'); $this->slots()->output(\'_content\'); ?>', 29 | 'layout()->content; $this->layout()->content; ?>', 30 | ], 31 | [ 32 | 'slots()->output(\'_content\') ?>', 33 | 'layout()->content ?>', 34 | ], 35 | [ 36 | 'slots()->output(\'_content\') ?>', 37 | 'layout()->content ?>', 38 | ], 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/NavigationFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'navigation(); ?>', 25 | 'pimcoreNavigation(); ?>', 26 | ], 27 | [ 28 | 'navigation()->menu()->setUseTranslator(false)->renderPartial($mainNav, \'Navigation/partials/sidebar.html.php\'); ?>', 29 | 'pimcoreNavigation()->menu()->setUseTranslator(false)->renderPartial($mainNav, \'Navigation/partials/sidebar.html.php\'); ?>', 30 | ], 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/NavigationRenderPartialHelperFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'navigation()->menu()->setUseTranslator(false)->renderPartial($mainNav, \'Navigation/partials/sidebar.html.php\'); ?>', 25 | 'navigation()->menu()->setUseTranslator(false)->renderPartial($mainNav, \'/navigation/partials/sidebar.php\'); ?>', 26 | ], 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/NavigationSetPartialHelperFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'navigation()->menu()->setUseTranslator(false)->setPartial(\'Navigation/partials/sidebar.html.php\'); ?>', 25 | 'navigation()->menu()->setUseTranslator(false)->setPartial(\'/navigation/partials/sidebar.php\'); ?>', 26 | ], 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/PartialHelperFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'render(\'Includes/galleryRow.html.php\'); ?>', 25 | 'partial(\'/includes/gallery-row.php\'); ?>', 26 | ], 27 | [ 28 | 'render(\'Includes/galleryRow.html.php\'); ?>', 29 | 'partial(\'/includes/gallery-row.php\'); ?>', 30 | ], 31 | // make sure a echo is inserted if missing 32 | [ 33 | 'render(\'Includes/galleryRow.html.php\'); ?>', 34 | 'partial(\'/includes/gallery-row.php\'); ?>', 35 | ], 36 | // make sure echo is also inserted when not directly after open tag 37 | [ 38 | 'render(\'Includes/galleryRow.html.php\'); ?>', 39 | 'partial(\'/includes/gallery-row.php\'); ?>', 40 | ], 41 | // make sure other parameters are kept 42 | [ 43 | 'render(\'Includes/galleryRow.html.php\', [1, 2, 3], true, \'foo\'); ?>', 44 | 'partial(\'/includes/gallery-row.php\', [1, 2, 3], true, \'foo\'); ?>', 45 | ] 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/SetLayoutFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'extend(\'layout.html.php\'); ?>', 25 | 'layout()->setLayout(\'layout\'); ?>', 26 | ], 27 | [ 28 | 'extend(\'layout.html.php\'); ?>', 29 | 'layout()->setLayout(\'layout\', true); ?>', 30 | ], 31 | [ 32 | 'extend(\'layout.html.php\'); ?>', 33 | 'layout()->setLayout(\'layout\', false); ?>', 34 | ], 35 | [ 36 | 'extend(\'lay\' . \'out\' . \'.html.php\'); ?>', 37 | 'layout()->setLayout(\'lay\' . \'out\'); ?>', 38 | ], 39 | [ 40 | 'extend(\'lay\' . \'out\' . \'.html.php\'); ?>', 41 | 'layout()->setLayout(\'lay\' . \'out\'); ?>', 42 | ], 43 | [ 44 | // test nested call, even if the call makes no sense, but the fix should catch it properly 45 | 'extend($this->extend(\'layout.html.php\') . \'.html.php\'); ?>', 46 | 'layout()->setLayout($this->layout()->setLayout(\'layout\')); ?>', 47 | ], 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/TemplateHelperFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | return [ 23 | [ 24 | 'template(\'Includes/galleryRow.html.php\'); ?>', 25 | 'template(\'/includes/gallery-row.php\'); ?>', 26 | ], 27 | [ 28 | 'template(\'Includes/galleryRow.html.php\'); ?>', 29 | 'template(\'/includes/gallery-row.php\'); ?>', 30 | ], 31 | // make sure a echo is inserted if missing 32 | [ 33 | 'template(\'Includes/galleryRow.html.php\'); ?>', 34 | 'template(\'/includes/gallery-row.php\'); ?>', 35 | ], 36 | // make sure echo is also inserted when not directly after open tag 37 | [ 38 | 'template(\'Includes/galleryRow.html.php\'); ?>', 39 | 'template(\'/includes/gallery-row.php\'); ?>', 40 | ], 41 | // make sure other parameters are kept 42 | [ 43 | 'template(\'Includes/galleryRow.html.php\', [1, 2, 3], true, \'foo\'); ?>', 44 | 'template(\'/includes/gallery-row.php\', [1, 2, 3], true, \'foo\'); ?>', 45 | ] 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/CsFixer/Fixer/View/TypehintHeaderFixerTest.php: -------------------------------------------------------------------------------- 1 | doTest($expected, $input); 18 | } 19 | 20 | public function provideFixCases() 21 | { 22 | $normalViewInput = <<<'EOF' 23 | template("/includes/content-headline.php"); ?> 24 | 25 | areablock("content"); ?> 26 | EOF; 27 | 28 | $normalViewExpected = <<<'EOF' 29 | 36 | template("/includes/content-headline.php"); ?> 37 | 38 | areablock("content"); ?> 39 | EOF; 40 | 41 | $onlyPhpInput = <<<'EOF' 42 | 55 | 76 | FOO 77 | BAR 78 | BAZ 79 | EOF; 80 | 81 | return [ 82 | [ 83 | $normalViewExpected, 84 | $normalViewInput 85 | ], 86 | [ 87 | $onlyPhpExpected, 88 | $onlyPhpInput 89 | ], 90 | [ 91 | $noPhpExpected, 92 | $noPhpInput 93 | ], 94 | // test a block is not added twice 95 | [$normalViewExpected, null], 96 | [$onlyPhpExpected, null], 97 | [$noPhpExpected,null], 98 | ]; 99 | } 100 | } 101 | --------------------------------------------------------------------------------