├── .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 | [](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%1$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() -> = $this->template()
136 | if ($prevToken->isGivenKind(T_OPEN_TAG)) {
137 | $tokens->offsetSet($prev, new Token([T_OPEN_TAG_WITH_ECHO, '=']));
138 | $tokens->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('= $this->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 | '= $this->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 | '= $this->render(\'Includes/galleryRow.html.php\'); ?>',
29 | '= $this->partial(\'/includes/gallery-row.php\'); ?>',
30 | ],
31 | // make sure a echo is inserted if missing
32 | [
33 | '= $this->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 | '= $this->render(\'Includes/galleryRow.html.php\', [1, 2, 3], true, \'foo\'); ?>',
44 | '= $this->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 | '= $this->template(\'Includes/galleryRow.html.php\'); ?>',
29 | '= $this->template(\'/includes/gallery-row.php\'); ?>',
30 | ],
31 | // make sure a echo is inserted if missing
32 | [
33 | '= $this->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 | '= $this->template(\'Includes/galleryRow.html.php\', [1, 2, 3], true, \'foo\'); ?>',
44 | '= $this->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 | = $this->areablock("content"); ?>
26 | EOF;
27 |
28 | $normalViewExpected = <<<'EOF'
29 |
36 | template("/includes/content-headline.php"); ?>
37 |
38 | = $this->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 |
--------------------------------------------------------------------------------