diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..4a27d2a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 120
+
+[{docker-compose.yml,docker-compose.override.yml}]
+indent_size = 2
+
+# markdown uses two trailing spaces for explicit line breaks
+[*.md]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/.github/workflows/test-application.yaml b/.github/workflows/test-application.yaml
new file mode 100644
index 0000000..d8ee319
--- /dev/null
+++ b/.github/workflows/test-application.yaml
@@ -0,0 +1,89 @@
+name: Test application
+
+on:
+ pull_request:
+ push:
+ branches:
+ - '[0-9]+.x'
+ - '[0-9]+.[0-9]+'
+
+jobs:
+ test:
+ name: 'PHP ${{ matrix.php-version }} (${{ matrix.dependency-versions }})'
+ runs-on: ubuntu-latest
+
+ env:
+ APP_ENV: test
+ DATABASE_URL: mysql://root:root@127.0.0.1:3306/su_content_test?serverVersion=5.7.32
+ DATABASE_CHARSET: utf8mb4
+ DATABASE_COLLATE: utf8mb4_unicode_ci
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - php-version: '8.1'
+ dependency-versions: 'lowest'
+ env:
+ SYMFONY_DEPRECATIONS_HELPER: weak
+
+ - php-version: '8.2'
+ dependency-versions: 'highest'
+ env:
+ SYMFONY_DEPRECATIONS_HELPER: weak
+
+ - php-version: '8.3'
+ dependency-versions: 'highest'
+ env:
+ SYMFONY_DEPRECATIONS_HELPER: weak
+
+ services:
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306:3306
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
+
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@v2
+
+ - name: Install and configure PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: ctype, iconv, mysql
+ tools: 'composer:v2'
+
+ - name: Install composer dependencies
+ uses: ramsey/composer-install@v2
+ with:
+ dependency-versions: ${{ matrix.dependency-versions }}
+ composer-options: ${{ matrix.composer-options }}
+
+ - name: Execute test cases
+ run: composer test
+
+ lint:
+ name: "PHP Lint"
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@v2
+
+ - name: Install and configure PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.1
+ extensions: ctype, iconv, mysql
+
+ - name: Install composer dependencies
+ uses: ramsey/composer-install@v2
+ with:
+ dependency-versions: highest
+
+ - name: Lint Code
+ run: composer lint
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bb19384
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,65 @@
+# composer
+/composer.phar
+composer.lock
+/vendor
+
+.php-cs-fixer.cache
+.eslintcache
+.env.local
+
+# phpunit
+.phpunit
+phpunit.xml
+.phpunit.result.cache
+
+# IDEs
+/.idea
+*.iml
+*~
+.web-server-pid
+
+# System files
+.DS_Store
+
+# Styleguide
+/styleguide
+
+# IDEs
+.idea
+*.iml
+
+# Jackrabbit
+jackrabbit-standalone*
+jackrabbit/*
+
+# Symfony CLI
+# https://symfony.com/doc/current/setup/symfony_server.html#different-php-settings-per-project
+/php.ini
+/.php-version
+
+###> symfony/framework-bundle ###
+/.env.local
+/.env.local.php
+/.env.*.local
+/public/bundles/
+/var/
+/vendor/
+/Tests/Application/var
+###< symfony/framework-bundle ###
+
+###> phpunit/phpunit ###
+/phpunit.xml
+###< phpunit/phpunit ###
+
+###> symfony/phpunit-bridge ###
+.phpunit
+/phpunit.xml
+###< symfony/phpunit-bridge ###
+
+###> symfony/web-server-bundle ###
+/.web-server-pid
+###< symfony/web-server-bundle ###
+
+###> qossmic/deptrac ###
+/.deptrac.cache
+###< qossmic/deptrac ###
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..649058a
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,50 @@
+exclude(['var/cache', 'tests/Resources/cache', 'node_modules'])
+ ->in(__DIR__);
+
+$config = new PhpCsFixer\Config();
+$config->setRiskyAllowed(true)
+ ->setRules([
+ '@Symfony' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'class_definition' => false,
+ 'concat_space' => ['spacing' => 'one'],
+ 'function_declaration' => ['closure_function_spacing' => 'none'],
+ 'header_comment' => ['header' => $header],
+ 'native_constant_invocation' => true,
+ 'native_function_casing' => true,
+ 'native_function_invocation' => ['include' => ['@internal']],
+ 'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false],
+ 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => true],
+ 'ordered_imports' => true,
+ 'phpdoc_align' => ['align' => 'left'],
+ 'phpdoc_types_order' => false,
+ 'single_line_throw' => false,
+ 'single_line_comment_spacing' => false,
+ 'phpdoc_to_comment' => [
+ 'ignored_tags' => ['todo', 'var'],
+ ],
+ 'phpdoc_separation' => [
+ 'groups' => [
+ ['Serializer\\*', 'VirtualProperty', 'Accessor', 'Type', 'Groups', 'Expose', 'Exclude', 'SerializedName', 'Inline', 'ExclusionPolicy'],
+ ],
+ ],
+ 'get_class_to_class_keyword' => false, // should be enabled as soon as support for php < 8 is dropped
+ 'nullable_type_declaration_for_default_null_value' => true,
+ 'no_null_property_initialization' => false,
+ 'fully_qualified_strict_types' => false,
+ ])
+ ->setFinder($finder);
+
+return $config;
\ No newline at end of file
diff --git a/README.md b/README.md
index 834d129..41bc2c7 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ We are committed to a fully transparent development process and **highly appreci
In case you have questions, we are happy to welcome you in our official [Slack channel](https://sulu.io/services-and-support).
If you found a bug or miss a specific feature, feel free to **file a new issue** with a respective title and description
-on the the [sulu/SuluHeadlessBundle](https://github.com/sulu/SuluHeadlessBundle) repository.
+on the [sulu/SuluPHPCRMigrationBundle](https://github.com/sulu/SuluPHPCRMigrationBundle) repository.
## 📘 License
diff --git a/Resources/config/command.xml b/Resources/config/command.xml
new file mode 100644
index 0000000..039df9a
--- /dev/null
+++ b/Resources/config/command.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/config/session.xml b/Resources/config/session.xml
new file mode 100644
index 0000000..0888f23
--- /dev/null
+++ b/Resources/config/session.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ %sulu_phpcr_migration.configuration%
+
+
+
+
+
diff --git a/Tests/Application/.env b/Tests/Application/.env
new file mode 100644
index 0000000..afd9352
--- /dev/null
+++ b/Tests/Application/.env
@@ -0,0 +1 @@
+APP_ENV=test
diff --git a/Tests/Application/config/bootstrap.php b/Tests/Application/config/bootstrap.php
new file mode 100644
index 0000000..36801a1
--- /dev/null
+++ b/Tests/Application/config/bootstrap.php
@@ -0,0 +1,39 @@
+=1.2)
+if (\is_array($env = @include \dirname(__DIR__) . '/.env.local.php')) {
+ $_SERVER += $env;
+ $_ENV += $env;
+} elseif (!\class_exists(Dotenv::class)) {
+ throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
+} else {
+ $path = \dirname(__DIR__) . '/.env';
+ $dotenv = new Dotenv();
+ $dotenv->loadEnv($path);
+}
+
+$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
+$_SERVER['APP_DEBUG'] ??= $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
+$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || \filter_var($_SERVER['APP_DEBUG'], \FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
diff --git a/Tests/Functional/.gitkeep b/Tests/Functional/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/Tests/Unit/.gitkeep b/Tests/Unit/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/Tests/test-bootstrap.php b/Tests/test-bootstrap.php
new file mode 100644
index 0000000..42cbea5
--- /dev/null
+++ b/Tests/test-bootstrap.php
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ./Tests/Unit
+
+
+
+ ./Tests/Functional
+
+
+
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..60374f6
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,33 @@
+paths([__DIR__ . '/src', __DIR__ . '/Tests']);
+
+ $rectorConfig->phpstanConfigs([
+ __DIR__ . '/phpstan.neon',
+ ]);
+
+ // basic rules
+ $rectorConfig->importNames();
+ $rectorConfig->importShortClasses(false);
+
+ $rectorConfig->sets([
+ SetList::CODE_QUALITY,
+ LevelSetList::UP_TO_PHP_81,
+ ]);
+};
diff --git a/src/Application/Session/SessionManager.php b/src/Application/Session/SessionManager.php
new file mode 100644
index 0000000..7ee6a1a
--- /dev/null
+++ b/src/Application/Session/SessionManager.php
@@ -0,0 +1,73 @@
+workspace = $configuration['workspace']['default'];
+ $this->workspaceLive = $configuration['workspace']['live'];
+ }
+
+ public function getDefaultSession(): SessionInterface
+ {
+ return $this->getSession($this->workspace);
+ }
+
+ public function getLiveSession(): SessionInterface
+ {
+ return $this->getSession($this->workspaceLive);
+ }
+
+ private function getSession(string $workspace): SessionInterface
+ {
+ $factory = $this->connection instanceof Connection ? new RepositoryFactoryDoctrineDBAL() : new RepositoryFactoryJackrabbit();
+ $repository = $factory->getRepository(\array_filter([
+ 'jackalope.doctrine_dbal_connection' => $this->connection,
+ 'jackalope.jackrabbit_uri' => $this->configuration['connection']['url'] ?? null,
+ ]));
+
+ $credentials = new SimpleCredentials(
+ $this->configuration['connection']['user'] ?? 'dummy',
+ $this->configuration['connection']['password'] ?? 'dummy'
+ );
+
+ return $repository->login($credentials, $workspace);
+ }
+}
diff --git a/src/SuluPhpcrMigrationBundle.php b/src/SuluPhpcrMigrationBundle.php
new file mode 100644
index 0000000..b20ff18
--- /dev/null
+++ b/src/SuluPhpcrMigrationBundle.php
@@ -0,0 +1,128 @@
+load('session.xml');
+ $loader->load('command.xml');
+ }
+
+ public function configure(DefinitionConfigurator $definition): void
+ {
+ /** @var ArrayNodeDefinition $rootNode */
+ $rootNode = $definition->rootNode();
+ $rootNode
+ ->children()
+ ->scalarNode('DSN')->isRequired()->end()
+ ->end();
+ }
+
+ public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
+ {
+ /** @var array{'DSN': string}[] $config */
+ $config = $builder->getExtensionConfig('sulu_phpcr_migration');
+
+ $dsn = $config[0]['DSN'];
+ $builder->setParameter('sulu_phpcr_migration.dsn', $dsn);
+
+ $configuration = $this->getConnectionConfiguration($dsn);
+ $builder->setParameter('sulu_phpcr_migration.configuration', $configuration);
+
+ if ('dbal' === $configuration['connection']['type'] && $name = ($configuration['connection']['name'] ?? null)) {
+ $builder->setAlias('sulu_phpcr_migration.connection', \sprintf('doctrine.dbal.%s_connection', $name));
+
+ return;
+ }
+ }
+
+ /**
+ * @return array{
+ * connection: array{
+ * type: string,
+ * name?: string,
+ * url?: string,
+ * user?: string|null,
+ * password?: string|null
+ * },
+ * workspace: array{
+ * default: string,
+ * live: string
+ * }
+ * }
+ */
+ private function getConnectionConfiguration(string $dsn): array
+ {
+ /** @var array{
+ * scheme: string,
+ * host?: string,
+ * port?: string,
+ * path?: string,
+ * query: string,
+ * user?: string,
+ * pass?: string
+ * } $parts
+ */
+ $parts = \parse_url($dsn);
+ \parse_str($parts['query'], $query);
+
+ /** @var string|null $workspace */
+ $workspace = $query['workspace'] ?? '';
+ unset($query['workspace']);
+
+ if (!$workspace) {
+ throw new \InvalidArgumentException('Workspace is missing in DSN');
+ }
+
+ $result = [
+ 'connection' => [
+ 'type' => $parts['scheme'],
+ ],
+ 'workspace' => [
+ 'default' => $workspace,
+ 'live' => $workspace . '_live',
+ ],
+ ];
+
+ if ('dbal' === $parts['scheme'] && ($host = $parts['host'] ?? null)) {
+ $result['connection']['name'] = $host;
+
+ return $result;
+ }
+
+ $result['connection']['url'] = \sprintf(
+ '%s:%s%s%s',
+ $parts['host'] ?? '',
+ $parts['port'] ?? '',
+ $parts['path'] ?? '',
+ $query ? '?' . \http_build_query($query) : '',
+ );
+ $result['connection']['user'] = $parts['user'] ?? null;
+ $result['connection']['password'] = $parts['pass'] ?? null;
+
+ return $result;
+ }
+}
diff --git a/src/UserInterface/Command/MigratePhpcrCommand.php b/src/UserInterface/Command/MigratePhpcrCommand.php
new file mode 100644
index 0000000..174355a
--- /dev/null
+++ b/src/UserInterface/Command/MigratePhpcrCommand.php
@@ -0,0 +1,35 @@
+sessionManager->getDefaultSession();
+ $liveSession = $this->sessionManager->getLiveSession();
+
+ return Command::SUCCESS;
+ }
+}