From 2d7aafabaa2355c790cddca69577fa417aa9ca84 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 8 Jul 2025 08:38:33 +0200 Subject: [PATCH 01/32] Improve Titlebuilder to add a root page if more then one space is migrated to one namespace --- composer.json | 2 +- composer.lock | 3626 ----------------- doc/config.sample.yaml | 4 +- src/Analyzer/ConfluenceAnalyzer.php | 8 +- src/Converter/ConfluenceConverter.php | 6 +- .../Utility/TitleBuilder/TitleBuilderTest.php | 79 +- 6 files changed, 77 insertions(+), 3648 deletions(-) delete mode 100644 composer.lock diff --git a/composer.json b/composer.json index 8896033..4faf661 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "psr/log": "~1.1", "hallowelt/mediawiki-lib-mediawikixml": "~1", "hallowelt/mediawiki-lib-commandline-tools": "~1", - "hallowelt/mediawiki-lib-migration": "~1", + "hallowelt/mediawiki-lib-migration": "~2", "hallowelt/mediawiki-lib-wikitext": "~1", "ext-dom": "*", "symfony/yaml": "~4" diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 0ca1120..0000000 --- a/composer.lock +++ /dev/null @@ -1,3626 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "f6a707bd30ae80daf962853907ad148c", - "packages": [ - { - "name": "hallowelt/mediawiki-lib-commandline-tools", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/hallowelt/mediawiki-lib-commandline-tools.git", - "reference": "8a284dd0010f45f2251c8326f93fc6863d1af0a4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hallowelt/mediawiki-lib-commandline-tools/zipball/8a284dd0010f45f2251c8326f93fc6863d1af0a4", - "reference": "8a284dd0010f45f2251c8326f93fc6863d1af0a4", - "shasum": "" - }, - "require-dev": { - "jakub-onderka/php-console-highlighter": "0.4.0", - "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "31.0.0", - "mediawiki/mediawiki-phan-config": "0.11.0", - "mediawiki/minus-x": "1.1.1", - "phpunit/phpunit": "^8" - }, - "type": "library", - "autoload": { - "psr-4": { - "HalloWelt\\MediaWiki\\Lib\\CommandLineTools\\": "src/", - "HalloWelt\\MediaWiki\\Lib\\CommandLineTools\\Tests\\": "tests/phpunit/" - } - }, - "scripts": { - "unittest": [ - "vendor/phpunit/phpunit/phpunit --configuration .phpunit.xml" - ], - "test": [ - "parallel-lint . --exclude vendor --exclude node_modules", - "minus-x check .", - "phpcs -sp" - ], - "fix": [ - "minus-x fix .", - "phpcbf" - ], - "lint": [ - "phan --no-progress-bar -m text | sed 's, ,:,'" - ] - }, - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Robert Vogel", - "email": "vogel@hallowelt.com" - } - ], - "description": "Class library for various command line applications for MediaWiki", - "support": { - "source": "https://github.com/hallowelt/mediawiki-lib-commandline-tools/tree/1.0.1", - "issues": "https://github.com/hallowelt/mediawiki-lib-commandline-tools/issues" - }, - "time": "2022-01-26T14:12:23+00:00" - }, - { - "name": "hallowelt/mediawiki-lib-mediawikixml", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/hallowelt/mediawiki-lib-mediawikixml.git", - "reference": "dfd11e6a5f74f1c12b8289cb0e315fc97c9098aa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hallowelt/mediawiki-lib-mediawikixml/zipball/dfd11e6a5f74f1c12b8289cb0e315fc97c9098aa", - "reference": "dfd11e6a5f74f1c12b8289cb0e315fc97c9098aa", - "shasum": "" - }, - "require-dev": { - "mediawiki/mediawiki-codesniffer": "31.0.0", - "mediawiki/minus-x": "1.1.0", - "php-parallel-lint/php-console-highlighter": "0.5.0", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpunit/phpunit": "^9" - }, - "type": "library", - "autoload": { - "psr-4": { - "HalloWelt\\MediaWiki\\Lib\\MediaWikiXML\\": "src/", - "HalloWelt\\MediaWiki\\Lib\\MediaWikiXML\\Tests\\": "tests/phpunit/" - } - }, - "scripts": { - "unittest": [ - "vendor/phpunit/phpunit/phpunit --configuration .phpunit.xml" - ], - "test": [ - "parallel-lint . --exclude vendor --exclude node_modules", - "minus-x check .", - "phpcs -sp" - ], - "fix": [ - "minus-x fix .", - "phpcbf" - ] - }, - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Robert Vogel", - "email": "vogel@hallowelt.com" - } - ], - "description": "Class library for dealing with MediaWiki XML", - "support": { - "source": "https://github.com/hallowelt/mediawiki-lib-mediawikixml/tree/1.0.4", - "issues": "https://github.com/hallowelt/mediawiki-lib-mediawikixml/issues" - }, - "time": "2022-01-14T12:34:09+00:00" - }, - { - "name": "hallowelt/mediawiki-lib-migration", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/hallowelt/mediawiki-lib-migration.git", - "reference": "96d31d9c36bc9b29905521c720b027ee10d6ac73" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hallowelt/mediawiki-lib-migration/zipball/96d31d9c36bc9b29905521c720b027ee10d6ac73", - "reference": "96d31d9c36bc9b29905521c720b027ee10d6ac73", - "shasum": "" - }, - "require": { - "symfony/console": "v3.4.9" - }, - "require-dev": { - "jakub-onderka/php-console-highlighter": "0.4.0", - "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "31.0.0", - "mediawiki/mediawiki-phan-config": "0.11.0", - "mediawiki/minus-x": "1.1.1", - "phpunit/phpunit": "^8" - }, - "type": "library", - "autoload": { - "psr-4": { - "HalloWelt\\MediaWiki\\Lib\\Migration\\": "src/", - "HalloWelt\\MediaWiki\\Lib\\Migration\\Tests\\": "tests/phpunit/" - } - }, - "scripts": { - "unittest": [ - "vendor/phpunit/phpunit/phpunit --configuration .phpunit.xml" - ], - "test": [ - "parallel-lint . --exclude vendor --exclude node_modules", - "minus-x check .", - "phpcs -sp" - ], - "fix": [ - "minus-x fix .", - "phpcbf" - ], - "lint": [ - "phan --no-progress-bar -m text | sed 's, ,:,'" - ] - }, - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Robert Vogel", - "email": "vogel@hallowelt.com" - } - ], - "description": "Class library for migration projects for MediaWiki", - "support": { - "source": "https://github.com/hallowelt/mediawiki-lib-migration/tree/1.0.4", - "issues": "https://github.com/hallowelt/mediawiki-lib-migration/issues" - }, - "time": "2022-04-22T13:33:16+00:00" - }, - { - "name": "hallowelt/mediawiki-lib-wikitext", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/hallowelt/mediawiki-lib-wikitext.git", - "reference": "d835c926d9982ffc8aad81d173edb23bee74891f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hallowelt/mediawiki-lib-wikitext/zipball/d835c926d9982ffc8aad81d173edb23bee74891f", - "reference": "d835c926d9982ffc8aad81d173edb23bee74891f", - "shasum": "" - }, - "require-dev": { - "jakub-onderka/php-console-highlighter": "0.4.0", - "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "31.0.0", - "mediawiki/mediawiki-phan-config": "0.11.0", - "mediawiki/minus-x": "1.1.1", - "phpunit/phpunit": "^8" - }, - "type": "library", - "autoload": { - "psr-4": { - "HalloWelt\\MediaWiki\\Lib\\WikiText\\": "src/", - "HalloWelt\\MediaWiki\\Lib\\WikiText\\Tests\\": "tests/phpunit/" - } - }, - "scripts": { - "unittest": [ - "vendor/phpunit/phpunit/phpunit --configuration .phpunit.xml" - ], - "test": [ - "parallel-lint . --exclude vendor --exclude node_modules", - "minus-x check .", - "phpcs -sp" - ], - "fix": [ - "minus-x fix .", - "phpcbf" - ], - "lint": [ - "phan --no-progress-bar -m text | sed 's, ,:,'" - ] - }, - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Robert Vogel", - "email": "vogel@hallowelt.com" - } - ], - "description": "Class library for dealing with MediaWiki WikiText", - "support": { - "source": "https://github.com/hallowelt/mediawiki-lib-wikitext/tree/1.0.1", - "issues": "https://github.com/hallowelt/mediawiki-lib-wikitext/issues" - }, - "time": "2022-01-26T14:06:37+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "symfony/console", - "version": "v3.4.9", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "5b1fdfa8eb93464bcc36c34da39cedffef822cdf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5b1fdfa8eb93464bcc36c34da39cedffef822cdf", - "reference": "5b1fdfa8eb93464bcc36c34da39cedffef822cdf", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.3|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.3|~4.0" - }, - "suggest": { - "psr/log-implementation": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/console/tree/3.4" - }, - "time": "2018-04-30T01:22:56+00:00" - }, - { - "name": "symfony/debug", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "1a692492190773c5310bc7877cb590c04c2f05be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", - "reference": "1a692492190773c5310bc7877cb590c04c2f05be", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "abandoned": "symfony/error-handler", - "time": "2022-07-28T16:29:46+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/yaml", - "version": "v4.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", - "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-02T15:47:23+00:00" - } - ], - "packages-dev": [ - { - "name": "composer/pcre", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - }, - "phpstan": { - "includes": [ - "extension.neon" - ] - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-11-12T16:29:46+00:00" - }, - { - "name": "composer/semver", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-04-01T19:23:25+00:00" - }, - { - "name": "composer/spdx-licenses", - "version": "1.5.8", - "source": { - "type": "git", - "url": "https://github.com/composer/spdx-licenses.git", - "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", - "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Spdx\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "SPDX licenses list and validation library.", - "keywords": [ - "license", - "spdx", - "validator" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.8" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-11-20T07:44:33+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" - }, - "time": "2024-01-30T19:34:25+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:15:36+00:00" - }, - { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "shasum": "" - }, - "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" - }, - { - "name": "mediawiki/mediawiki-codesniffer", - "version": "v41.0.0", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/mediawiki-tools-codesniffer.git", - "reference": "0ccdbc10ad819e86d7872175e1649037a3352000" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-codesniffer/zipball/0ccdbc10ad819e86d7872175e1649037a3352000", - "reference": "0ccdbc10ad819e86d7872175e1649037a3352000", - "shasum": "" - }, - "require": { - "composer/semver": "3.3.2", - "composer/spdx-licenses": "~1.5.2", - "ext-json": "*", - "ext-mbstring": "*", - "php": ">=7.4.0", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/polyfill-php80": "^1.26.0" - }, - "require-dev": { - "mediawiki/mediawiki-phan-config": "0.12.0", - "mediawiki/minus-x": "1.1.1", - "php-parallel-lint/php-console-highlighter": "1.0.0", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpunit/phpunit": "9.5.28" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "MediaWiki\\Sniffs\\": "MediaWiki/Sniffs/", - "MediaWiki\\Sniffs\\Tests\\": "MediaWiki/Tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "description": "MediaWiki CodeSniffer Standards", - "homepage": "https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP", - "keywords": [ - "codesniffer", - "mediawiki" - ], - "support": { - "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v41.0.0" - }, - "time": "2023-02-25T22:10:21+00:00" - }, - { - "name": "mediawiki/mediawiki-phan-config", - "version": "0.14.0", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/mediawiki-tools-phan.git", - "reference": "58c01ba9cea443c9d345dc1ba87ffe082a188633" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-phan/zipball/58c01ba9cea443c9d345dc1ba87ffe082a188633", - "reference": "58c01ba9cea443c9d345dc1ba87ffe082a188633", - "shasum": "" - }, - "require": { - "mediawiki/phan-taint-check-plugin": "6.0.0", - "phan/phan": "5.4.3", - "php": ">=7.4.0" - }, - "require-dev": { - "mediawiki/mediawiki-codesniffer": "41.0.0", - "mediawiki/minus-x": "1.1.1", - "ockcyp/covers-validator": "1.6.0", - "php-parallel-lint/php-console-highlighter": "1.0.0", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpunit/phpunit": "9.6.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "MediaWikiPhanConfig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "authors": [ - { - "name": "MediaWiki developers", - "email": "wikitech-l@lists.wikimedia.org" - } - ], - "description": "Standard MediaWiki phan configuration", - "homepage": "https://www.mediawiki.org/wiki/Continuous_integration/Phan", - "support": { - "source": "https://github.com/wikimedia/mediawiki-tools-phan/tree/0.14.0" - }, - "time": "2024-02-03T20:26:03+00:00" - }, - { - "name": "mediawiki/minus-x", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/mediawiki-tools-minus-x.git", - "reference": "74b1fce4acbe6be1f9b4a0775287e09e0e3f6af2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-minus-x/zipball/74b1fce4acbe6be1f9b4a0775287e09e0e3f6af2", - "reference": "74b1fce4acbe6be1f9b4a0775287e09e0e3f6af2", - "shasum": "" - }, - "require": { - "php": ">=7.2.9", - "symfony/console": "^3.3.5 || ^4 || ^5" - }, - "require-dev": { - "mediawiki/mediawiki-codesniffer": "34.0.0", - "php-parallel-lint/php-console-highlighter": "0.5.0", - "php-parallel-lint/php-parallel-lint": "1.2.0" - }, - "bin": [ - "bin/minus-x" - ], - "type": "library", - "autoload": { - "psr-4": { - "MediaWiki\\MinusX\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-or-later" - ], - "authors": [ - { - "name": "Kunal Mehta", - "email": "legoktm@member.fsf.org" - } - ], - "description": "Removes executable bit from files that shouldn't be executable", - "homepage": "https://www.mediawiki.org/wiki/MinusX", - "support": { - "source": "https://github.com/wikimedia/mediawiki-tools-minus-x/tree/1.1.1" - }, - "time": "2021-01-06T01:11:18+00:00" - }, - { - "name": "mediawiki/phan-taint-check-plugin", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/mediawiki-tools-phan-SecurityCheckPlugin.git", - "reference": "01fb7e924a2e9fa58e88eda61775feccee38aad6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-phan-SecurityCheckPlugin/zipball/01fb7e924a2e9fa58e88eda61775feccee38aad6", - "reference": "01fb7e924a2e9fa58e88eda61775feccee38aad6", - "shasum": "" - }, - "require": { - "ext-json": "*", - "phan/phan": "5.4.3", - "php": "^7.4.0 | ^8.0.0" - }, - "require-dev": { - "mediawiki/mediawiki-codesniffer": "41.0.0", - "mediawiki/minus-x": "1.1.1", - "php-parallel-lint/php-console-highlighter": "1.0.0", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpunit/phpunit": "9.6.16" - }, - "bin": [ - "scripts/seccheck" - ], - "type": "library", - "autoload": { - "psr-4": { - "SecurityCheckPlugin\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "authors": [ - { - "name": "Brian Wolff", - "email": "bawolff+wn@gmail.com" - }, - { - "name": "Daimona Eaytoy", - "email": "daimona.wiki@gmail.com" - } - ], - "description": "A Phan plugin to do security checking", - "keywords": [ - "analyzer", - "phan", - "php", - "security", - "static", - "taint" - ], - "support": { - "irc": "irc://irc.libera.chat/wikimedia-dev", - "issues": "https://phabricator.wikimedia.org/maniphest/task/edit/form/1/?projects=securitycheckplugin", - "source": "https://phabricator.wikimedia.org/diffusion/MTPS/", - "wiki": "https://www.mediawiki.org/wiki/SecurityCheckPlugin" - }, - "time": "2024-02-02T01:29:05+00:00" - }, - { - "name": "microsoft/tolerant-php-parser", - "version": "v0.1.2", - "source": { - "type": "git", - "url": "https://github.com/microsoft/tolerant-php-parser.git", - "reference": "3eccfd273323aaf69513e2f1c888393f5947804b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/3eccfd273323aaf69513e2f1c888393f5947804b", - "reference": "3eccfd273323aaf69513e2f1c888393f5947804b", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Microsoft\\PhpParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rob Lourens", - "email": "roblou@microsoft.com" - } - ], - "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios", - "support": { - "issues": "https://github.com/microsoft/tolerant-php-parser/issues", - "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.1.2" - }, - "time": "2022-10-05T17:30:19+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.12.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2024-11-08T17:47:46+00:00" - }, - { - "name": "netresearch/jsonmapper", - "version": "v4.5.0", - "source": { - "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", - "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Map nested JSON structures onto PHP classes", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" - }, - "time": "2024-09-08T10:13:13+00:00" - }, - { - "name": "phan/phan", - "version": "5.4.3", - "source": { - "type": "git", - "url": "https://github.com/phan/phan.git", - "reference": "86a7acd99c1239b8867b49feca2398851212e7fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phan/phan/zipball/86a7acd99c1239b8867b49feca2398851212e7fe", - "reference": "86a7acd99c1239b8867b49feca2398851212e7fe", - "shasum": "" - }, - "require": { - "composer/semver": "^1.4|^2.0|^3.0", - "composer/xdebug-handler": "^2.0|^3.0", - "ext-filter": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.0.4", - "microsoft/tolerant-php-parser": "0.1.2", - "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0", - "php": "^7.2.0|^8.0.0", - "sabre/event": "^5.1.3", - "symfony/console": "^3.2|^4.0|^5.0|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.11.0", - "symfony/polyfill-php80": "^1.20.0", - "tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.0" - }, - "suggest": { - "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.16+ is recommended.", - "ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8", - "ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable", - "ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8", - "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.", - "ext-var_representation": "Suggested for converting values to strings in issue messages" - }, - "bin": [ - "phan", - "phan_client", - "tocheckstyle" - ], - "type": "project", - "autoload": { - "psr-4": { - "Phan\\": "src/Phan" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tyson Andre" - }, - { - "name": "Rasmus Lerdorf" - }, - { - "name": "Andrew S. Morrison" - } - ], - "description": "A static analyzer for PHP", - "keywords": [ - "analyzer", - "php", - "static", - "static analysis" - ], - "support": { - "issues": "https://github.com/phan/phan/issues", - "source": "https://github.com/phan/phan/tree/5.4.3" - }, - "time": "2023-12-26T17:57:35+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "php-parallel-lint/php-console-color", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", - "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", - "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "replace": { - "jakub-onderka/php-console-color": "*" - }, - "require-dev": { - "php-parallel-lint/php-code-style": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.0", - "php-parallel-lint/php-var-dump-check": "0.*", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com" - } - ], - "description": "Simple library for creating colored console ouput.", - "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", - "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1" - }, - "time": "2021-12-25T06:49:29+00:00" - }, - { - "name": "php-parallel-lint/php-console-highlighter", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git", - "reference": "5b4803384d3303cf8e84141039ef56c8a123138d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/5b4803384d3303cf8e84141039ef56c8a123138d", - "reference": "5b4803384d3303cf8e84141039ef56c8a123138d", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.2", - "php-parallel-lint/php-console-color": "^1.0.1" - }, - "replace": { - "jakub-onderka/php-console-highlighter": "*" - }, - "require-dev": { - "php-parallel-lint/php-code-style": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.0", - "php-parallel-lint/php-var-dump-check": "0.*", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "acci@acci.cz", - "homepage": "http://www.acci.cz/" - } - ], - "description": "Highlight PHP code in terminal", - "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues", - "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/v1.0.0" - }, - "time": "2022-02-18T08:23:19+00:00" - }, - { - "name": "php-parallel-lint/php-parallel-lint", - "version": "v1.3.2", - "source": { - "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", - "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6483c9832e71973ed29cf71bd6b3f4fde438a9de", - "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=5.3.0" - }, - "replace": { - "grogy/php-parallel-lint": "*", - "jakub-onderka/php-parallel-lint": "*" - }, - "require-dev": { - "nette/tester": "^1.3 || ^2.0", - "php-parallel-lint/php-console-highlighter": "0.* || ^1.0", - "squizlabs/php_codesniffer": "^3.6" - }, - "suggest": { - "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" - }, - "bin": [ - "parallel-lint" - ], - "type": "library", - "autoload": { - "classmap": [ - "./src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "ahoj@jakubonderka.cz" - } - ], - "description": "This tool check syntax of PHP files about 20x faster than serial check.", - "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", - "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", - "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.3.2" - }, - "time": "2022-02-21T12:50:22+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.6.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c", - "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.1", - "ext-filter": "*", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.5 || ~1.6.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-webmozart-assert": "^1.2", - "phpunit/phpunit": "^9.5", - "psalm/phar": "^5.26" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.0" - }, - "time": "2024-11-12T11:25:25+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.3 || ^8.0", - "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18|^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" - }, - "time": "2024-11-09T15:12:26+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" - }, - "time": "2024-10-13T11:29:49+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "7.0.17", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", - "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": ">=7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.3 || ^4.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" - }, - "require-dev": { - "phpunit/phpunit": "^8.2.2" - }, - "suggest": { - "ext-xdebug": "^2.7.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:09:37+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "2.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "69deeb8664f611f156a924154985fbd4911eb36b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", - "reference": "69deeb8664f611f156a924154985fbd4911eb36b", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:39:50+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" - }, - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "2.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", - "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:42:41+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2020-08-04T08:28:15+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "8.5.41", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d843cb5bcf0bf9ae3484016444fe0c5b6ec7e4fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d843cb5bcf0bf9ae3484016444fe0c5b6ec7e4fa", - "reference": "d843cb5bcf0bf9ae3484016444fe0c5b6ec7e4fa", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.5.0", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=7.2", - "phpunit/php-code-coverage": "^7.0.17", - "phpunit/php-file-iterator": "^2.0.6", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.4", - "sebastian/comparator": "^3.0.5", - "sebastian/diff": "^3.0.6", - "sebastian/environment": "^4.2.5", - "sebastian/exporter": "^3.1.6", - "sebastian/global-state": "^3.0.5", - "sebastian/object-enumerator": "^3.0.5", - "sebastian/resource-operations": "^2.0.3", - "sebastian/type": "^1.1.5", - "sebastian/version": "^2.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", - "phpunit/php-invoker": "To allow enforcing time limits" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "8.5-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.41" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2024-12-05T13:44:26+00:00" - }, - { - "name": "sabre/event", - "version": "5.1.7", - "source": { - "type": "git", - "url": "https://github.com/sabre-io/event.git", - "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2", - "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.17.1||^3.63", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6" - }, - "type": "library", - "autoload": { - "files": [ - "lib/coroutine.php", - "lib/Loop/functions.php", - "lib/Promise/functions.php" - ], - "psr-4": { - "Sabre\\Event\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Evert Pot", - "email": "me@evertpot.com", - "homepage": "http://evertpot.com/", - "role": "Developer" - } - ], - "description": "sabre/event is a library for lightweight event-based programming", - "homepage": "http://sabre.io/event/", - "keywords": [ - "EventEmitter", - "async", - "coroutine", - "eventloop", - "events", - "hooks", - "plugin", - "promise", - "reactor", - "signal" - ], - "support": { - "forum": "https://groups.google.com/group/sabredav-discuss", - "issues": "https://github.com/sabre-io/event/issues", - "source": "https://github.com/fruux/sabre-event" - }, - "time": "2024-08-27T11:23:05+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", - "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:45:45+00:00" - }, - { - "name": "sebastian/comparator", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:31:48+00:00" - }, - { - "name": "sebastian/diff", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", - "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:16:36+00:00" - }, - { - "name": "sebastian/environment", - "version": "4.2.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "56932f6049a0482853056ffd617c91ffcc754205" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", - "reference": "56932f6049a0482853056ffd617c91ffcc754205", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:49:59+00:00" - }, - { - "name": "sebastian/exporter", - "version": "3.1.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1939bc8fd1d39adcfa88c5b35335910869214c56", - "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:21:38+00:00" - }, - { - "name": "sebastian/global-state", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/91c7c47047a971f02de57ed6f040087ef110c5d9", - "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^8.0" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:13:16+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "ac5b293dba925751b808e02923399fb44ff0d541" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", - "reference": "ac5b293dba925751b808e02923399fb44ff0d541", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:54:02+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", - "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:56:04+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", - "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T14:07:30+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", - "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T13:59:09+00:00" - }, - { - "name": "sebastian/type", - "version": "1.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", - "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-01T14:04:07+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" - }, - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.7.2", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - } - ], - "time": "2023-02-22T23:07:41+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:36:25+00:00" - }, - { - "name": "tysonandre/var_representation_polyfill", - "version": "0.1.3", - "source": { - "type": "git", - "url": "https://github.com/TysonAndre/var_representation_polyfill.git", - "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/e9116c2c352bb0835ca428b442dde7767c11ad32", - "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.2.0|^8.0.0" - }, - "provide": { - "ext-var_representation": "*" - }, - "require-dev": { - "phan/phan": "^5.4.1", - "phpunit/phpunit": "^8.5.0" - }, - "suggest": { - "ext-var_representation": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.1.3-dev" - } - }, - "autoload": { - "files": [ - "src/var_representation.php" - ], - "psr-4": { - "VarRepresentation\\": "src/VarRepresentation" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tyson Andre" - } - ], - "description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export", - "keywords": [ - "var_export", - "var_representation" - ], - "support": { - "issues": "https://github.com/TysonAndre/var_representation_polyfill/issues", - "source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.1.3" - }, - "time": "2022-08-31T12:59:22+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "ext-dom": "*" - }, - "platform-dev": [], - "platform-overrides": { - "php": "8.1", - "ext-ldap": "8.1", - "ext-intl": "8.1", - "ext-pdo_sqlite": "8.1", - "ext-mongodb": "1.14.0" - }, - "plugin-api-version": "2.6.0" -} diff --git a/doc/config.sample.yaml b/doc/config.sample.yaml index d8388cd..db0e6db 100644 --- a/doc/config.sample.yaml +++ b/doc/config.sample.yaml @@ -1,7 +1,9 @@ config: mainpage: My Main Page space-prefix: - ABC: MYNAMESPACE + ABC: "MY_NAMESPACE:ABC/" + DEF: "MY_NAMESPACE:DEf/" + GHI: "GHI_NAMESPACE:" categories: - My Category 1 - My Category 2 diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 4d82d7a..5c64c67 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -8,7 +8,7 @@ use HalloWelt\MediaWiki\Lib\Migration\DataBuckets; use HalloWelt\MediaWiki\Lib\Migration\InvalidTitleException; use HalloWelt\MediaWiki\Lib\Migration\IOutputAwareInterface; -use HalloWelt\MediaWiki\Lib\Migration\TitleBuilder as MigrationTitleBuilder; +use HalloWelt\MediaWiki\Lib\Migration\TitleBuilder as GenericTitleBuilder; use HalloWelt\MediaWiki\Lib\Migration\Workspace; use HalloWelt\MigrateConfluence\Utility\FilenameBuilder; use HalloWelt\MigrateConfluence\Utility\TitleBuilder; @@ -375,8 +375,8 @@ private function makePagenamesMap() { * "Detailed_planning" -> "Dokumentation/Detailed_planning" */ $this->pageConfluenceTitle = $this->helper->getPropertyValue( 'title', $pageNode ); - $migrationTitleBuilder = new MigrationTitleBuilder( [] ); - $this->pageConfluenceTitle = $migrationTitleBuilder + $genericTitleBuilder = new GenericTitleBuilder( [] ); + $this->pageConfluenceTitle = $genericTitleBuilder ->appendTitleSegment( $this->pageConfluenceTitle )->build(); // We need to preserve the spaceID, so we can properly resolve cross-space links // in the `convert` stage @@ -782,7 +782,7 @@ private function makeMWUserName( $userName ) { $newUsername = ucfirst( strtolower( $newUsername ) ); // A MW username must always be avalid page title - $titleBuilder = new MigrationTitleBuilder( [] ); + $titleBuilder = new GenericTitleBuilder( [] ); $titleBuilder->appendTitleSegment( $newUsername ); return $titleBuilder->build(); diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index fef3f83..27bfdf1 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -147,7 +147,7 @@ protected function doConvert( SplFileInfo $file ): string { } if ( isset( $this->config['config']['mainpage'] ) ) { - $this->mainpage = $this->advancedConfig['mainpage']; + $this->mainpage = $this->config['config']['mainpage']; } $bodyContentId = $this->getBodyContentIdFromFilename(); @@ -804,8 +804,8 @@ private function getCurrentPageTitle(): string { $prefix = $spaceIdPrefixMap[$this->currentSpace]; $currentPageTitle = $this->currentPageTitle; - if ( substr( $currentPageTitle, 0, strlen( "$prefix:" ) ) === "$prefix:" ) { - $currentPageTitle = str_replace( "$prefix:", '', $currentPageTitle ); + if ( substr( $currentPageTitle, 0, strlen( $prefix ) ) === $prefix ) { + $currentPageTitle = str_replace( $prefix, '', $currentPageTitle ); } return $currentPageTitle; diff --git a/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php b/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php index 43f3389..3d217f4 100644 --- a/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php +++ b/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php @@ -16,28 +16,38 @@ public function testBuildTitle() { $dom->load( __DIR__ . '/entities_test.xml' ); $helper = new XMLHelper( $dom ); - $spacePrefixToIdMap = [ - 32973 => 'TestNS', - 99999 => 'TestNS_NoMain_Page' - ]; - $spaceIdHomepages = [ 32973 => 32974, 99999 => -1 ]; - $this->useDefaultMainpage( $spacePrefixToIdMap, $spaceIdHomepages, $helper ); - $this->useCustomMainpage( $spacePrefixToIdMap, $spaceIdHomepages, $helper, 'CustomMainpage' ); + $spaceIdPrefixMap = [ + 32973 => 'TestNS:', + 32974 => 'TestNS:', + 99999 => 'TestNS_NoMain_Page:' + ]; + + $this->useDefaultMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); + $this->useCustomMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, 'CustomMainpage' ); + + $spaceIdPrefixMap = [ + 32973 => 'TestNS:32973/', + 32974 => 'TestNS:32974/', + 99999 => 'TestNS_NoMain_Page:' + ]; + + $this->useDefaultMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); + $this->useCustomMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, 'CustomMainpage' ); } /** - * @param array $spacePrefixToIdMap + * @param array $spaceIdPrefixMap * @param array $spaceIdHomepages * @param XMLHelper $helper * @return void */ - private function useDefaultMainpage( $spacePrefixToIdMap, $spaceIdHomepages, $helper ): void { - $titleBuilder = new TitleBuilder( $spacePrefixToIdMap, $spaceIdHomepages, $helper ); + private function useDefaultMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ): void { + $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); $actualTitles = $this->buildTitles( $titleBuilder, $helper ); $expectedTitles = [ @@ -52,14 +62,14 @@ private function useDefaultMainpage( $spacePrefixToIdMap, $spaceIdHomepages, $he } /** - * @param array $spacePrefixToIdMap + * @param array $spaceIdPrefixMap * @param array $spaceIdHomepages * @param XMLHelper $helper * @param string $customMainpage * @return void */ - private function useCustomMainpage( $spacePrefixToIdMap, $spaceIdHomepages, $helper, $customMainpage ): void { - $titleBuilder = new TitleBuilder( $spacePrefixToIdMap, $spaceIdHomepages, $helper, $customMainpage ); + private function useCustomMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ): void { + $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ); $actualTitles = $this->buildTitles( $titleBuilder, $helper ); $expectedTitles = [ @@ -73,6 +83,49 @@ private function useCustomMainpage( $spacePrefixToIdMap, $spaceIdHomepages, $hel $this->assertEquals( $expectedTitles, $actualTitles ); } + /** + * @param array $spaceIdPrefixMap + * @param array $spaceIdHomepages + * @param XMLHelper $helper + * @return void + */ + private function useDefaultMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ): void { + $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); + $actualTitles = $this->buildTitles( $titleBuilder, $helper ); + + $expectedTitles = [ + "TestNS:32973/Main_Page", + "TestNS:32973/Roadmap", + "TestNS:32973/Roadmap/Detailed_planning", + "TestNS_NoMain_Page:Dokumentation", + "TestNS_NoMain_Page:Dokumentation/Roadmap", + ]; + + $this->assertEquals( $expectedTitles, $actualTitles ); + } + + /** + * @param array $spaceIdPrefixMap + * @param array $spaceIdHomepages + * @param XMLHelper $helper + * @param string $customMainpage + * @return void + */ + private function useCustomMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ): void { + $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ); + $actualTitles = $this->buildTitles( $titleBuilder, $helper ); + + $expectedTitles = [ + "TestNS:32973/$customMainpage", + "TestNS:32973/Roadmap", + "TestNS:32973/Roadmap/Detailed_planning", + "TestNS_NoMain_Page:Dokumentation", + "TestNS_NoMain_Page:Dokumentation/Roadmap", + ]; + + $this->assertEquals( $expectedTitles, $actualTitles ); + } + /** * @param TitleBuilder $titleBuilder * @param XMLHelper $helper From a2ef75fc1b7bc9bfd6c0b417a5a73b07740a50b3 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 8 Jul 2025 13:41:41 +0200 Subject: [PATCH 02/32] Fix page link processor page key generation --- src/Analyzer/ConfluenceAnalyzer.php | 2 +- src/Converter/Processor/PageLink.php | 9 +++++++++ .../Converter/Processor/PageLinkTest.php | 17 ++++++++++------- tests/phpunit/data/pagelinktest-input.xml | 6 ++++++ tests/phpunit/data/pagelinktest-output.xml | 6 +++++- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 5c64c67..3e4a69d 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -219,7 +219,7 @@ private function makeSpacesMap() { if ( $this->hasAdvancedConfig && isset( $this->advancedConfig['space-prefix'][$spaceKey] ) ) { $customSpacePrefix = $this->advancedConfig['space-prefix'][$spaceKey]; } else { - $customSpacePrefix = $spaceKey; + $customSpacePrefix = "{$spaceKey}:"; } $this->customBuckets->addData( diff --git a/src/Converter/Processor/PageLink.php b/src/Converter/Processor/PageLink.php index a8adbf8..ebbef3f 100644 --- a/src/Converter/Processor/PageLink.php +++ b/src/Converter/Processor/PageLink.php @@ -4,6 +4,7 @@ use DOMElement; use DOMNode; +use HalloWelt\MediaWiki\Lib\Migration\TitleBuilder as GenericTitleBuilder; class PageLink extends LinkProcessorBase { @@ -73,6 +74,10 @@ private function ensureSpaceId( DOMNode $node ): int { * @return string */ private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ): string { + $genericTitleBuilder = new GenericTitleBuilder( [] ); + $rawPageTitle = $genericTitleBuilder + ->appendTitleSegment( $rawPageTitle )->build(); + $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); return "$spaceId---$rawPageTitle"; } @@ -82,6 +87,10 @@ private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ) * @return string */ private function generateConfluenceKey( int $spaceId, string $rawPageTitle ): string { + $genericTitleBuilder = new GenericTitleBuilder( [] ); + $rawPageTitle = $genericTitleBuilder + ->appendTitleSegment( $rawPageTitle )->build(); + $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); return "Confluence---$spaceId---$rawPageTitle"; } diff --git a/tests/phpunit/Converter/Processor/PageLinkTest.php b/tests/phpunit/Converter/Processor/PageLinkTest.php index ca43a94..afadb40 100644 --- a/tests/phpunit/Converter/Processor/PageLinkTest.php +++ b/tests/phpunit/Converter/Processor/PageLinkTest.php @@ -23,19 +23,22 @@ public function testPreprocess() { $currentSpaceId = 42; $currentRawPagename = 'SomePage'; $dataLookup = new ConversionDataLookup( + // space-id-to-prefix-map [ 0 => '', 42 => 'ABC', 23 => 'DEVOPS' ], + // pages-titles-map [ - '42---Page Title' => 'ABC:Page_Title', - '42---Page Title2' => 'ABC:Page_Title2', - '42---Page Title3' => 'ABC:Page_Title3', - '42---Page Title5' => 'ABC:Test/Page_Title5', - '23---Page Title3' => 'DEVOPS:Page_Title3', - '0---Page Title6' => 'Page_Title6', - '0---Page Title7' => 'Test/Page_Title7', + '42---Page_Title' => 'ABC:Page_Title', + '42---Page_Title2' => 'ABC:Page_Title2', + '42---Page_Title3' => 'ABC:Page_Title3', + '42---Page_Title5' => 'ABC:Test/Page_Title5', + '23---Page_Title3' => 'DEVOPS:Page_Title3', + '23---Page_Title3,_Test' => 'DEVOPS:Page_Title3/Test', + '0---Page_Title6' => 'Page_Title6', + '0---Page_Title7' => 'Test/Page_Title7', ], [], [], diff --git a/tests/phpunit/data/pagelinktest-input.xml b/tests/phpunit/data/pagelinktest-input.xml index 73e6444..1344d2f 100644 --- a/tests/phpunit/data/pagelinktest-input.xml +++ b/tests/phpunit/data/pagelinktest-input.xml @@ -29,6 +29,12 @@ +
+ + + +
+
diff --git a/tests/phpunit/data/pagelinktest-output.xml b/tests/phpunit/data/pagelinktest-output.xml index 0361743..543483e 100644 --- a/tests/phpunit/data/pagelinktest-output.xml +++ b/tests/phpunit/data/pagelinktest-output.xml @@ -15,8 +15,12 @@ [[DEVOPS:Page_Title3|Page Title3]]
+
+ [[DEVOPS:Page_Title3/Test|Test]] +
+
- [[Confluence----1---Page Title4|Confluence----1---Page Title4]][[Category:Broken_page_link]] + [[Confluence----1---Page_Title4|Confluence----1---Page Title4]][[Category:Broken_page_link]]
From 72c9537cc786c3e3a447ccac5b51801875b11351 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 8 Jul 2025 13:45:18 +0200 Subject: [PATCH 03/32] CC --- tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php b/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php index 3d217f4..b74e0d7 100644 --- a/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php +++ b/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php @@ -111,7 +111,9 @@ private function useDefaultMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHome * @param string $customMainpage * @return void */ - private function useCustomMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ): void { + private function useCustomMainpageWithRootPage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage + ): void { $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ); $actualTitles = $this->buildTitles( $titleBuilder, $helper ); From 92be9f468380e5c8787fd29cc887168dc05c4951 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 14 Jul 2025 08:38:26 +0200 Subject: [PATCH 04/32] Fix usage of jira macro processor --- src/Converter/ConfluenceConverter.php | 1 + src/Converter/Processor/StructuredMacroJira.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index 27bfdf1..1825b94 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -401,6 +401,7 @@ private function processMacro( $sender, $match, $dom, $xpath ) { 'toc', 'view-file', 'warning', + 'jira' ] ) ) { return; diff --git a/src/Converter/Processor/StructuredMacroJira.php b/src/Converter/Processor/StructuredMacroJira.php index c457a89..14cfcdc 100644 --- a/src/Converter/Processor/StructuredMacroJira.php +++ b/src/Converter/Processor/StructuredMacroJira.php @@ -23,7 +23,7 @@ protected function doProcessMacro( $node ): void { $wikitextTemplate->setRenderFormatted( false ); $node->parentNode->replaceChild( $node->ownerDocument->createTextNode( - (string)$wikitextTemplate + $wikitextTemplate->render() ), $node ); From d5262f3e3dddf8bde6f633b3c32ba3c27fb4131f Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 14 Jul 2025 09:52:13 +0200 Subject: [PATCH 05/32] Fix inclued macro converter --- .../Processor/StructuredMacroInclude.php | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/Converter/Processor/StructuredMacroInclude.php b/src/Converter/Processor/StructuredMacroInclude.php index 5e03a97..6935e28 100644 --- a/src/Converter/Processor/StructuredMacroInclude.php +++ b/src/Converter/Processor/StructuredMacroInclude.php @@ -2,6 +2,7 @@ namespace HalloWelt\MigrateConfluence\Converter\Processor; +use HalloWelt\MediaWiki\Lib\Migration\TitleBuilder as GenericTitleBuilder; use HalloWelt\MigrateConfluence\Utility\ConversionDataLookup; class StructuredMacroInclude extends StructuredMacroProcessorBase { @@ -73,29 +74,20 @@ private function setMediaWikiPageName(): void { return; } $targetPageName = $pageEl->getAttribute( 'ri:content-title' ); - $confluencePageKey = $this->currentSpaceId . '---' . $targetPageName; + $confluencePageKey = $this->generatePageConfluenceKey( $this->currentSpaceId, $targetPageName ); $this->mediaWikiPageName = $this->dataLookup->getTargetTitleFromConfluencePageKey( $confluencePageKey ); } /** - * - * @param DOMNode $macro - * @return array + * @param int $spaceId + * @param string $rawPageTitle + * @return string */ - private function getMacroParams( $macro ): array { - $params = []; - foreach ( $macro->childNodes as $childNode ) { - if ( $childNode->nodeName === 'ac:parameter' ) { - $paramName = $childNode->getAttribute( 'ac:name' ); - - if ( $paramName === '' ) { - continue; - } - - $params[$paramName] = $childNode->nodeValue; - } - } - - return $params; + private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ): string { + $genericTitleBuilder = new GenericTitleBuilder( [] ); + $rawPageTitle = $genericTitleBuilder + ->appendTitleSegment( $rawPageTitle )->build(); + $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); + return "$spaceId---$rawPageTitle"; } } From 7050661f91cf167fddc4d3db13a8626f19e3af61 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 14 Jul 2025 11:47:06 +0200 Subject: [PATCH 06/32] Refactor widget macro converter --- src/Converter/ConfluenceConverter.php | 30 +---- .../Postprocessor/RestorePStyleTag.php | 25 ++++ src/Converter/Processor/PreservePStyleTag.php | 55 ++++++++ src/Converter/Processor/Widget.php | 118 ++++++++++++++++++ .../Converter/Processor/WidgetTest.php | 32 +++++ tests/phpunit/data/widget-input.xml | 11 ++ tests/phpunit/data/widget-output.xml | 0 7 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 src/Converter/Postprocessor/RestorePStyleTag.php create mode 100644 src/Converter/Processor/PreservePStyleTag.php create mode 100644 src/Converter/Processor/Widget.php create mode 100644 tests/phpunit/Converter/Processor/WidgetTest.php create mode 100644 tests/phpunit/data/widget-input.xml create mode 100644 tests/phpunit/data/widget-output.xml diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index 1825b94..2a51899 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -52,6 +52,7 @@ use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroToc; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroViewFile; use HalloWelt\MigrateConfluence\Converter\Processor\UserLink; +use HalloWelt\MigrateConfluence\Converter\Processor\Widget; use HalloWelt\MigrateConfluence\Utility\ConversionDataLookup; use HalloWelt\MigrateConfluence\Utility\ConversionDataWriter; use SplFileInfo; @@ -266,7 +267,8 @@ private function runProcessors( $dom ) { new StructuredMacroViewFile( $this->dataLookup, $this->currentSpace, $currentPageTitle, $this->nsFileRepoCompat - ) + ), + new Widget() ]; /** @var IProcessor $processor */ @@ -401,7 +403,8 @@ private function processMacro( $sender, $match, $dom, $xpath ) { 'toc', 'view-file', 'warning', - 'jira' + 'jira', + 'widget' ] ) ) { return; @@ -415,8 +418,6 @@ private function processMacro( $sender, $match, $dom, $xpath ) { $this->processExcerptMacro( $sender, $match, $dom, $xpath, $replacement ); } elseif ( $sMacroName === 'viewdoc' || $sMacroName === 'viewxls' || $sMacroName === 'viewpdf' ) { $this->processViewXMacro( $sender, $match, $dom, $xpath, $replacement, $sMacroName ); - } elseif ( $sMacroName === 'widget' ) { - $this->processWidgetMacro( $sender, $match, $dom, $xpath, $replacement ); } else { // TODO: 'calendar', 'contributors', 'anchor', // 'navitabs', 'include', 'listlabels', 'content-report-table' @@ -644,27 +645,6 @@ private function processGliffyMacro( $sender, $match, $dom, $xpath, &$replacemen } } - /** - * @param ConfluenceConverter $sender - * @param DOMElement $match - * @param DOMDocument $dom - * @param DOMXPath $xpath - * @param string &$replacement - */ - private function processWidgetMacro( $sender, $match, $dom, $xpath, &$replacement ) { - $oParamEls = $xpath->query( './ac:parameter', $match ); - $params = [ - 'url' => '' - ]; - foreach ( $oParamEls as $oParamEl ) { - $params[$oParamEl->getAttribute( 'ac:name' )] = $oParamEl->nodeValue; - } - $oContainer = $dom->createElement( 'div', $params['url'] ); - $oContainer->setAttribute( 'class', "ac-widget" ); - $oContainer->setAttribute( 'data-params', json_encode( $params ) ); - $match->parentNode->insertBefore( $oContainer, $match ); - } - /** * * @param DOMDocument $dom diff --git a/src/Converter/Postprocessor/RestorePStyleTag.php b/src/Converter/Postprocessor/RestorePStyleTag.php new file mode 100644 index 0000000..a608fd0 --- /dev/null +++ b/src/Converter/Postprocessor/RestorePStyleTag.php @@ -0,0 +1,25 @@ +{$text}

"; + }, + $wikiText + ); + + return $newWikiText; + } +} diff --git a/src/Converter/Processor/PreservePStyleTag.php b/src/Converter/Processor/PreservePStyleTag.php new file mode 100644 index 0000000..2099cb6 --- /dev/null +++ b/src/Converter/Processor/PreservePStyleTag.php @@ -0,0 +1,55 @@ +getElementsByTagName( 'p' ); + + foreach ( $tags as $tag ) { + if ( $tag instanceof DOMElement === false ) { + continue; + } + + if ( !$tag->hasAttributes() ) { + continue; + } + + $attributes = []; + $attributeMap = $tag->attributes; + for ( $index = 0; $index < count( $attributeMap ); $index ++ ) { + $name = $attributeMap->item( $index )->nodeName; + $value = $attributeMap->item( $index )->nodeValue; + $attributes[$name] = "{$name}=\"{$value}\""; + } + + if ( count( $attributes ) > 1 || !isset( $attributes['style'] ) ) { + continue; + } + + $attributesString = implode( ' ', $attributes ); + + $openingTagReplacement = $tag->ownerDocument->createTextNode( + "#####PRESERVEPSTYLEOPEN $attributesString#####" + ); + + $closeingTagReplacement = $tag->ownerDocument->createTextNode( + "#####PRESERVEPSTYLECLOSE#####" + ); + + $tag->prepend( $openingTagReplacement ); + $tag->append( $closeingTagReplacement ); + } + + } +} diff --git a/src/Converter/Processor/Widget.php b/src/Converter/Processor/Widget.php new file mode 100644 index 0000000..6e60ef4 --- /dev/null +++ b/src/Converter/Processor/Widget.php @@ -0,0 +1,118 @@ +ownerDocument->createElement( 'div' ); + $macroReplacement->setAttribute( 'class', "ac-widget" ); + + $params = $this->macroParams( $node, $macroReplacement ); + + if ( isset( $params[ 'url' ] ) ) { + $macroReplacement->nodeValue = $params['url']; + } + + $node->parentNode->replaceChild( $macroReplacement, $node ); + } + + /** + * + * @param DOMNode $macro + * @param DOMElement $macroReplacement + * @return void + */ + private function macroParams( $macro, $macroReplacement ): array { + $params = []; + foreach ( $macro->childNodes as $childNode ) { + if ( $childNode->nodeName !== 'ac:parameter' ) { + continue; + } + $attrName = $this->getAttribute( $childNode, 'ac:name' ); + if ( !$attrName ) { + continue; + } + + $name = $attrName->nodeValue; + + $value = $this->getParamValue( $childNode, 'ri:url'); + + $params[$name] = $value; + } + + if ( !empty( $params ) ) { + $macroReplacement->setAttribute( 'data-params', json_encode( $params ) ); + } + + return $params; + } + + /** + * @param DOMNode $node + * @param string $name + * @return ?DOMAttr + */ + private function getAttribute( DOMNode $node, string $name ): ?DOMAttr { + $attributes = $node->attributes; + + if ( $attributes && $attributes->count() > 0 ) { + for ( $index = 0; $index < $attributes->count(); $index++ ) { + $attribute = $attributes->item( $index ); + + if ( $attribute->nodeName !== $name ) { + continue; + } + + return $attribute; + } + } + + return null; + } + + /** + * @param DOMNode $node + * @param string $name + * @return void + */ + private function getParamValue( DOMNode $node, string $name ) { + $value = ''; + $childNodes = $node->childNodes; + + if ( $childNodes->count() > 0 ) { + for( $index = 0; $index < $childNodes->count(); $index++ ) { + $child = $childNodes->item( $index ); + $attrName = $this->getAttribute( $child, 'ri:value' ); + if ( !$attrName ) { + continue; + } + + $value = $attrName->nodeValue; + } + } + + if ( $value === '' ) { + $value = $node->nodeValue; + } + + return $value; + } +} diff --git a/tests/phpunit/Converter/Processor/WidgetTest.php b/tests/phpunit/Converter/Processor/WidgetTest.php new file mode 100644 index 0000000..0586a1b --- /dev/null +++ b/tests/phpunit/Converter/Processor/WidgetTest.php @@ -0,0 +1,32 @@ +loadXML( $input ); + + $processor = new Widget(); + $processor->process( $dom ); + + $actualOutput = $dom->saveXML( $dom->documentElement ); + $expectedOutput = file_get_contents( "$dir/widget-output.xml" ); + + $this->assertEquals( $expectedOutput, $actualOutput ); + } +} diff --git a/tests/phpunit/data/widget-input.xml b/tests/phpunit/data/widget-input.xml new file mode 100644 index 0000000..b8a0a4b --- /dev/null +++ b/tests/phpunit/data/widget-input.xml @@ -0,0 +1,11 @@ + + + + + + + + + https://www.example.com/2 + + \ No newline at end of file diff --git a/tests/phpunit/data/widget-output.xml b/tests/phpunit/data/widget-output.xml new file mode 100644 index 0000000..e69de29 From 7dc53f8fa430650213ddead795283bf4d3dc4d31 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 14 Jul 2025 13:31:52 +0200 Subject: [PATCH 07/32] Update ConfluenceConverter --- src/Converter/ConfluenceConverter.php | 6 +++++- src/Converter/Processor/PageLink.php | 12 ++++-------- tests/phpunit/data/pagelinktest-input.xml | 5 ++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index 2a51899..b9e06d6 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -14,6 +14,7 @@ use HalloWelt\MigrateConfluence\Converter\Postprocessor\FixLineBreakInHeadings; use HalloWelt\MigrateConfluence\Converter\Postprocessor\NestedHeadings; use HalloWelt\MigrateConfluence\Converter\Postprocessor\RestoreCode; +use HalloWelt\MigrateConfluence\Converter\Postprocessor\RestorePStyleTag; use HalloWelt\MigrateConfluence\Converter\Postprocessor\RestoreStructuredMacroTasksReport; use HalloWelt\MigrateConfluence\Converter\Postprocessor\RestoreTimeTag; use HalloWelt\MigrateConfluence\Converter\Preprocessor\CDATAClosingFixer; @@ -34,6 +35,7 @@ use HalloWelt\MigrateConfluence\Converter\Processor\MacroAlign; use HalloWelt\MigrateConfluence\Converter\Processor\PageLink; use HalloWelt\MigrateConfluence\Converter\Processor\PreserveCode; +use HalloWelt\MigrateConfluence\Converter\Processor\PreservePStyleTag; use HalloWelt\MigrateConfluence\Converter\Processor\PreserveStructuredMacroTasksReport; use HalloWelt\MigrateConfluence\Converter\Processor\PreserveTimeTag; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroAttachments; @@ -268,7 +270,8 @@ private function runProcessors( $dom ) { $this->dataLookup, $this->currentSpace, $currentPageTitle, $this->nsFileRepoCompat ), - new Widget() + new Widget(), + new PreservePStyleTag() ]; /** @var IProcessor $processor */ @@ -283,6 +286,7 @@ private function runProcessors( $dom ) { */ private function runPostProcessors() { $postProcessors = [ + new RestorePStyleTag(), new RestoreTimeTag(), new FixLineBreakInHeadings(), new FixImagesWithExternalUrl(), diff --git a/src/Converter/Processor/PageLink.php b/src/Converter/Processor/PageLink.php index ebbef3f..a1fa9d7 100644 --- a/src/Converter/Processor/PageLink.php +++ b/src/Converter/Processor/PageLink.php @@ -75,10 +75,10 @@ private function ensureSpaceId( DOMNode $node ): int { */ private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ): string { $genericTitleBuilder = new GenericTitleBuilder( [] ); - $rawPageTitle = $genericTitleBuilder - ->appendTitleSegment( $rawPageTitle )->build(); - $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); - return "$spaceId---$rawPageTitle"; + $pageConfluenceTitle = $genericTitleBuilder + ->appendTitleSegment( $this->rawPageTitle )->build(); + $pageConfluenceTitle = str_replace( ' ', '_', $pageConfluenceTitle ); + return "$spaceId---$pageConfluenceTitle"; } /** @@ -87,10 +87,6 @@ private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ) * @return string */ private function generateConfluenceKey( int $spaceId, string $rawPageTitle ): string { - $genericTitleBuilder = new GenericTitleBuilder( [] ); - $rawPageTitle = $genericTitleBuilder - ->appendTitleSegment( $rawPageTitle )->build(); - $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); return "Confluence---$spaceId---$rawPageTitle"; } diff --git a/tests/phpunit/data/pagelinktest-input.xml b/tests/phpunit/data/pagelinktest-input.xml index 1344d2f..eb90fbd 100644 --- a/tests/phpunit/data/pagelinktest-input.xml +++ b/tests/phpunit/data/pagelinktest-input.xml @@ -25,13 +25,16 @@
- + +<<<<<<< Updated upstream
+======= +>>>>>>> Stashed changes
From c6c8e7e54d792cb4079d7bc0aef9d7ebc258f579 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 14 Jul 2025 13:36:31 +0200 Subject: [PATCH 08/32] Restore page link processor --- src/Converter/Processor/PageLink.php | 12 ++++++++---- tests/phpunit/data/pagelinktest-input.xml | 5 +---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Converter/Processor/PageLink.php b/src/Converter/Processor/PageLink.php index a1fa9d7..ebbef3f 100644 --- a/src/Converter/Processor/PageLink.php +++ b/src/Converter/Processor/PageLink.php @@ -75,10 +75,10 @@ private function ensureSpaceId( DOMNode $node ): int { */ private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ): string { $genericTitleBuilder = new GenericTitleBuilder( [] ); - $pageConfluenceTitle = $genericTitleBuilder - ->appendTitleSegment( $this->rawPageTitle )->build(); - $pageConfluenceTitle = str_replace( ' ', '_', $pageConfluenceTitle ); - return "$spaceId---$pageConfluenceTitle"; + $rawPageTitle = $genericTitleBuilder + ->appendTitleSegment( $rawPageTitle )->build(); + $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); + return "$spaceId---$rawPageTitle"; } /** @@ -87,6 +87,10 @@ private function generatePageConfluenceKey( int $spaceId, string $rawPageTitle ) * @return string */ private function generateConfluenceKey( int $spaceId, string $rawPageTitle ): string { + $genericTitleBuilder = new GenericTitleBuilder( [] ); + $rawPageTitle = $genericTitleBuilder + ->appendTitleSegment( $rawPageTitle )->build(); + $rawPageTitle = str_replace( ' ', '_', $rawPageTitle ); return "Confluence---$spaceId---$rawPageTitle"; } diff --git a/tests/phpunit/data/pagelinktest-input.xml b/tests/phpunit/data/pagelinktest-input.xml index eb90fbd..1344d2f 100644 --- a/tests/phpunit/data/pagelinktest-input.xml +++ b/tests/phpunit/data/pagelinktest-input.xml @@ -25,16 +25,13 @@
- -<<<<<<< Updated upstream +
-======= ->>>>>>> Stashed changes
From 10cffa2b59ec9f89f45f1931cc22b314cc2f2b61 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 15 Jul 2025 06:58:25 +0200 Subject: [PATCH 09/32] Improve filename --- src/Utility/FilenameBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utility/FilenameBuilder.php b/src/Utility/FilenameBuilder.php index 97b4f84..0228cb8 100644 --- a/src/Utility/FilenameBuilder.php +++ b/src/Utility/FilenameBuilder.php @@ -52,7 +52,7 @@ public function buildFilename( $attachmentNode, $assocTitle ) { if ( empty( $title ) ) { $title = $this->helper->getPropertyValue( 'title', $attachmentNode ); } - $this->builder->appendTitleSegment( $title ); + $this->builder->appendTitleSegment( "-{$title}" ); if ( !empty( $assocTitle ) ) { $assocTitle = str_replace( '/', '_', $assocTitle ); // Unset potential namespace prefix to avoid duplications From db315c32ed3f309e3a53b2f466edf5277fa4d94d Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 15 Jul 2025 07:09:31 +0200 Subject: [PATCH 10/32] CC --- src/Converter/Postprocessor/RestorePStyleTag.php | 2 +- src/Converter/Processor/PreservePStyleTag.php | 5 ++--- src/Converter/Processor/Widget.php | 6 +++--- tests/phpunit/Converter/Processor/WidgetTest.php | 2 -- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Converter/Postprocessor/RestorePStyleTag.php b/src/Converter/Postprocessor/RestorePStyleTag.php index a608fd0..4475f8c 100644 --- a/src/Converter/Postprocessor/RestorePStyleTag.php +++ b/src/Converter/Postprocessor/RestorePStyleTag.php @@ -12,7 +12,7 @@ class RestorePStyleTag implements IPostprocessor { public function postprocess( string $wikiText ): string { $newWikiText = preg_replace_callback( '/#####PRESERVEPSTYLEOPEN (.*?)#####(.*?)#####PRESERVEPSTYLECLOSE#####/si', - function( $matches ) { + static function ( $matches ) { $attributes = str_replace( """, "\"", $matches[1] ); $text = $matches[2]; return "

{$text}

"; diff --git a/src/Converter/Processor/PreservePStyleTag.php b/src/Converter/Processor/PreservePStyleTag.php index 2099cb6..30e23c9 100644 --- a/src/Converter/Processor/PreservePStyleTag.php +++ b/src/Converter/Processor/PreservePStyleTag.php @@ -10,7 +10,7 @@ class PreservePStyleTag implements IProcessor { /** * Pandoc removes p tags with style - * + * * @inheritDoc */ public function process( DOMDocument $dom ): void { @@ -27,7 +27,7 @@ public function process( DOMDocument $dom ): void { $attributes = []; $attributeMap = $tag->attributes; - for ( $index = 0; $index < count( $attributeMap ); $index ++ ) { + for ( $index = 0; $index < count( $attributeMap ); $index++ ) { $name = $attributeMap->item( $index )->nodeName; $value = $attributeMap->item( $index )->nodeValue; $attributes[$name] = "{$name}=\"{$value}\""; @@ -50,6 +50,5 @@ public function process( DOMDocument $dom ): void { $tag->prepend( $openingTagReplacement ); $tag->append( $closeingTagReplacement ); } - } } diff --git a/src/Converter/Processor/Widget.php b/src/Converter/Processor/Widget.php index 6e60ef4..ba8fb43 100644 --- a/src/Converter/Processor/Widget.php +++ b/src/Converter/Processor/Widget.php @@ -53,7 +53,7 @@ private function macroParams( $macro, $macroReplacement ): array { $name = $attrName->nodeValue; - $value = $this->getParamValue( $childNode, 'ri:url'); + $value = $this->getParamValue( $childNode, 'ri:url' ); $params[$name] = $value; } @@ -98,7 +98,7 @@ private function getParamValue( DOMNode $node, string $name ) { $childNodes = $node->childNodes; if ( $childNodes->count() > 0 ) { - for( $index = 0; $index < $childNodes->count(); $index++ ) { + for ( $index = 0; $index < $childNodes->count(); $index++ ) { $child = $childNodes->item( $index ); $attrName = $this->getAttribute( $child, 'ri:value' ); if ( !$attrName ) { @@ -108,7 +108,7 @@ private function getParamValue( DOMNode $node, string $name ) { $value = $attrName->nodeValue; } } - + if ( $value === '' ) { $value = $node->nodeValue; } diff --git a/tests/phpunit/Converter/Processor/WidgetTest.php b/tests/phpunit/Converter/Processor/WidgetTest.php index 0586a1b..166960b 100644 --- a/tests/phpunit/Converter/Processor/WidgetTest.php +++ b/tests/phpunit/Converter/Processor/WidgetTest.php @@ -3,9 +3,7 @@ namespace HalloWelt\MigrateConfluence\Tests\Converter\Processor; use DOMDocument; -use HalloWelt\MigrateConfluence\Converter\Processor\PageLink; use HalloWelt\MigrateConfluence\Converter\Processor\Widget; -use HalloWelt\MigrateConfluence\Utility\ConversionDataLookup; use PHPUnit\Framework\TestCase; class WidgetTest extends TestCase { From 4486dbe75587739af78f3371ab31bd753559aa9b Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 15 Jul 2025 07:18:35 +0200 Subject: [PATCH 11/32] Fix widget unittest --- tests/phpunit/data/widget-output.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/phpunit/data/widget-output.xml b/tests/phpunit/data/widget-output.xml index e69de29..f933fa6 100644 --- a/tests/phpunit/data/widget-output.xml +++ b/tests/phpunit/data/widget-output.xml @@ -0,0 +1,5 @@ + +
https://www.example.com/1
+ +
https://www.example.com/2
+
\ No newline at end of file From 4a9ea7b02b7f2ae3603f69f465a6689b75c31a7b Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Fri, 18 Jul 2025 06:30:30 +0200 Subject: [PATCH 12/32] Add gliffy processor and template --- src/Analyzer/ConfluenceAnalyzer.php | 18 +- src/Composer/_defaultpages/Template/Gliffy | 4 + src/Converter/ConfluenceConverter.php | 34 ++-- .../Processor/StructuredMacroGliffy.php | 168 ++++++++++++++++++ .../Processor/StructuredMacroGliffyTest.php | 80 +++++++++ .../data/structuredmacro-gliffy-input.xml | 15 ++ .../data/structuredmacro-gliffy-output-1.xml | 12 ++ .../data/structuredmacro-gliffy-output-2.xml | 12 ++ .../data/structuredmacro-gliffy-output-3.xml | 12 ++ 9 files changed, 319 insertions(+), 36 deletions(-) create mode 100644 src/Composer/_defaultpages/Template/Gliffy create mode 100644 src/Converter/Processor/StructuredMacroGliffy.php create mode 100644 tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php create mode 100644 tests/phpunit/data/structuredmacro-gliffy-input.xml create mode 100644 tests/phpunit/data/structuredmacro-gliffy-output-1.xml create mode 100644 tests/phpunit/data/structuredmacro-gliffy-output-2.xml create mode 100644 tests/phpunit/data/structuredmacro-gliffy-output-3.xml diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 3e4a69d..4a24137 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -576,18 +576,12 @@ private function makeAttachmentTargetFilename( $attachment, $containerTitle ) { $file = new SplFileInfo( $targetName ); if ( $this->hasNoExplicitFileExtension( $file ) ) { $contentType = $this->helper->getPropertyValue( 'contentType', $attachment ); - if ( $contentType === 'application/gliffy+json' ) { - $targetName .= '.json'; - } elseif ( $contentType === 'application/gliffy+xml' ) { - $targetName .= '.xml'; - } else { - $this->logger->debug( - "Could not find file extension for $fileName as " - . "{$attachment->getNodePath()}; " - . "contentType: $contentType" - ); - $targetName .= '.unknown'; - } + $this->logger->debug( + "Could not find file extension for $fileName as " + . "{$attachment->getNodePath()}; " + . "contentType: $contentType" + ); + $targetName .= '.unknown'; } $fileKey = "{$this->pageConfluenceTitle}---$fileName"; diff --git a/src/Composer/_defaultpages/Template/Gliffy b/src/Composer/_defaultpages/Template/Gliffy new file mode 100644 index 0000000..b24a044 --- /dev/null +++ b/src/Composer/_defaultpages/Template/Gliffy @@ -0,0 +1,4 @@ +{{#tag:drawio +| +|filename={{{name}}} +}} \ No newline at end of file diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index b9e06d6..5f72f67 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -44,6 +44,7 @@ use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroContenByLabel; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroDrawio; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroExcerptInclude; +use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroGliffy; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroInclude; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroJira; use HalloWelt\MigrateConfluence\Converter\Processor\StructuredMacroNoFormat; @@ -121,7 +122,8 @@ public function __construct( $config, Workspace $workspace ) { 'attachment-orig-filename-target-filename-map', 'files', 'userkey-to-username-map', - 'space-description-id-to-body-id-map' + 'space-description-id-to-body-id-map', + 'gliffy-map' ] ); $this->dataBuckets->loadFromWorkspace( $this->workspace ); @@ -259,6 +261,10 @@ private function runProcessors( $dom ) { $this->dataLookup, $this->conversionDataWriter, $this->currentSpace, $currentPageTitle, $this->nsFileRepoCompat ), + new StructuredMacroGliffy( + $this->dataLookup, $this->conversionDataWriter, $this->currentSpace, + $currentPageTitle, $this->dataBuckets, $this->nsFileRepoCompat + ), new StructuredMacroContenByLabel( $this->currentPageTitle ), new StructuredMacroAttachments(), new ExpandMacro(), @@ -408,15 +414,14 @@ private function processMacro( $sender, $match, $dom, $xpath ) { 'view-file', 'warning', 'jira', - 'widget' + 'widget', + 'gliffy' ] ) ) { return; } - if ( $sMacroName === 'gliffy' ) { - $this->processGliffyMacro( $sender, $match, $dom, $xpath, $replacement ); - } elseif ( $sMacroName === 'localtabgroup' || $sMacroName === 'localtab' ) { + if ( $sMacroName === 'localtabgroup' || $sMacroName === 'localtab' ) { $this->processLocalTabMacro( $sender, $match, $dom, $xpath, $replacement, $sMacroName ); } elseif ( $sMacroName === 'excerpt' ) { $this->processExcerptMacro( $sender, $match, $dom, $xpath, $replacement ); @@ -630,25 +635,6 @@ private function processViewXMacro( $sender, $match, $dom, $xpath, &$replacement } } - /** - * @param ConfluenceConverter $sender - * @param DOMElement $match - * @param DOMDocument $dom - * @param DOMXPath $xpath - * @param string &$replacement - */ - private function processGliffyMacro( $sender, $match, $dom, $xpath, &$replacement ) { - $oNameParam = $xpath->query( './ac:parameter[@ac:name="name"]', $match )->item( 0 ); - $currentPageTitle = $this->getCurrentPageTitle(); - if ( !empty( $oNameParam->nodeValue ) ) { - $imageProcessor = new Image( - $this->dataLookup, $this->currentSpace, $currentPageTitle, $this->nsFileRepoCompat - ); - $replacementNode = $imageProcessor->makeImageLink( $dom, [ "{$oNameParam->nodeValue}.png" ] ); - $replacement = $replacementNode->ownerDocument->saveXML( $replacementNode ); - } - } - /** * * @param DOMDocument $dom diff --git a/src/Converter/Processor/StructuredMacroGliffy.php b/src/Converter/Processor/StructuredMacroGliffy.php new file mode 100644 index 0000000..fe0a1d2 --- /dev/null +++ b/src/Converter/Processor/StructuredMacroGliffy.php @@ -0,0 +1,168 @@ +dataLookup = $dataLookup; + $this->conversionDataWriter = $conversionDataWriter; + $this->currentSpaceId = $currentSpaceId; + $this->rawPageTitle = $rawPageTitle; + $this->dataBuckets = $dataBuckets; + $this->nsFileRepoCompat = $nsFileRepoCompat; + } + + /** + * + * @return string + */ + protected function getMacroName(): string { + return 'gliffy'; + } + + /** + * @param DOMNode $node + * @return void + */ + protected function doProcessMacro( $node ): void { + $params = $this->getMacroParams( $node ); + + if ( isset( $params['name'] ) ) { + $paramsString = $this->makeParamsString( $params ); + + // Gliffy will be used as Drawio image + $node->parentNode->replaceChild( + $node->ownerDocument->createTextNode( "{{Gliffy$paramsString}}" ), + $node + ); + } + } + + /** + * @param array $params + * @return string + */ + private function makeParamsString( array $params ): string { + $paramsString = ''; + + if ( isset( $params['name'] ) && $params['name'] !== '' ) { + $name = $params['name']; + if ( strtolower( substr( $name, strlen( $name ) - 4 ) ) !== '.png' ) { + $name .= '.png'; + } + $filename = $this->getFilename( $name ); + if ( $filename !== '' ) { + $params['name'] = $filename; + $this->dataBuckets->addData( 'gliffy-map', $params['name'], $filename, true, true ); + } + } else { + return ''; + } + + if ( isset( $params['macroId'] ) ) { + unset( $params['macroId'] ); + } + + foreach ( $params as $key => $value ) { + $paramsString .= "|$key=$value\n"; + } + + return $paramsString; + } + + /** + * + * @param DOMNode $macro + * @return array + */ + private function getMacroParams( $macro ): array { + $params = []; + foreach ( $macro->childNodes as $childNode ) { + if ( $childNode->nodeName === 'ac:parameter' ) { + $paramName = $childNode->getAttribute( 'ac:name' ); + + if ( $paramName === '' ) { + continue; + } + + $params[$paramName] = $childNode->nodeValue; + } + } + + return $params; + } + + /** + * @param string $diagramName + * @return string + */ + private function getFilename( string $diagramName ): string { + $spaceId = $this->currentSpaceId; + $rawPageTitle = basename( $this->rawPageTitle ); + + + // TODO get correct key !! + + + + + $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $diagramName ); + $filename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); + if ( $this->nsFileRepoCompat ) { + $filenameParts = explode( '_', $filename ); + + if ( count( $filenameParts ) > 2 ) { + $filename = $filenameParts[0]; + array_shift( $filenameParts ); + $filename .= ':' . implode( '_', $filenameParts ); + } + } + + return $filename; + } +} diff --git a/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php b/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php new file mode 100644 index 0000000..df9e557 --- /dev/null +++ b/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php @@ -0,0 +1,80 @@ +dataLookup = new ConversionDataLookup( + [ + 42 => 'ABC', + 23 => 'DEVOPS' + ], + [ + '42---SomeLinkedPage' => 'ABC:SomeLinkedPage', + ], + [ + '0---SomePage---gliffy-file-1.png' => 'SomePage_gliffy-file-1.png', + '0---SomePage---gliffy-file-1' => 'SomePage_gliffy-file-1.unknown', + '0---SomePage---gliffy-file-2.png' => 'SomePage_gliffy-file-2.png', + '0---SomePage---gliffy-file-2' => 'SomePage_gliffy-file-2.unknown', + '23---SomePage---gliffy-file-1.png' => 'DEVOPS_SomePage_gliffy-file-1.png', + '23---SomePage---gliffy-file-1' => 'DEVOPS_SomePage_gliffy-file-1.unknown', + '23---SomePage---gliffy-file-2.png' => 'DEVOPS_SomePage_gliffy-file-2.png', + '23---SomePage---gliffy-file-2' => 'DEVOPS_SomePage_gliffy-file-2.unknown', + ], + [], + [], + [] + ); + $this->conversionDataWriter = new ConversionDataWriter( [] ); + + /** SpaceId GENERAL */ + #$this->doTest( 0, false, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-1.xml' ); + #$this->doTest( 0, true, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-1.xml' ); + + /** Random SpaceId */ + $this->doTest( 23, false, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-2.xml' ); + #$this->doTest( 23, true, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-3.xml' ); + } + + private function doTest( $spaceId, $nsFileRepoCompat, $input, $output ) { + $input = file_get_contents( dirname( __DIR__, 2 ) . "/data/$input" ); + $expectedOutput = file_get_contents( dirname( __DIR__, 2 ) . "/data/$output" ); + + $dom = new DOMDocument(); + $dom->loadXML( $input ); + + $dataBuckets = new DataBuckets( [ 'gliffy-map' ] ); + $processor = new StructuredMacroGliffy( $this->dataLookup, $this->conversionDataWriter, + $spaceId, 'SomePage', $dataBuckets, $nsFileRepoCompat ); + $processor->process( $dom ); + $actualOutput = $dom->saveXML(); + + $this->assertEquals( $expectedOutput, $actualOutput ); + + // TODO: Add unittest for bucket + //var_dump( $dataBuckets->getBucketData( 'gliffy-map' ) ); + } +} diff --git a/tests/phpunit/data/structuredmacro-gliffy-input.xml b/tests/phpunit/data/structuredmacro-gliffy-input.xml new file mode 100644 index 0000000..dc64673 --- /dev/null +++ b/tests/phpunit/data/structuredmacro-gliffy-input.xml @@ -0,0 +1,15 @@ + + + 12345 + gliffy-file-1.png + gliffy-file-1.png + 5 + + + + 12345 + gliffy-file-2 + gliffy-file-2 + 5 + + \ No newline at end of file diff --git a/tests/phpunit/data/structuredmacro-gliffy-output-1.xml b/tests/phpunit/data/structuredmacro-gliffy-output-1.xml new file mode 100644 index 0000000..ca69f53 --- /dev/null +++ b/tests/phpunit/data/structuredmacro-gliffy-output-1.xml @@ -0,0 +1,12 @@ + + + {{Gliffy|displayName=gliffy-file-1.png +|name=SomePage_gliffy-file-1.png +|pagePin=5 +}} + + {{Gliffy|displayName=gliffy-file-2 +|name=SomePage_gliffy-file-2.png +|pagePin=5 +}} + diff --git a/tests/phpunit/data/structuredmacro-gliffy-output-2.xml b/tests/phpunit/data/structuredmacro-gliffy-output-2.xml new file mode 100644 index 0000000..66fadd8 --- /dev/null +++ b/tests/phpunit/data/structuredmacro-gliffy-output-2.xml @@ -0,0 +1,12 @@ + + + {{Gliffy|displayName=gliffy-file-1.png +|name=DEVOPS_SomePage_gliffy-file-1.png +|pagePin=5 +}} + + {{Gliffy|displayName=gliffy-file-2 +|name=DEVOPS_SomePage_gliffy-file-2.png +|pagePin=5 +}} + diff --git a/tests/phpunit/data/structuredmacro-gliffy-output-3.xml b/tests/phpunit/data/structuredmacro-gliffy-output-3.xml new file mode 100644 index 0000000..24e8c47 --- /dev/null +++ b/tests/phpunit/data/structuredmacro-gliffy-output-3.xml @@ -0,0 +1,12 @@ + + + {{Gliffy|displayName=gliffy-file-1.png +|name=DEVOPS:SomePage_gliffy-file-1.png +|pagePin=5 +}} + + {{Gliffy|displayName=gliffy-file-2 +|name=DEVOPS:SomePage_gliffy-file-2.png +|pagePin=5 +}} + From 759ac84a8b08fe6454fa81e1bcb060d813c2912b Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Fri, 18 Jul 2025 06:46:43 +0200 Subject: [PATCH 13/32] Add custom bucket for gliffy --- src/Composer/ConfluenceComposer.php | 5 +++ src/Converter/ConfluenceConverter.php | 12 ++++++- .../Processor/StructuredMacroGliffy.php | 32 +++++++------------ .../Processor/StructuredMacroGliffyTest.php | 9 ++---- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index 38c6a28..3bbb4b5 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -19,6 +19,11 @@ class ConfluenceComposer extends ComposerBase implements IOutputAwareInterface { */ private $dataBuckets; + /** + * @var DataBuckets + */ + private $customBuckets; + /** * @var Output */ diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index 5f72f67..408ad6f 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -69,6 +69,9 @@ class ConfluenceConverter extends PandocHTML implements IOutputAwareInterface { /** @var DataBuckets */ private $dataBuckets = null; + /** @var DataBuckets */ + private $customBuckets = null; + /** @var ConversionDataLookup */ private $dataLookup = null; @@ -127,6 +130,11 @@ public function __construct( $config, Workspace $workspace ) { ] ); $this->dataBuckets->loadFromWorkspace( $this->workspace ); + + $this->customBuckets = new DataBuckets( [ + 'title-uploads', + 'title-uploads-fail' + ] ); } /** @@ -209,6 +217,8 @@ protected function doConvert( SplFileInfo $file ): string { $this->postProcessLinks(); $this->postprocessWikiText(); + $this->customBuckets->saveToWorkspace( $this->workspace ); + return $this->wikiText; } @@ -263,7 +273,7 @@ private function runProcessors( $dom ) { ), new StructuredMacroGliffy( $this->dataLookup, $this->conversionDataWriter, $this->currentSpace, - $currentPageTitle, $this->dataBuckets, $this->nsFileRepoCompat + $currentPageTitle, $this->customBuckets, $this->nsFileRepoCompat ), new StructuredMacroContenByLabel( $this->currentPageTitle ), new StructuredMacroAttachments(), diff --git a/src/Converter/Processor/StructuredMacroGliffy.php b/src/Converter/Processor/StructuredMacroGliffy.php index fe0a1d2..514c6f4 100644 --- a/src/Converter/Processor/StructuredMacroGliffy.php +++ b/src/Converter/Processor/StructuredMacroGliffy.php @@ -5,7 +5,6 @@ use HalloWelt\MediaWiki\Lib\Migration\DataBuckets; use HalloWelt\MigrateConfluence\Utility\ConversionDataLookup; use HalloWelt\MigrateConfluence\Utility\ConversionDataWriter; -use HalloWelt\MigrateConfluence\Utility\DrawIOFileHandler; class StructuredMacroGliffy extends StructuredMacroProcessorBase { @@ -42,13 +41,13 @@ class StructuredMacroGliffy extends StructuredMacroProcessorBase { /** * @param ConversionDataLookup $dataLookup * @param ConversionDataWriter $conversionDataWriter - * @param integer $currentSpaceId + * @param int $currentSpaceId * @param string $rawPageTitle - * @param DataBuckets $dataBuckets - * @param boolean $nsFileRepoCompat + * @param DataBuckets &$dataBuckets + * @param bool $nsFileRepoCompat */ public function __construct( ConversionDataLookup $dataLookup, ConversionDataWriter $conversionDataWriter, - int $currentSpaceId, string $rawPageTitle, DataBuckets &$dataBuckets, bool $nsFileRepoCompat = false ) { + int $currentSpaceId, string $rawPageTitle, DataBuckets &$dataBuckets, bool $nsFileRepoCompat = false ) { $this->dataLookup = $dataLookup; $this->conversionDataWriter = $conversionDataWriter; $this->currentSpaceId = $currentSpaceId; @@ -92,13 +91,16 @@ private function makeParamsString( array $params ): string { if ( isset( $params['name'] ) && $params['name'] !== '' ) { $name = $params['name']; - if ( strtolower( substr( $name, strlen( $name ) - 4 ) ) !== '.png' ) { + if ( strtolower( substr( $name, strlen( $name ) - 4 ) ) !== '.png' ) { $name .= '.png'; } - $filename = $this->getFilename( $name ); + $spaceId = $this->currentSpaceId; + $rawPageTitle = basename( $this->rawPageTitle ); + $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); + $filename = $this->getFilename( $confluenceFileKey ); if ( $filename !== '' ) { $params['name'] = $filename; - $this->dataBuckets->addData( 'gliffy-map', $params['name'], $filename, true, true ); + $this->dataBuckets->addData( 'gliffy-map', $confluenceFileKey, $filename, true, true ); } } else { return ''; @@ -138,20 +140,10 @@ private function getMacroParams( $macro ): array { } /** - * @param string $diagramName + * @param string $confluenceFileKey * @return string */ - private function getFilename( string $diagramName ): string { - $spaceId = $this->currentSpaceId; - $rawPageTitle = basename( $this->rawPageTitle ); - - - // TODO get correct key !! - - - - - $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $diagramName ); + private function getFilename( string $confluenceFileKey ): string { $filename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); if ( $this->nsFileRepoCompat ) { $filenameParts = explode( '_', $filename ); diff --git a/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php b/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php index df9e557..687117b 100644 --- a/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php +++ b/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php @@ -51,12 +51,12 @@ public function testPreprocess() { $this->conversionDataWriter = new ConversionDataWriter( [] ); /** SpaceId GENERAL */ - #$this->doTest( 0, false, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-1.xml' ); - #$this->doTest( 0, true, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-1.xml' ); + $this->doTest( 0, false, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-1.xml' ); + $this->doTest( 0, true, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-1.xml' ); /** Random SpaceId */ $this->doTest( 23, false, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-2.xml' ); - #$this->doTest( 23, true, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-3.xml' ); + $this->doTest( 23, true, 'structuredmacro-gliffy-input.xml', 'structuredmacro-gliffy-output-3.xml' ); } private function doTest( $spaceId, $nsFileRepoCompat, $input, $output ) { @@ -73,8 +73,5 @@ private function doTest( $spaceId, $nsFileRepoCompat, $input, $output ) { $actualOutput = $dom->saveXML(); $this->assertEquals( $expectedOutput, $actualOutput ); - - // TODO: Add unittest for bucket - //var_dump( $dataBuckets->getBucketData( 'gliffy-map' ) ); } } From 53afa1fbee47ed6f477d6a10a0573bd39f9a46d9 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Fri, 18 Jul 2025 07:12:32 +0200 Subject: [PATCH 14/32] Add fallback to PNG to gliffy --- .../Processor/StructuredMacroGliffy.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Converter/Processor/StructuredMacroGliffy.php b/src/Converter/Processor/StructuredMacroGliffy.php index 514c6f4..f768124 100644 --- a/src/Converter/Processor/StructuredMacroGliffy.php +++ b/src/Converter/Processor/StructuredMacroGliffy.php @@ -89,15 +89,27 @@ protected function doProcessMacro( $node ): void { private function makeParamsString( array $params ): string { $paramsString = ''; + $spaceId = $this->currentSpaceId; + $rawPageTitle = basename( $this->rawPageTitle ); + if ( isset( $params['name'] ) && $params['name'] !== '' ) { $name = $params['name']; - if ( strtolower( substr( $name, strlen( $name ) - 4 ) ) !== '.png' ) { + + $extension = strtolower( substr( $name, strlen( $name ) - 4 ) ); + if ( $extension !== '.png' && $extension !== '.svg' ) { $name .= '.png'; } - $spaceId = $this->currentSpaceId; - $rawPageTitle = basename( $this->rawPageTitle ); + $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); $filename = $this->getFilename( $confluenceFileKey ); + if ( $filename === '' ) { + // Fallback + $name = $params['name']; + $name .= '.PNG'; + $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); + $filename = $this->getFilename( $confluenceFileKey ); + } + if ( $filename !== '' ) { $params['name'] = $filename; $this->dataBuckets->addData( 'gliffy-map', $confluenceFileKey, $filename, true, true ); From a8569b8cc43b15af390561b4dc77824ec1380c8f Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Fri, 18 Jul 2025 08:52:28 +0200 Subject: [PATCH 15/32] Improve gliffy fallback --- .../Processor/StructuredMacroGliffy.php | 48 ++++++++++++------- .../Processor/StructuredMacroGliffyTest.php | 2 + .../data/structuredmacro-gliffy-input.xml | 7 +++ .../data/structuredmacro-gliffy-output-1.xml | 5 ++ .../data/structuredmacro-gliffy-output-2.xml | 5 ++ .../data/structuredmacro-gliffy-output-3.xml | 5 ++ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/Converter/Processor/StructuredMacroGliffy.php b/src/Converter/Processor/StructuredMacroGliffy.php index f768124..5ef1559 100644 --- a/src/Converter/Processor/StructuredMacroGliffy.php +++ b/src/Converter/Processor/StructuredMacroGliffy.php @@ -87,33 +87,34 @@ protected function doProcessMacro( $node ): void { * @return string */ private function makeParamsString( array $params ): string { - $paramsString = ''; - - $spaceId = $this->currentSpaceId; - $rawPageTitle = basename( $this->rawPageTitle ); - if ( isset( $params['name'] ) && $params['name'] !== '' ) { $name = $params['name']; - $extension = strtolower( substr( $name, strlen( $name ) - 4 ) ); - if ( $extension !== '.png' && $extension !== '.svg' ) { - $name .= '.png'; + $extension = substr( $name, strlen( $name ) - 4 ); + if ( strtolower( $extension ) !== '.png' && strtolower( $extension ) !== '.svg' ) { + $key = $this->getConfluenceKey( $name, '.png' ); + } else { + $key = $this->getConfluenceKey( $name ); } + $filename = $this->getFilename( $key ); - $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); - $filename = $this->getFilename( $confluenceFileKey ); if ( $filename === '' ) { - // Fallback - $name = $params['name']; - $name .= '.PNG'; - $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); - $filename = $this->getFilename( $confluenceFileKey ); + $fallbackExtensions = [ '.PNG', '.svg', '.png' ]; + foreach ( $fallbackExtensions as $ext ) { + $key = $this->getConfluenceKey( $name, $ext ); + $filename = $this->getFilename( $key ); + + if ( $filename === '' ) { + break; + } + } } if ( $filename !== '' ) { $params['name'] = $filename; - $this->dataBuckets->addData( 'gliffy-map', $confluenceFileKey, $filename, true, true ); } + + $this->dataBuckets->addData( 'gliffy-map', $key, $filename, true, true ); } else { return ''; } @@ -122,6 +123,7 @@ private function makeParamsString( array $params ): string { unset( $params['macroId'] ); } + $paramsString = ''; foreach ( $params as $key => $value ) { $paramsString .= "|$key=$value\n"; } @@ -130,7 +132,6 @@ private function makeParamsString( array $params ): string { } /** - * * @param DOMNode $macro * @return array */ @@ -139,7 +140,6 @@ private function getMacroParams( $macro ): array { foreach ( $macro->childNodes as $childNode ) { if ( $childNode->nodeName === 'ac:parameter' ) { $paramName = $childNode->getAttribute( 'ac:name' ); - if ( $paramName === '' ) { continue; } @@ -151,6 +151,18 @@ private function getMacroParams( $macro ): array { return $params; } + /** + * @param string $name + * @param string $extension + * @return string + */ + private function getConfluenceKey( string $name, string $extension = '' ): string { + $spaceId = $this->currentSpaceId; + $rawPageTitle = basename( $this->rawPageTitle ); + $name .= $extension; + return "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); + } + /** * @param string $confluenceFileKey * @return string diff --git a/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php b/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php index 687117b..932e83f 100644 --- a/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php +++ b/tests/phpunit/Converter/Processor/StructuredMacroGliffyTest.php @@ -39,10 +39,12 @@ public function testPreprocess() { '0---SomePage---gliffy-file-1' => 'SomePage_gliffy-file-1.unknown', '0---SomePage---gliffy-file-2.png' => 'SomePage_gliffy-file-2.png', '0---SomePage---gliffy-file-2' => 'SomePage_gliffy-file-2.unknown', + '0---SomePage---gliffy-file-2.svg' => 'SomePage_gliffy-file-2.svg', '23---SomePage---gliffy-file-1.png' => 'DEVOPS_SomePage_gliffy-file-1.png', '23---SomePage---gliffy-file-1' => 'DEVOPS_SomePage_gliffy-file-1.unknown', '23---SomePage---gliffy-file-2.png' => 'DEVOPS_SomePage_gliffy-file-2.png', '23---SomePage---gliffy-file-2' => 'DEVOPS_SomePage_gliffy-file-2.unknown', + '23---SomePage---gliffy-file-2.svg' => 'DEVOPS_SomePage_gliffy-file-2.svg', ], [], [], diff --git a/tests/phpunit/data/structuredmacro-gliffy-input.xml b/tests/phpunit/data/structuredmacro-gliffy-input.xml index dc64673..0a0a10f 100644 --- a/tests/phpunit/data/structuredmacro-gliffy-input.xml +++ b/tests/phpunit/data/structuredmacro-gliffy-input.xml @@ -12,4 +12,11 @@ gliffy-file-2 5 + + + 12345 + gliffy-file-2 + gliffy-file-2.svg + 5 + \ No newline at end of file diff --git a/tests/phpunit/data/structuredmacro-gliffy-output-1.xml b/tests/phpunit/data/structuredmacro-gliffy-output-1.xml index ca69f53..cb475af 100644 --- a/tests/phpunit/data/structuredmacro-gliffy-output-1.xml +++ b/tests/phpunit/data/structuredmacro-gliffy-output-1.xml @@ -8,5 +8,10 @@ {{Gliffy|displayName=gliffy-file-2 |name=SomePage_gliffy-file-2.png |pagePin=5 +}} + + {{Gliffy|displayName=gliffy-file-2 +|name=SomePage_gliffy-file-2.svg +|pagePin=5 }} diff --git a/tests/phpunit/data/structuredmacro-gliffy-output-2.xml b/tests/phpunit/data/structuredmacro-gliffy-output-2.xml index 66fadd8..6416b3b 100644 --- a/tests/phpunit/data/structuredmacro-gliffy-output-2.xml +++ b/tests/phpunit/data/structuredmacro-gliffy-output-2.xml @@ -8,5 +8,10 @@ {{Gliffy|displayName=gliffy-file-2 |name=DEVOPS_SomePage_gliffy-file-2.png |pagePin=5 +}} + + {{Gliffy|displayName=gliffy-file-2 +|name=DEVOPS_SomePage_gliffy-file-2.svg +|pagePin=5 }} diff --git a/tests/phpunit/data/structuredmacro-gliffy-output-3.xml b/tests/phpunit/data/structuredmacro-gliffy-output-3.xml index 24e8c47..00511e4 100644 --- a/tests/phpunit/data/structuredmacro-gliffy-output-3.xml +++ b/tests/phpunit/data/structuredmacro-gliffy-output-3.xml @@ -8,5 +8,10 @@ {{Gliffy|displayName=gliffy-file-2 |name=DEVOPS:SomePage_gliffy-file-2.png |pagePin=5 +}} + + {{Gliffy|displayName=gliffy-file-2 +|name=DEVOPS:SomePage_gliffy-file-2.svg +|pagePin=5 }} From beb8bd6e4aa96c97f4d7a6c6449b710b2535c575 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Wed, 24 Sep 2025 08:50:10 +0200 Subject: [PATCH 16/32] Improve performace --- src/Analyzer/ConfluenceAnalyzer.php | 1276 +++++++++++++++---------- src/Converter/ConfluenceConverter.php | 4 +- src/Utility/FilenameBuilder.php | 26 + src/Utility/TitleBuilder.php | 26 +- 4 files changed, 831 insertions(+), 501 deletions(-) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 4a24137..cdb495a 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -19,6 +19,7 @@ use SplFileInfo; use Symfony\Component\Console\Input\Input; use Symfony\Component\Console\Output\Output; +use XMLReader; class ConfluenceAnalyzer extends AnalyzerBase implements LoggerAwareInterface, IOutputAwareInterface { @@ -80,11 +81,6 @@ class ConfluenceAnalyzer extends AnalyzerBase implements LoggerAwareInterface, I */ private $advancedConfig = []; - /** - * @var bool - */ - private $hasAdvancedConfig = false; - /** * * @param array $config @@ -94,33 +90,59 @@ class ConfluenceAnalyzer extends AnalyzerBase implements LoggerAwareInterface, I public function __construct( $config, Workspace $workspace, DataBuckets $buckets ) { parent::__construct( $config, $workspace, $buckets ); $this->customBuckets = new DataBuckets( [ + 'space-id-to-prefix-map', 'space-key-to-prefix-map', + 'space-name-to-prefix-map', + 'space-id-to-name-map', + 'space-key-to-name-map', 'space-id-homepages', + 'space-id-to-description-id-map', + 'space-description-id-to-body-id-map', + 'space-details', + + 'page-id-to-confluence-title-map', + 'page-id-to-parent-page-id-map', + + 'body-content-id-to-page-id-map', + + 'attachment-id-to-orig-filename-map', + 'attachment-id-to-space-id-map', + 'attachment-id-to-reference-map', + + 'userkey-to-username-map', + 'pages-titles-map', - 'pages-ids-to-titles-map', - 'body-contents-to-pages-map', - 'title-invalids', - 'filenames-to-filetitles-map', + 'page-id-to-confluence-key-map', + 'page-id-to-title-map', 'page-id-to-space-id', - 'attachment-file-extensions', - 'space-name-to-prefix-map', - 'missing-attachment-id-to-filename', - 'userkey-to-username-map', - 'users', 'title-files', - 'additional-files', 'attachment-orig-filename-target-filename-map', - 'title-attachments', - 'space-id-to-description-id-map', - 'space-id-details-map', - 'space-description-id-to-body-id-map' + + ] ); + $this->logger = new NullLogger(); + $this->setConfigVars(); + } + + /** + * @return void + */ + private function setConfigVars(): void { if ( isset( $this->config['config'] ) ) { $this->advancedConfig = $this->config['config']; - $this->hasAdvancedConfig = true; + } + + if ( isset( $this->advancedConfig['ext-ns-file-repo-compat'] ) ) { + if ( is_bool( $this->advancedConfig['ext-ns-file-repo-compat'] ) ) { + $this->extNsFileRepoCompat = $this->advancedConfig['ext-ns-file-repo-compat']; + } + } + + if ( isset( $this->advancedConfig['mainpage'] ) ) { + $this->mainpage = $this->advancedConfig['mainpage']; } } @@ -168,550 +190,796 @@ public function analyze( SplFileInfo $file ): bool { * @return bool */ protected function doAnalyze( SplFileInfo $file ): bool { - $this->dom = new DOMDocument(); - $this->dom->load( $file->getPathname() ); - $this->helper = new XMLHelper( $this->dom ); + $xmlReader = new XMLReader(); + + // Process Space and BodyContents objects (needed by other objects) + $this->output->writeln( "\nPrepare required maps:" ); + + $xmlReader->open( $file->getPathname() ); + $read = $xmlReader->read(); + while ( $read ) { + if ( $xmlReader->name !== 'object' ) { + // Usually all root nodes should be objects. + $read = $xmlReader->read(); + continue; + } - if ( $this->hasAdvancedConfig && isset( $this->advancedConfig['ext-ns-file-repo-compat'] ) ) { - if ( is_bool( $this->advancedConfig['ext-ns-file-repo-compat'] ) ) { - $this->extNsFileRepoCompat = $this->advancedConfig['ext-ns-file-repo-compat']; - } else { - $this->extNsFileRepoCompat = false; + $objectXML = $xmlReader->readOuterXml(); + + $objectDom = new DOMDocument(); + $objectDom->loadXML( $objectXML ); + + $class = $xmlReader->getAttribute( 'class' ); + if ( $class === 'Space' ) { + $this->buildSpaceMaps( $objectDom ); + } else if ( $class === 'SpaceDescription' ) { + $this->buildSpaceDescriptionMap( $objectDom ); + } else if ( $class === "Page") { + $this->buildParentPageMap( $objectDom ); + } else if ( $class === "BodyContent") { + $this->buildBodyContentMap( $objectDom ); + } else if ( $class === "Attachment") { + $this->buildAttachmentMaps( $objectDom ); + } else if ( $class === "ConfluenceUserImpl") { + $this->buildUserMap( $objectDom ); } - } - if ( $this->hasAdvancedConfig && isset( $this->advancedConfig['mainpage'] ) ) { - $this->mainpage = $this->advancedConfig['mainpage']; + $read = $xmlReader->next(); } + $xmlReader->close(); - $this->userMap(); - $this->makeSpacesMap(); - $this->makeSpaceDetailsMap(); - $this->makeSpaceDescriptionMap(); - $this->makePagenamesMap(); - $this->addTitleAttachmentsFallback(); - $this->addAdditionalFiles(); + // Process Page objects (needed by other objects) + $this->output->writeln( "\nAnalyze pages:" ); - return true; - } + $xmlReader->open( $file->getPathname() ); + $read = $xmlReader->read(); + while ( $read ) { + if ( $xmlReader->name !== 'object' ) { + // Usually all root nodes should be objects. + $read = $xmlReader->read(); + continue; + } - private function makeSpacesMap() { - $spaces = $this->helper->getObjectNodes( 'Space' ); - $this->output->writeln( "\nFinding namespaces" ); - foreach ( $spaces as $space ) { - $spaceId = $this->helper->getIDNodeValue( $space ); - $spaceKey = $this->helper->getPropertyValue( 'key', $space ); - $spaceName = $this->helper->getPropertyValue( 'name', $space ); - if ( substr( $spaceKey, 0, 1 ) === '~' ) { - // User namespaces - $spaceKey = $this->sanitizeUserSpaceKey( $spaceKey, $spaceName ); - $this->output->writeln( "\033[31m- $spaceKey (ID:$spaceId) - protected user namespace\033[39m" ); - } else { - $this->output->writeln( "- $spaceKey (ID:$spaceId)" ); + $nodeXML = $xmlReader->readOuterXml(); + + $objectDom = new DOMDocument(); + $objectDom->loadXML( $nodeXML ); + + $class = $xmlReader->getAttribute( 'class' ); + if ( $class === 'Page' ) { + $this->buildPageMaps( $objectDom ); } - // Confluence's GENERAL equals MediaWiki's NS_MAIN, thus having no prefix - $bucketSpaceKey = $spaceKey; - if ( $spaceKey === 'GENERAL' ) { - $spaceKey = ''; + $read = $xmlReader->next(); + } + $xmlReader->close(); + + // Process title attachments fallback + $xmlReader->open( $file->getPathname() ); + $read = $xmlReader->read(); + while ( $read ) { + if ( $xmlReader->name !== 'object' ) { + // Usually all root nodes should be objects. + $read = $xmlReader->read(); + continue; } - if ( $this->hasAdvancedConfig && isset( $this->advancedConfig['space-prefix'][$spaceKey] ) ) { - $customSpacePrefix = $this->advancedConfig['space-prefix'][$spaceKey]; - } else { - $customSpacePrefix = "{$spaceKey}:"; + $nodeXML = $xmlReader->readOuterXml(); + + $objectDom = new DOMDocument(); + $objectDom->loadXML( $nodeXML ); + + $class = $xmlReader->getAttribute( 'class' ); + if ( $class === 'Attachment' ) { + $this->buildTitleAttachmentsFallbackMaps( $objectDom ); } - $this->customBuckets->addData( - 'space-id-to-prefix-map', $spaceId, $customSpacePrefix, false, true - ); - $this->customBuckets->addData( - 'space-key-to-prefix-map', $bucketSpaceKey, $customSpacePrefix, false, true - ); - $this->customBuckets->addData( - 'space-name-to-prefix-map', $spaceName, $customSpacePrefix, false, true - ); + $read = $xmlReader->next(); + } + $xmlReader->close(); + + // Process additional attachments + $xmlReader->open( $file->getPathname() ); + $read = $xmlReader->read(); + while ( $read ) { + if ( $xmlReader->name !== 'object' ) { + // Usually all root nodes should be objects. + $read = $xmlReader->read(); + continue; + } + + $nodeXML = $xmlReader->readOuterXml(); - $homePageId = -1; - $homePagePropertyNode = $this->helper->getPropertyNode( 'homePage' ); - if ( $homePagePropertyNode !== null ) { - $homePageId = $this->helper->getIDNodeValue( $homePagePropertyNode ); + $objectDom = new DOMDocument(); + $objectDom->loadXML( $nodeXML ); + + $class = $xmlReader->getAttribute( 'class' ); + if ( $class === 'Attachment' ) { + $this->buildAdditionalFilesMap( $objectDom ); } - $this->customBuckets->addData( 'space-id-homepages', $spaceId, $homePageId, false, true ); + + $read = $xmlReader->next(); } + + $xmlReader->close(); + + return true; } - private function makeSpaceDetailsMap() { - $spaces = $this->helper->getObjectNodes( 'Space' ); - $this->output->writeln( "\nFinding space details" ); - foreach ( $spaces as $space ) { - $details = []; - $spaceId = $this->helper->getIDNodeValue( $space ); - $spacekey = $this->helper->getPropertyValue( 'key', $space ); + /** + * @param DOMDocument $dom + * @return void + */ + private function buildSpaceMaps( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); + $spaces = $xmlHelper->getObjectNodes( 'Space' ); + if ( count( $spaces ) < 1 ) { + return; + } + $space = $spaces->item( 0 ); + if ( $space instanceof DOMElement === false ) { + return; + } - $this->output->writeln( "- $spacekey" ); + $spaceId = $xmlHelper->getIDNodeValue( $space ); + if ( $spaceId === -1 ) { + return; + } + $spaceKey = $xmlHelper->getPropertyValue( 'key', $space ); + $spaceName = $xmlHelper->getPropertyValue( 'name', $space ); + if ( substr( $spaceKey, 0, 1 ) === '~' ) { + // User namespaces + $spaceKey = $this->sanitizeUserSpaceKey( $spaceKey, $spaceName ); + $this->output->writeln( "\033[31mAdd space $spaceKey (ID:$spaceId) - protected user namespace\033[39m" ); + } else { + $this->output->writeln( "Add space $spaceKey (ID:$spaceId)" ); + } - // Property id - $details['id'] = $spaceId; + // Confluence's GENERAL equals MediaWiki's NS_MAIN, thus having no prefix + if ( $spaceKey === 'GENERAL' ) { + $spaceKey = ''; + } - // Property key - $details['key'] = $spacekey; + if ( isset( $this->advancedConfig['space-prefix'][$spaceKey] ) ) { + $customSpacePrefix = $this->advancedConfig['space-prefix'][$spaceKey]; + } else if ( $spaceKey !== '' ) { + $customSpacePrefix = "{$spaceKey}:"; + } else { + return; + } - // Text only propterties - $properties = [ - 'name', 'creationDate', 'lastModificationDate', 'spaceType', 'spaceStatus' - ]; + $this->customBuckets->addData( + 'space-id-to-prefix-map', $spaceId, $customSpacePrefix, false, true + ); + $this->customBuckets->addData( + 'space-key-to-prefix-map', $spaceKey, $customSpacePrefix, false, true + ); + $this->customBuckets->addData( + 'space-name-to-prefix-map', $spaceName, $customSpacePrefix, false, true + ); + $this->customBuckets->addData( + 'space-id-to-name-map', $spaceId, $spaceName, false, true + ); + $this->customBuckets->addData( + 'space-key-to-name-map', $spaceKey, $spaceName, false, true + ); + + + $homePageId = -1; + $homePagePropertyNode = $xmlHelper->getPropertyNode( 'homePage', $space ); + if ( $homePagePropertyNode !== null ) { + $homePageId = $xmlHelper->getIDNodeValue( $homePagePropertyNode ); + } + if ( $homePageId > -1 ) { + $this->customBuckets->addData( 'space-id-homepages', $spaceId, $homePageId, false, true ); + } - foreach ( $properties as $property ) { - $details[$property] = $this->helper->getPropertyValue( $property, $space ); - } + $details = []; + // Property id + $details['id'] = $spaceId; - // ID (int) node propterties - $propertyNode = $this->helper->getPropertyNode( 'description' ); - if ( $propertyNode !== null ) { - $details['description'] = $this->helper->getIDNodeValue( $propertyNode ); - $this->customBuckets->addData( - 'space-id-to-description-id-map', - $spaceId, - $details['description'], - false, - true - ); - } + // Property key + $details['key'] = $spaceKey; - $propertyNode = $this->helper->getPropertyNode( 'homePage' ); - if ( $propertyNode !== null ) { - $details['homePage'] = $this->helper->getIDNodeValue( $propertyNode ); - } + // Text only propterties + $properties = [ + 'name', 'creationDate', 'lastModificationDate', 'spaceType', 'spaceStatus' + ]; - // ID (key) node propterties - $properties = [ - 'creator', 'lastModifier' - ]; + foreach ( $properties as $property ) { + $details[$property] = $xmlHelper->getPropertyValue( $property, $space ); + } - foreach ( $properties as $property ) { - $propertyNode = $this->helper->getPropertyNode( $property ); - if ( $propertyNode !== null ) { - $details[$property] = $this->helper->getKeyNodeValue( $propertyNode ); - } - } + // ID (int) node propterties + $propertyNode = $xmlHelper->getPropertyNode( 'description' ); + if ( $propertyNode !== null ) { + $details['description'] = $xmlHelper->getIDNodeValue( $propertyNode ); + $this->customBuckets->addData( + 'space-id-to-description-id-map', + $spaceId, + $details['description'], + false, + true + ); - $this->customBuckets->addData( 'space-id-details-map', $spaceId, $details, false, true ); + $this->output->writeln( "Add space description ($spaceId)" ); } - } - private function makeSpaceDescriptionMap() { - $spacesDesc = $this->helper->getObjectNodes( 'SpaceDescription' ); - $this->output->writeln( "\nFinding SpaceDescription body id's" ); - foreach ( $spacesDesc as $desc ) { - $descID = $this->helper->getIDNodeValue( $desc ); - $bodyContents = $this->helper->getElementsFromCollection( 'bodyContents', $desc ); - $bodyContentIDs = []; - foreach ( $bodyContents as $bodyContent ) { - $id = $this->helper->getIDNodeValue( $bodyContent ); - $this->customBuckets->addData( 'space-description-id-to-body-id-map', $descID, $id, false, true ); - $this->output->writeln( "- $id" ); + $propertyNode = $xmlHelper->getPropertyNode( 'homePage' ); + if ( $propertyNode !== null ) { + $details['homePage'] = $xmlHelper->getIDNodeValue( $propertyNode ); + } + + // ID (key) node propterties + $properties = [ + 'creator', 'lastModifier' + ]; + + foreach ( $properties as $property ) { + $propertyNode = $xmlHelper->getPropertyNode( $property ); + if ( $propertyNode !== null ) { + $details[$property] = $xmlHelper->getKeyNodeValue( $propertyNode ); } + } + if ( !empty( $details ) ) { + $this->customBuckets->addData( 'space-details', $spaceId, $details, false, true ); + $this->output->writeln( "Add details description ($spaceId)" ); } } /** - * - * @param int|string $spaceKey - * @param string $spaceName - * @return string + * @param DOMDocument $dom + * @return void */ - private function sanitizeUserSpaceKey( $spaceKey, $spaceName ) { - $spaceKey = substr( $spaceKey, 1, strlen( $spaceKey ) - 1 ); - if ( is_numeric( $spaceKey ) ) { - $spaceKey = $spaceName; + private function buildSpaceDescriptionMap( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); + $spaceDescriptions = $xmlHelper->getObjectNodes( 'SpaceDescription' ); + if ( count( $spaceDescriptions ) < 1 ) { + return; + } + $spaceDescription = $spaceDescriptions->item( 0 ); + if ( $spaceDescription instanceof DOMElement === false ) { + return; } - $spaceKey = preg_replace( '/[^A-Za-z0-9]/', '', $spaceKey ); - return 'User' . ucfirst( $spaceKey ); - } - - private function makePagenamesMap() { - $this->output->writeln( "\nFinding pages" ); - $pageNodes = $this->helper->getObjectNodes( "Page" ); - $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); - $spaceIdHomepages = $this->customBuckets->getBucketData( 'space-id-homepages' ); - $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $this->helper, $this->mainpage ); - foreach ( $pageNodes as $pageNode ) { - if ( $pageNode instanceof DOMElement === false ) { - continue; - } - - $status = $this->helper->getPropertyValue( 'contentStatus', $pageNode ); - if ( $status !== 'current' ) { - continue; - } - - $spaceId = $this->helper->getPropertyValue( 'space', $pageNode ); - if ( $spaceId === null ) { - continue; - } - $originalVersionID = $this->helper->getPropertyValue( 'originalVersion', $pageNode ); - if ( $originalVersionID !== null ) { - continue; - } + $descID = $xmlHelper->getIDNodeValue( $spaceDescription ); + $bodyContents = $xmlHelper->getElementsFromCollection( 'bodyContents', $spaceDescription ); + foreach ( $bodyContents as $bodyContent ) { + $id = $xmlHelper->getIDNodeValue( $bodyContent ); + $this->customBuckets->addData( 'space-description-id-to-body-id-map', $descID, $id, false, true ); + $this->output->writeln( "\nAdd space description ($id)" ); + } + } - $pageId = $this->helper->getIDNodeValue( $pageNode ); + /** + * @param DOMDocument $dom + * @return void + */ + private function buildParentPageMap( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); - try { - $targetTitle = $titleBuilder->buildTitle( $pageNode ); - } catch ( InvalidTitleException $ex ) { - $this->buckets->addData( 'title-invalids', $pageId, $ex->getInvalidTitle() ); - continue; - } + $pages = $xmlHelper->getObjectNodes( 'Page' ); + if ( count( $pages ) < 1 ) { + + return; + } + $pageNode = $pages->item( 0 ); + if ( $pageNode instanceof DOMElement === false ) { + $this->output->writeln( __LINE__ ); + return; + } + $status = $xmlHelper->getPropertyValue( 'contentStatus', $pageNode ); + if ( $status !== 'current' ) { + return; + } + $spaceId = $xmlHelper->getPropertyValue( 'space', $pageNode ); + if ( $spaceId === null ) { + return; + } + $originalVersionID = $xmlHelper->getPropertyValue( 'originalVersion', $pageNode ); + if ( $originalVersionID !== null ) { + $this->output->writeln( __LINE__ ); + return; + } - $this->output->writeln( "- '$targetTitle' (ID:$pageId)" ); - - /** - * Adds data bucket "pages-titles-map", which contains mapping from page title itself to full page title. - * Full page title contains parent pages and namespace (if it is not general space). - * Example: - * "Detailed_planning" -> "Dokumentation/Detailed_planning" - */ - $this->pageConfluenceTitle = $this->helper->getPropertyValue( 'title', $pageNode ); - $genericTitleBuilder = new GenericTitleBuilder( [] ); - $this->pageConfluenceTitle = $genericTitleBuilder - ->appendTitleSegment( $this->pageConfluenceTitle )->build(); - // We need to preserve the spaceID, so we can properly resolve cross-space links - // in the `convert` stage - $this->pageConfluenceTitle = "$spaceId---{$this->pageConfluenceTitle}"; - // Some normalization - $this->pageConfluenceTitle = str_replace( ' ', '_', $this->pageConfluenceTitle ); - $this->customBuckets->addData( 'pages-titles-map', $this->pageConfluenceTitle, $targetTitle, false, true ); - - // Also add pages IDs in Confluence to full page title mapping. - // It is needed to have enough context on converting stage, - // to know from filename which page is currently being converted. - $this->customBuckets->addData( 'pages-ids-to-titles-map', $pageId, $targetTitle, false, true ); - - $this->customBuckets->addData( 'page-id-to-space-id', $pageId, $spaceId, false, true ); - - $revisionTimestamp = $this->buildRevisionTimestamp( $pageNode ); - $bodyContentIds = $this->getBodyContentIds( $pageNode ); - - if ( !empty( $bodyContentIds ) ) { - foreach ( $bodyContentIds as $bodyContentId ) { - // TODO: Add UserImpl-key or directly MediaWiki username - // (could also be done in `extract` as "metadata" ) - $this->customBuckets->addData( 'body-contents-to-pages-map', $bodyContentId, $pageId, false, true ); - } - } else { - $bodyContentIds = []; - - $bodyContents = $this->helper->getObjectNodes( 'BodyContent' ); - foreach ( $bodyContents as $bodyContent ) { - $bodyContentId = $this->helper->getIDNodeValue( $bodyContent ); - $contentPageId = $this->helper->getPropertyValue( 'content', $bodyContent ); - - if ( $pageId === $contentPageId ) { - $bodyContentIds[] = $bodyContentId; - - $this->customBuckets->addData( - 'body-contents-to-pages-map', - $bodyContentId, - $pageId, - false, - true - ); - } - } - } + $pageId = $xmlHelper->getIDNodeValue( $pageNode ); + $parentPageId = $xmlHelper->getPropertyValue( 'parent', $pageNode ); + if ( $parentPageId !== null ) { + $this->customBuckets->addData( 'page-id-to-parent-page-id-map', $pageId, $parentPageId, false, true ); + } - $version = $this->helper->getPropertyValue( 'version', $pageNode ); - - $this->addTitleRevision( $targetTitle, implode( '/', $bodyContentIds ) . "@$version-$revisionTimestamp" ); - - // In case of ERM34465 this seems to be empty because - // title-attachments and missing-attachment-id-to-filename are empty - $attachmentRefs = $this->helper->getElementsFromCollection( 'attachments', $pageNode ); - foreach ( $attachmentRefs as $attachmentRef ) { - $attachmentId = $this->helper->getIDNodeValue( $attachmentRef ); - $attachment = $this->helper->getObjectNodeById( $attachmentId, 'Attachment' ); - $attachmentTargetFilename = $this->makeAttachmentTargetFilename( $attachment, $targetTitle ); - $attachmentReference = $this->makeAttachmentReference( $attachment ); - if ( empty( $attachmentReference ) ) { - $this->output->writeln( - //phpcs:ignore Generic.Files.LineLength.TooLong - "\033[31m\t- File '$attachmentId' ($attachmentTargetFilename) not found\033[39m" - ); - $this->customBuckets->addData( - 'missing-attachment-id-to-filename', - $attachmentId, - $attachmentTargetFilename, - false, - true - ); - continue; - } - // In case of ERM34465 no files are added to title-attachments - $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); - $this->addFile( $attachmentTargetFilename, $attachmentReference ); - $this->customBuckets->addData( 'title-files', $targetTitle, $attachmentTargetFilename, false, true ); - $this->addedAttachmentIds[$attachmentId] = true; - - $fileName = $this->helper->getPropertyValue( 'fileName', $attachment ); - if ( $fileName === null ) { - $fileName = $this->helper->getPropertyValue( 'title', $attachment ); - } - $this->customBuckets->addData( - 'attachment-orig-filename-target-filename-map', - $fileName, - $attachmentTargetFilename - ); - } + $pageId = $xmlHelper->getIDNodeValue( $pageNode ); + $confluenceTitle = $xmlHelper->getPropertyValue( 'title', $pageNode ); + if ( $confluenceTitle !== null ) { + $this->customBuckets->addData( 'page-id-to-confluence-title-map', $pageId, $confluenceTitle, false, true ); } } - private function addTitleAttachmentsFallback() { - $currentTitleAttachments = $this->customBuckets->getBucketData( 'title-attachments' ); - if ( empty( $currentTitleAttachments ) ) { - $this->output->writeln( "\nFinding title attachments fallback" ); - - $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); - $spaceIdHomepages = $this->customBuckets->getBucketData( 'space-id-homepages' ); - $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $this->helper, $this->mainpage ); - - $attachmentObjs = $this->helper->getObjectNodes( 'Attachment' ); - foreach ( $attachmentObjs as $attachmentObj ) { - $attachmentId = $this->helper->getIDNodeValue( $attachmentObj ); - $containerContent = $this->helper->getPropertyNode( 'containerContent', $attachmentObj ); - $containerContentId = $this->helper->getIDNodeValue( $containerContent ); - $pageObj = $this->helper->getObjectNodeById( $containerContentId, 'Page' ); - if ( $pageObj instanceof DOMElement === false ) { - continue; - } + /** + * @param DOMDocument $dom + * @return void + */ + private function buildBodyContentMap( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); + $bodyContentObjects = $xmlHelper->getObjectNodes( 'BodyContent' ); + if ( count( $bodyContentObjects ) < 1 ) { + return; + } + $bodyContentObject = $bodyContentObjects->item( 0 ); + if ( $bodyContentObject instanceof DOMElement === false ) { + return; + } - if ( $containerContentId !== $this->helper->getIDNodeValue( $pageObj ) ) { - continue; - } + $bodyContentId = $xmlHelper->getIDNodeValue( $bodyContentObject ); + $contentPageId = $xmlHelper->getPropertyValue( 'content', $bodyContentObject ); + $this->customBuckets->addData( 'body-content-id-to-page-id-map', + $bodyContentId, $contentPageId, false, true ); + } - $attachmentObjContentStatus = $this->helper->getPropertyValue( 'contentStatus', $attachmentObj ); - if ( strtolower( $attachmentObjContentStatus ) !== 'current' ) { - continue; - } + /** + * @param DOMDocument $dom + * @return void + */ + private function buildAttachmentMaps( DOMDocument $dom ):void { + $xmlHelper = new XMLHelper( $dom ); - try { - $targetTitle = $titleBuilder->buildTitle( $pageObj ); - } catch ( InvalidTitleException $ex ) { - continue; - } + $attachmentNodes = $xmlHelper->getObjectNodes( 'Attachment' ); + if ( count( $attachmentNodes ) < 1 ) { + return; + } + $attachmentNode = $attachmentNodes->item( 0 ); + if ( $attachmentNode instanceof DOMElement === false ) { + return; + } - $attachmentId = $this->helper->getIDNodeValue( $attachmentObj ); - $attachmentTargetFilename = $this->makeAttachmentTargetFilename( $attachmentObj, $targetTitle ); - $attachmentReference = $this->makeAttachmentReference( $attachmentObj ); - if ( empty( $attachmentReference ) ) { - $this->output->writeln( - //phpcs:ignore Generic.Files.LineLength.TooLong - "\033[31m\t- File '$attachmentId' ($attachmentTargetFilename) not found\033[39m" - ); - $this->customBuckets->addData( - 'missing-attachment-id-to-filename', - $attachmentId, - $attachmentTargetFilename, - false, - true - ); - continue; - } - $this->output->writeln( "- $attachmentTargetFilename" ); - $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); - $this->addFile( $attachmentTargetFilename, $attachmentReference ); - $this->customBuckets->addData( 'title-files', $targetTitle, $attachmentTargetFilename, false, true ); - $this->addedAttachmentIds[$attachmentId] = true; - - $fileName = $this->helper->getPropertyValue( 'fileName', $attachmentObj ); - if ( $fileName === null ) { - $fileName = $this->helper->getPropertyValue( 'title', $attachmentObj ); - } - $this->customBuckets->addData( - 'attachment-orig-filename-target-filename-map', - $fileName, - $attachmentTargetFilename - ); - } + $attachmentId = $xmlHelper->getIDNodeValue( $attachmentNode ); + $attachmentFilename = $xmlHelper->getPropertyValue( 'fileName', $attachmentNode ); + if ( $attachmentFilename === null ) { + $attachmentFilename = $xmlHelper->getPropertyValue( 'title', $attachmentNode ); + } + + if ( $attachmentFilename !== '' && is_int( $attachmentId ) ) { + $this->customBuckets->addData( 'attachment-id-to-orig-filename-map', $attachmentId, $attachmentFilename, false, true ); + } + $attachmentSpaceId = $xmlHelper->getPropertyValue( 'space', $attachmentNode ); + if ( is_int( $attachmentId ) ) { + $this->customBuckets->addData( 'attachment-id-to-space-id-map', $attachmentId, $attachmentSpaceId, false, true ); + } + $attachmentReference = $this->makeAttachmentReference( $xmlHelper, $attachmentNode ); + if ( $attachmentReference !== '' ) { + $this->customBuckets->addData( 'attachment-id-to-reference-map', $attachmentId, $attachmentReference, false, true ); } } /** - * - * @param DOMElement $attachment - * @param string $containerTitle - * @return string + * @param DOMDocument $dom + * @return void */ - private function makeAttachmentTargetFilename( $attachment, $containerTitle ) { - $fileName = $this->helper->getPropertyValue( 'fileName', $attachment ); - if ( $fileName === null ) { - $fileName = $this->helper->getPropertyValue( 'title', $attachment ); - } + private function buildUserMap( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); - $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); - $filenameBuilder = new FilenameBuilder( $spaceIdPrefixMap, $this->helper ); - $attachmentId = $this->helper->getIDNodeValue( $attachment ); - try { - $targetName = $filenameBuilder->buildFilename( $attachment, $containerTitle ); - } catch ( InvalidTitleException $e ) { - try { - // Probably it is just too long. Let's try to use a shortened variant - // This is not ideal, but should be okay as a fallback in most cases. - $shortContainerTitle = basename( $containerTitle ); - $targetName = $filenameBuilder->buildFilename( $attachment, $shortContainerTitle ); - } catch ( InvalidTitleException $ex ) { - $this->buckets->addData( 'title-invalids', $attachmentId, $ex->getInvalidTitle() ); - $this->logger->error( $ex->getMessage() ); - return '###INVALID###'; - } + $userImpls = $xmlHelper->getObjectNodes( 'ConfluenceUserImpl' ); + if ( count( $userImpls ) < 1 ) { + return; + } + $userImpl = $userImpls->item( 0 ); + if ( $userImpl instanceof DOMElement === false ) { + return; } - /* - * Some attachments do not have a file extension available. We try - * to find an extension by looking a the content type, but - * sometimes even this won't help... ("octet-stream") - */ - $file = new SplFileInfo( $targetName ); - if ( $this->hasNoExplicitFileExtension( $file ) ) { - $contentType = $this->helper->getPropertyValue( 'contentType', $attachment ); - $this->logger->debug( - "Could not find file extension for $fileName as " - . "{$attachment->getNodePath()}; " - . "contentType: $contentType" - ); - $targetName .= '.unknown'; + // Can not use `XMLHelper::getIDNodeValue` here, as the key is not an integer + $idNode = $userImpl->getElementsByTagName( 'id' )->item( 0 ); + $userImplKey = $idNode->nodeValue; + $lcUserName = $xmlHelper->getPropertyValue( 'lowerName', $userImpl ); + $email = $xmlHelper->getPropertyValue( 'email', $userImpl ); + if ( !$lcUserName ) { + $this->output->writeln( "\033[31m User $userImplKey has no username\033[39m" ); + return; } - $fileKey = "{$this->pageConfluenceTitle}---$fileName"; - // Some normalization - $fileKey = str_replace( ' ', '_', $fileKey ); - $this->customBuckets->addData( 'filenames-to-filetitles-map', $fileKey, $targetName, false, true ); + $mediaWikiUsername = $this->makeMWUserName( $lcUserName ); - return $targetName; + $this->customBuckets->addData( + 'userkey-to-username-map', + $userImplKey, + $mediaWikiUsername, + false + ); + + $this->customBuckets->addData( + 'users', + $mediaWikiUsername, + [ + 'email' => $email === null ? '' : $email + ], + false, + true + ); + + $this->output->writeln( "Add user '$mediaWikiUsername' (ID:$userImplKey)" ); } /** - * - * @param DOMElement $attachment - * @return string + * @param DOMDocument $dom + * @param array $spaceIdPrefixMap + * @param array $spaceIdHomepages + * @param array $pageIdParentPageIdMap + * @param array $pageIConfluenceTitledMap + * @param array $bodyContents + * @param array $attachmentIdToOrigFilenameMap + * @param array $attachmentIdToSpaceIdMap + * @param string $mainpage + * @return void */ - private function makeAttachmentReference( $attachment ) { - $basePath = $this->currentFile->getPath() . '/attachments'; - $attachmentId = $this->helper->getIDNodeValue( $attachment ); - $containerId = $this->helper->getPropertyValue( 'content', $attachment ); - if ( empty( $containerId ) ) { - $containerId = $this->helper->getPropertyValue( 'containerContent', $attachment ); + private function buildPageMaps( DOMDocument $dom ): void { + $spaceIdToPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); + $spaceIdHomepages = $this->customBuckets->getBucketData( 'space-id-homepages' ); + $pageIdParentPageIdMap = $this->customBuckets->getBucketData( 'page-id-to-parent-page-id-map' ); + $pageIdConfluendTitleMap = $this->customBuckets->getBucketData( 'page-id-to-confluence-title-map' ); + $bodyContents = $this->customBuckets->getBucketData( 'body-content-id-to-page-id-map' ); + $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); + $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); + $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); + + $xmlHelper = new XMLHelper( $dom ); + + $pages = $xmlHelper->getObjectNodes( 'Page' ); + if ( count( $pages ) < 1 ) { + + return; } - $attachmentVersion = $this->helper->getPropertyValue( 'attachmentVersion', $attachment ); - if ( empty( $attachmentVersion ) ) { - $attachmentVersion = $this->helper->getPropertyValue( 'version', $attachment ); + $pageNode = $pages->item( 0 ); + if ( $pageNode instanceof DOMElement === false ) { + return; + } + $status = $xmlHelper->getPropertyValue( 'contentStatus', $pageNode ); + if ( $status !== 'current' ) { + return; + } + $spaceId = $xmlHelper->getPropertyValue( 'space', $pageNode ); + if ( $spaceId === null ) { + return; + } + $originalVersionID = $xmlHelper->getPropertyValue( 'originalVersion', $pageNode ); + if ( $originalVersionID !== null ) { + return; } + $pageId = $xmlHelper->getIDNodeValue( $pageNode ); + + $titleBuilder = new TitleBuilder( $spaceIdToPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIdConfluendTitleMap, $xmlHelper, $this->mainpage ); + try { + $targetTitle = $titleBuilder->buildTitle( $pageNode ); + $this->output->writeln( $targetTitle ); + } catch ( InvalidTitleException $ex ) { + $this->buckets->addData( 'title-invalids', $pageId, $ex->getInvalidTitle() ); + return; + } + + $this->output->writeln( "Add page '$targetTitle' (ID:$pageId)" ); + /** - * Sometimes there is no explicit version set in the "attachment" object. In such cases - * there we always fetch the highest number from the respective directory + * Adds data bucket "pages-titles-map", which contains mapping from page title itself to full page title. + * Full page title contains parent pages and namespace (if it is not general space). + * Example: + * "Detailed_planning" -> "Dokumentation/Detailed_planning" */ - if ( empty( $attachmentVersion ) ) { - $attachmentVersion = '__LATEST__'; - } + $pageConfluenceTitle = $xmlHelper->getPropertyValue( 'title', $pageNode ); + $genericTitleBuilder = new GenericTitleBuilder( [] ); + $pageConfluenceTitle = $genericTitleBuilder + ->appendTitleSegment( $pageConfluenceTitle )->build(); + // We need to preserve the spaceID, so we can properly resolve cross-space links + // in the `convert` stage + $pageConfluenceTitle = "$spaceId---{$pageConfluenceTitle}"; + // Some normalization + $pageConfluenceTitle = str_replace( ' ', '_', $pageConfluenceTitle ); + $this->customBuckets->addData( 'pages-titles-map', $pageConfluenceTitle, $targetTitle, false, true ); + $this->customBuckets->addData( 'page-id-to-confluence-key-map', $pageId, $pageConfluenceTitle, false, true ); + + // Also add pages IDs in Confluence to full page title mapping. + // It is needed to have enough context on converting stage, + // to know from filename which page is currently being converted. + $this->customBuckets->addData( 'page-id-to-title-map', $pageId, $targetTitle, false, true ); + $this->customBuckets->addData( 'page-id-to-space-id', $pageId, $spaceId, false, true ); + + $revisionTimestamp = $this->buildRevisionTimestamp( $xmlHelper, $pageNode ); + $bodyContentIds = $this->getBodyContentIds( $xmlHelper, $pageNode ); + if ( !empty( $bodyContentIds ) ) { + foreach ( $bodyContentIds as $bodyContentId ) { + // TODO: Add UserImpl-key or directly MediaWiki username + // (could also be done in `extract` as "metadata" ) + $this->customBuckets->addData( 'body-contents-to-pages-map', $bodyContentId, $pageId, false, true ); + } + } else { + $bodyContentIds = []; - $path = $basePath . "/" . $containerId . '/' . $attachmentId . '/' . $attachmentVersion; - if ( !file_exists( $path ) ) { - return ''; + foreach ( $bodyContents as $bodyContentId => $contentPageId ) { + if ( $pageId === $contentPageId ) { + $bodyContentIds[] = $bodyContentId; + + $this->customBuckets->addData( + 'body-contents-to-pages-map', + $bodyContentId, + $pageId, + false, + true + ); + } + } } - return $path; - } + $version = $xmlHelper->getPropertyValue( 'version', $pageNode ); - /** - * - * @param DOMElement $pageNode - * @return string - */ - private function buildRevisionTimestamp( $pageNode ) { - $lastModificationDate = $this->helper->getPropertyValue( 'lastModificationDate', $pageNode ); - $time = strtotime( $lastModificationDate ); - $mwTimestamp = date( 'YmdHis', $time ); - return $mwTimestamp; + $this->addTitleRevision( $targetTitle, implode( '/', $bodyContentIds ) . "@$version-$revisionTimestamp" ); + + // In case of ERM34465 this seems to be empty because + // title-attachments and debug-missing-attachment-id-to-filename are empty + $attachmentRefs = $xmlHelper->getElementsFromCollection( 'attachments', $pageNode ); + foreach ( $attachmentRefs as $attachmentRef ) { + $attachmentId = $xmlHelper->getIDNodeValue( $attachmentRef ); + if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { + continue; + }; + $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; + + if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { + $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; + } else { + $attachmentSpaceId = $spaceId; + } + + $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( + $pageConfluenceTitle, $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, $targetTitle, $spaceIdToPrefixMap ); + + if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { + $this->output->writeln( + //phpcs:ignore Generic.Files.LineLength.TooLong + "\033[31m\t- File '$attachmentId' ($attachmentTargetFilename) not found\033[39m" + ); + $this->customBuckets->addData( + 'debug-missing-attachment-id-to-filename', + $attachmentId, + $attachmentTargetFilename, + false, + true + ); + continue; + }; + $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; + + // In case of ERM34465 no files are added to title-attachments + $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); + $this->addFile( $attachmentTargetFilename, $attachmentReference ); + $this->customBuckets->addData( 'title-files', $targetTitle, $attachmentTargetFilename, false, true ); + $this->addedAttachmentIds[$attachmentId] = true; + + $this->customBuckets->addData( + 'attachment-orig-filename-target-filename-map', + $attachmentOrigFilename, + $attachmentTargetFilename + ); + } } /** - * - * @param DOMElement $pageNode - * @return array + * @param array $spaceIdHomepages + * @param array $bodyContents + * @param string $mainpage + * @return void */ - private function getBodyContentIds( $pageNode ) { - $ids = []; - $bodyContentEl = $this->helper->getElementsFromCollection( 'bodyContents', $pageNode ); + private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { + $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); + $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); + $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); + $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); + $pageIdToTitleMap = $this->customBuckets->getBucketData( 'page-id-to-title-map' ); + $titleAttachmentsMap = $this->buckets->getBucketData( 'title-attachments' ); - foreach ( $bodyContentEl as $bodyContentElement ) { - $ids[] = $this->helper->getIDNodeValue( $bodyContentElement ); + $xmlHelper = new XMLHelper( $dom ); + + $attachmentObjects = $xmlHelper->getObjectNodes( 'Attachment' ); + if ( count( $attachmentObjects ) < 1 ) { + return; } - return $ids; + $attachmentNode = $attachmentObjects->item( 0 ); + if ( $attachmentNode instanceof DOMElement === false ) { + return; + } + if ( !empty( $titleAttachmentsMap ) ) { + return; + } + $attachmentId = $xmlHelper->getIDNodeValue( $attachmentNode ); + $containerContent = $xmlHelper->getPropertyNode( 'containerContent', $attachmentNode ); + $containerContentId = $xmlHelper->getIDNodeValue( $containerContent ); + + if ( !isset( $pageIdToTitleMap[$containerContentId] ) ) { + $targetTitle = ''; + } else { + $targetTitle = $pageIdToTitleMap[$containerContentId]; + } + $attachmentNodeContentStatus = $xmlHelper->getPropertyValue( 'contentStatus', $attachmentNode ); + if ( strtolower( $attachmentNodeContentStatus ) !== 'current' ) { + return; + } + if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { + return; + }; + $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; + if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { + $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; + } else { + $attachmentSpaceId = 0; // TODO: Is this wise? + } + + $confluenceKey = ''; + if ( isset( $pageIdToConfluenceKey[$containerContentId] ) ) { + $confluenceKey = $pageIdToConfluenceKey[$containerContentId]; + } + $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( $confluenceKey, $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, $targetTitle, $spaceIdPrefixMap ); + if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { + $this->output->writeln( + //phpcs:ignore Generic.Files.LineLength.TooLong + "\033[31m\t- File '$attachmentId' ($attachmentTargetFilename) not found\033[39m" + ); + $this->customBuckets->addData( + 'debug-missing-attachment-id-to-filename', + $attachmentId, + $attachmentTargetFilename, + false, + true + ); + return; + }; + $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; + $this->output->writeln( "Add attachment $attachmentTargetFilename (fallback)" ); + $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); + $this->addFile( $attachmentTargetFilename, $attachmentReference ); + $this->addedAttachmentIds[$attachmentId] = true; + + + $this->customBuckets->addData( + 'attachment-orig-filename-target-filename-map', + $attachmentOrigFilename, + $attachmentTargetFilename + ); } - private function addAdditionalFiles() { + /** + * @param DOMDocument $dom + * @param array $spaceIdPrefixMap + * @return void + */ + private function buildAdditionalFilesMap( DOMDocument $dom ) { + $titleAttachmentsMap = $this->buckets->getBucketData( 'title-attachments' ); + $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); + $spaceIdHomepages = $this->customBuckets->getBucketData( 'space-id-homepages' ); + $pageIdParentPageIdMap = $this->customBuckets->getBucketData( 'page-id-to-parent-page-id-map' ); + $pageIdConfluendTitleMap = $this->customBuckets->getBucketData( 'page-id-to-confluence-title-map' ); + $bodyContents = $this->customBuckets->getBucketData( 'body-content-id-to-page-id-map' ); + $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); + $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); + $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); + $pageIdToTitleMap = $this->customBuckets->getBucketData( 'page-id-to-title-map' ); + $pageIdConflunceKey = $this->customBuckets->getBucketData( 'page-id-to-confluence-key-map' ); + $titleAttachmentsMap = $this->buckets->getBucketData( 'title-attachments' ); + + $xmlHelper = new XMLHelper( $dom ); + $this->output->writeln( "\nFinding attachments" ); - $attachments = $this->helper->getObjectNodes( 'Attachment' ); + $attachments = $xmlHelper->getObjectNodes( 'Attachment' ); foreach ( $attachments as $attachment ) { if ( $attachment instanceof DOMElement === false ) { continue; } - $originalVersionID = $this->helper->getPropertyValue( 'originalVersion', $attachment ); + $originalVersionID = $xmlHelper->getPropertyValue( 'originalVersion', $attachment ); // Skip legacy versions if ( $originalVersionID !== null ) { continue; } - $sourceContentID = $this->helper->getPropertyValue( 'sourceContent', $attachment ); + $sourceContentID = $xmlHelper->getPropertyValue( 'sourceContent', $attachment ); if ( !empty( $sourceContentID ) ) { // This has already been added as a page attachment continue; } - $attachmentId = $this->helper->getIDNodeValue( $attachment ); + $attachmentId = $xmlHelper->getIDNodeValue( $attachment ); if ( isset( $this->addedAttachmentIds[$attachmentId] ) ) { // This has already been added as a page attachment continue; } - $path = $this->makeAttachmentReference( $attachment ); - $targetName = $this->makeAttachmentTargetFilename( $attachment, '' ); - $this->output->writeln( "- '$targetName'" ); - $this->addFile( $targetName, $path ); - $this->customBuckets->addData( 'additional-files', $targetName, $path, false, true ); - $fileName = $this->helper->getPropertyValue( 'fileName', $attachment ); - if ( $fileName === null ) { - $fileName = $this->helper->getPropertyValue( 'title', $attachment ); + if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { + continue; + }; + $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; + + $attachmetFilenameMap = $this->customBuckets->getBucketData( 'attachment-orig-filename-target-filename-map' ); + if ( isset( $attachmetFilenameMap[$attachmentOrigFilename] ) ) { + continue; + }; + + if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { + $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; + } else { + $attachmentSpaceId = 0; // TODO: Is this wise? + } + + if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { + continue; } - $this->customBuckets->addData( 'attachment-orig-filename-target-filename-map', $fileName, $targetName ); + $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; + $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( '', $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, '', $spaceIdPrefixMap ); + $this->output->writeln( "- '$attachmentTargetFilename'" ); + $this->addFile( $attachmentTargetFilename, $attachmentReference ); + $this->customBuckets->addData( 'debug-additional-files', $attachmentTargetFilename, $attachmentReference, false, true ); + $this->customBuckets->addData( 'attachment-orig-filename-target-filename-map', $attachmentOrigFilename, $attachmentTargetFilename ); + $this->addedAttachmentIds[$attachmentId] = true; } } /** - * @inheritDoc + * @param int|string $spaceKey + * @param string $spaceName + * @return string */ - protected function addFile( $rawFilename, $attachmentReference = 'n/a' ) { - $parts = explode( '.', $rawFilename ); - if ( count( $parts ) > 1 ) { - $extension = array_pop( $parts ); - $normalExtension = strtolower( $extension ); - $this->customBuckets->addData( - 'attachment-file-extensions', - 'extensions', - $normalExtension, - true, - true - ); + private function sanitizeUserSpaceKey( $spaceKey, $spaceName ) { + $spaceKey = substr( $spaceKey, 1, strlen( $spaceKey ) - 1 ); + if ( is_numeric( $spaceKey ) ) { + $spaceKey = $spaceName; } - return parent::addFile( $rawFilename, $attachmentReference ); + $spaceKey = preg_replace( '/[^A-Za-z0-9]/', '', $spaceKey ); + return 'User' . ucfirst( $spaceKey ); + } + + /** + * @param string $userName + * @return string + */ + private function makeMWUserName( $userName ) { + // Email adresses are no valid MW usernames. We just use the first part + // While this could lead to collisions it is very unlikly + $usernameParts = explode( '@', $userName, 2 ); + $newUsername = $usernameParts[0]; + $newUsername = ucfirst( strtolower( $newUsername ) ); + + // A MW username must always be avalid page title + $titleBuilder = new GenericTitleBuilder( [] ); + $titleBuilder->appendTitleSegment( $newUsername ); + + return $titleBuilder->build(); + } + + /** + * @param XMLHelper $xmlHelper + * @param DOMElement $pageNode + * @return string + */ + private function buildRevisionTimestamp( XMLHelper $xmlHelper, DOMElement $pageNode ): string { + $lastModificationDate = $xmlHelper->getPropertyValue( 'lastModificationDate', $pageNode ); + $time = strtotime( $lastModificationDate ); + $mwTimestamp = date( 'YmdHis', $time ); + return $mwTimestamp; + } + + /** + * @param XMLHelper $xmlHelper + * @param DOMElement $pageNode + * @return array + */ + private function getBodyContentIds( XMLHelper $xmlHelper, DOMElement $pageNode ): array { + $ids = []; + $bodyContentEl = $xmlHelper->getElementsFromCollection( 'bodyContents', $pageNode ); + + foreach ( $bodyContentEl as $bodyContentElement ) { + $ids[] = $xmlHelper->getIDNodeValue( $bodyContentElement ); + } + return $ids; } /** - * * @param SplFileInfo $file * @return bool */ @@ -726,59 +994,81 @@ private function hasNoExplicitFileExtension( $file ) { return false; } - private function userMap() { - $this->output->writeln( "\nFinding users" ); - $userImpls = $this->helper->getObjectNodes( 'ConfluenceUserImpl' ); - foreach ( $userImpls as $userImpl ) { - // Can not use `XMLHelper::getIDNodeValue` here, as the key is not an integer - $idNode = $userImpl->getElementsByTagName( 'id' )->item( 0 ); - $userImplKey = $idNode->nodeValue; - $lcUserName = $this->helper->getPropertyValue( 'lowerName', $userImpl ); - $email = $this->helper->getPropertyValue( 'email', $userImpl ); - if ( !$lcUserName ) { - $this->output->writeln( "\033[31m- UserImpl $userImplKey has no username\033[39m" ); - continue; + /** + * @param integer $attachmentId + * @param integer $attachmentSpaceId + * @param string $attachmentOrigFilename + * @param string $containerTitle + * @param array $spaceIdToPrefixMap + * @return string + */ + private function makeAttachmentTargetFilenameFromData( string $pageConfluenceTitle, int $attachmentId, int $attachmentSpaceId, string $attachmentOrigFilename, string $containerTitle, array $spaceIdToPrefixMap ): string { + $filenameBuilder = new FilenameBuilder( $spaceIdToPrefixMap, null ); + try { + $targetName = $filenameBuilder->buildFromAttachmentData( $attachmentSpaceId, $attachmentOrigFilename, $containerTitle ); + } catch ( InvalidTitleException $e ) { + try { + // Probably it is just too long. Let's try to use a shortened variant + // This is not ideal, but should be okay as a fallback in most cases. + $shortTargetTitle = basename( $containerTitle ); + $targetName = $filenameBuilder->buildFromAttachmentData( $attachmentSpaceId, $attachmentOrigFilename, $shortTargetTitle ); + } catch ( InvalidTitleException $ex ) { + $this->buckets->addData( 'title-invalids', $attachmentId, $ex->getInvalidTitle() ); + $this->logger->error( $ex->getMessage() ); + return '###INVALID###'; } + } - $mediaWikiUsername = $this->makeMWUserName( $lcUserName ); - - $this->customBuckets->addData( - 'userkey-to-username-map', - $userImplKey, - $mediaWikiUsername, - false + /* + * Some attachments do not have a file extension available. We try + * to find an extension by looking a the content type, but + * sometimes even this won't help... ("octet-stream") + */ + $file = new SplFileInfo( $targetName ); + if ( $this->hasNoExplicitFileExtension( $file ) ) { + $this->logger->debug( + "Could not find file extension for $attachmentId" ); + $targetName .= '.unknown'; + } - $this->customBuckets->addData( - 'users', - $mediaWikiUsername, - [ - 'email' => $email === null ? '' : $email - ], - false, - true - ); + $fileKey = "{$pageConfluenceTitle}---$attachmentOrigFilename"; + // Some normalization + $fileKey = str_replace( ' ', '_', $fileKey ); + $this->customBuckets->addData( 'filenames-to-filetitles-map', $fileKey, $targetName, false, true ); - $this->output->writeln( "- '$mediaWikiUsername' (ID:$userImplKey)" ); - } + return $targetName; } /** - * - * @param string $userName + * @param DOMElement $attachment * @return string */ - private function makeMWUserName( $userName ) { - // Email adresses are no valid MW usernames. We just use the first part - // While this could lead to collisions it is very unlikly - $usernameParts = explode( '@', $userName, 2 ); - $newUsername = $usernameParts[0]; - $newUsername = ucfirst( strtolower( $newUsername ) ); + private function makeAttachmentReference( XMLHelper $xmlHelper, DOMElement $attachment ) { + $basePath = $this->currentFile->getPath() . '/attachments'; + $attachmentId = $xmlHelper->getIDNodeValue( $attachment ); + $containerId = $xmlHelper->getPropertyValue( 'content', $attachment ); + if ( empty( $containerId ) ) { + $containerId = $xmlHelper->getPropertyValue( 'containerContent', $attachment ); + } + $attachmentVersion = $xmlHelper->getPropertyValue( 'attachmentVersion', $attachment ); + if ( empty( $attachmentVersion ) ) { + $attachmentVersion = $xmlHelper->getPropertyValue( 'version', $attachment ); + } - // A MW username must always be avalid page title - $titleBuilder = new GenericTitleBuilder( [] ); - $titleBuilder->appendTitleSegment( $newUsername ); + /** + * Sometimes there is no explicit version set in the "attachment" object. In such cases + * there we always fetch the highest number from the respective directory + */ + if ( empty( $attachmentVersion ) ) { + $attachmentVersion = '__LATEST__'; + } - return $titleBuilder->build(); + $path = $basePath . "/" . $containerId . '/' . $attachmentId . '/' . $attachmentVersion; + if ( !file_exists( $path ) ) { + return ''; + } + + return $path; } } diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index 408ad6f..63c09a0 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -113,7 +113,7 @@ public function __construct( $config, Workspace $workspace ) { parent::__construct( $config, $workspace ); $this->dataBuckets = new DataBuckets( [ - 'pages-ids-to-titles-map', + 'page-id-to-title-map', 'pages-titles-map', 'title-attachments', 'body-contents-to-pages-map', @@ -173,7 +173,7 @@ protected function doConvert( SplFileInfo $file ): string { } $this->currentSpace = $this->getSpaceIdFromPageId( $pageId ); - $pagesIdsToTitlesMap = $this->dataBuckets->getBucketData( 'pages-ids-to-titles-map' ); + $pagesIdsToTitlesMap = $this->dataBuckets->getBucketData( 'page-id-to-title-map' ); if ( isset( $pagesIdsToTitlesMap[$pageId] ) ) { $this->currentPageTitle = $pagesIdsToTitlesMap[$pageId]; } else { diff --git a/src/Utility/FilenameBuilder.php b/src/Utility/FilenameBuilder.php index 0228cb8..a26386d 100644 --- a/src/Utility/FilenameBuilder.php +++ b/src/Utility/FilenameBuilder.php @@ -35,6 +35,32 @@ public function __construct( $spaceIdPrefixMap, $helper ) { $this->helper = $helper; } + /** + * @param integer $spaceId + * @param string $originalFilename + * @param string $assocTitle + * @return string + */ + public function buildFromAttachmentData( int $spaceId, string $originalFilename, string $assocTitle ): string { + $this->builder = new GenericTitleBuilder( $this->spaceIdPrefixMap ); + $this->builder->setNamespace( $spaceId ); + + if ( !empty( $assocTitle ) ) { + $assocTitle = str_replace( '/', '_', $assocTitle ); + // Unset potential namespace prefix to avoid duplications + $this->builder->setNamespace( 0 ); + $this->builder->appendTitleSegment( "-{$originalFilename}" ); + $this->builder->appendTitleSegment( $assocTitle ); + } else { + $this->builder->appendTitleSegment( "{$originalFilename}" ); + } + $builtTitle = $this->builder->invertTitleSegments()->build(); + + $filename = new WindowsFilename( $builtTitle ); + + return (string)$filename; + } + /** * * @param DOMElement $attachmentNode diff --git a/src/Utility/TitleBuilder.php b/src/Utility/TitleBuilder.php index 4f88dce..aaee2e0 100644 --- a/src/Utility/TitleBuilder.php +++ b/src/Utility/TitleBuilder.php @@ -30,6 +30,16 @@ class TitleBuilder { */ private $spaceIdHomepages = []; + /** + * @var array + */ + private $pageIdParentPageIdMap = []; + + /** + * @var array + */ + private $pageIConfluenceTitledMap = []; + /** * * @var int @@ -48,9 +58,11 @@ class TitleBuilder { * @param XMLHelper $helper * @param string $mainpage */ - public function __construct( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $mainpage = 'Main_Page' ) { + public function __construct( $spaceIdPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIConfluenceTitledMap, $helper, $mainpage = 'Main_Page' ) { $this->spaceIdPrefixMap = $spaceIdPrefixMap; $this->spaceIdHomepages = $spaceIdHomepages; + $this->pageIdParentPageIdMap = $pageIdParentPageIdMap; + $this->pageIConfluenceTitledMap = $pageIConfluenceTitledMap; $this->helper = $helper; $this->mainpage = $mainpage; } @@ -119,14 +131,16 @@ private function addParentTitles( $pageNode ) { $parentPageId = null; } - while ( is_int( $parentPageId ) ) { - $parentPage = $this->helper->getObjectNodeById( $parentPageId, 'Page' ); - $parentTitle = $this->helper->getPropertyValue( 'title', $parentPage ); + if ( !is_int( $parentPageId ) ) { + return $titles; + } - $titles[] = $parentTitle; + while ( isset( $this->pageIdParentPageIdMap[$parentPageId] ) ) { + $parentTitle = $this->pageIConfluenceTitledMap[$parentPageId]; - $parentPageId = $this->helper->getPropertyValue( 'parent', $parentPage ); + $titles[] = $parentTitle; + $parentPageId = $this->pageIdParentPageIdMap[$parentPageId]; if ( $parentPageId === $this->currentTitlesSpaceHomePageId ) { $parentPageId = null; } From 6dab58d2fdd82370a3d612f7b62308e0806c7eb1 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Wed, 24 Sep 2025 11:28:26 +0200 Subject: [PATCH 17/32] Improvements of extractor --- bin/migrate-confluence | 3 + src/Analyzer/ConfluenceAnalyzer.php | 143 +++++++++--------- src/Command/Compose.php | 67 +++++++++ src/Composer/ConfluenceComposer.php | 24 +++ src/Extractor/ConfluenceExtractor.php | 204 ++++++++++++++++++++------ src/Utility/FilenameBuilder.php | 2 +- src/Utility/TitleBuilder.php | 8 +- 7 files changed, 333 insertions(+), 118 deletions(-) create mode 100644 src/Command/Compose.php diff --git a/bin/migrate-confluence b/bin/migrate-confluence index 2e8bb6c..c59ddc3 100755 --- a/bin/migrate-confluence +++ b/bin/migrate-confluence @@ -29,6 +29,9 @@ $config = [ ], 'convert' => [ 'factory' => 'HalloWelt\MigrateConfluence\Command\Convert::factory' + ], + 'compose' => [ + 'factory' => 'HalloWelt\MigrateConfluence\Command\Compose::factory' ] ] ]; diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index cdb495a..926e6cf 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -90,7 +90,6 @@ class ConfluenceAnalyzer extends AnalyzerBase implements LoggerAwareInterface, I public function __construct( $config, Workspace $workspace, DataBuckets $buckets ) { parent::__construct( $config, $workspace, $buckets ); $this->customBuckets = new DataBuckets( [ - 'space-id-to-prefix-map', 'space-key-to-prefix-map', 'space-name-to-prefix-map', @@ -100,26 +99,19 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets 'space-id-to-description-id-map', 'space-description-id-to-body-id-map', 'space-details', - 'page-id-to-confluence-title-map', 'page-id-to-parent-page-id-map', - 'body-content-id-to-page-id-map', - 'attachment-id-to-orig-filename-map', 'attachment-id-to-space-id-map', 'attachment-id-to-reference-map', - 'userkey-to-username-map', - 'pages-titles-map', 'page-id-to-confluence-key-map', 'page-id-to-title-map', 'page-id-to-space-id', 'title-files', 'attachment-orig-filename-target-filename-map', - - ] ); $this->logger = new NullLogger(); @@ -191,7 +183,7 @@ public function analyze( SplFileInfo $file ): bool { */ protected function doAnalyze( SplFileInfo $file ): bool { $xmlReader = new XMLReader(); - + // Process Space and BodyContents objects (needed by other objects) $this->output->writeln( "\nPrepare required maps:" ); @@ -212,15 +204,15 @@ protected function doAnalyze( SplFileInfo $file ): bool { $class = $xmlReader->getAttribute( 'class' ); if ( $class === 'Space' ) { $this->buildSpaceMaps( $objectDom ); - } else if ( $class === 'SpaceDescription' ) { + } elseif ( $class === 'SpaceDescription' ) { $this->buildSpaceDescriptionMap( $objectDom ); - } else if ( $class === "Page") { + } elseif ( $class === "Page" ) { $this->buildParentPageMap( $objectDom ); - } else if ( $class === "BodyContent") { + } elseif ( $class === "BodyContent" ) { $this->buildBodyContentMap( $objectDom ); - } else if ( $class === "Attachment") { + } elseif ( $class === "Attachment" ) { $this->buildAttachmentMaps( $objectDom ); - } else if ( $class === "ConfluenceUserImpl") { + } elseif ( $class === "ConfluenceUserImpl" ) { $this->buildUserMap( $objectDom ); } @@ -244,7 +236,7 @@ protected function doAnalyze( SplFileInfo $file ): bool { $objectDom = new DOMDocument(); $objectDom->loadXML( $nodeXML ); - + $class = $xmlReader->getAttribute( 'class' ); if ( $class === 'Page' ) { $this->buildPageMaps( $objectDom ); @@ -268,7 +260,7 @@ protected function doAnalyze( SplFileInfo $file ): bool { $objectDom = new DOMDocument(); $objectDom->loadXML( $nodeXML ); - + $class = $xmlReader->getAttribute( 'class' ); if ( $class === 'Attachment' ) { $this->buildTitleAttachmentsFallbackMaps( $objectDom ); @@ -292,7 +284,7 @@ protected function doAnalyze( SplFileInfo $file ): bool { $objectDom = new DOMDocument(); $objectDom->loadXML( $nodeXML ); - + $class = $xmlReader->getAttribute( 'class' ); if ( $class === 'Attachment' ) { $this->buildAdditionalFilesMap( $objectDom ); @@ -342,7 +334,7 @@ private function buildSpaceMaps( DOMDocument $dom ): void { if ( isset( $this->advancedConfig['space-prefix'][$spaceKey] ) ) { $customSpacePrefix = $this->advancedConfig['space-prefix'][$spaceKey]; - } else if ( $spaceKey !== '' ) { + } elseif ( $spaceKey !== '' ) { $customSpacePrefix = "{$spaceKey}:"; } else { return; @@ -364,7 +356,6 @@ private function buildSpaceMaps( DOMDocument $dom ): void { 'space-key-to-name-map', $spaceKey, $spaceName, false, true ); - $homePageId = -1; $homePagePropertyNode = $xmlHelper->getPropertyNode( 'homePage', $space ); if ( $homePagePropertyNode !== null ) { @@ -461,7 +452,7 @@ private function buildParentPageMap( DOMDocument $dom ): void { $pages = $xmlHelper->getObjectNodes( 'Page' ); if ( count( $pages ) < 1 ) { - + return; } $pageNode = $pages->item( 0 ); @@ -521,7 +512,7 @@ private function buildBodyContentMap( DOMDocument $dom ): void { * @param DOMDocument $dom * @return void */ - private function buildAttachmentMaps( DOMDocument $dom ):void { + private function buildAttachmentMaps( DOMDocument $dom ): void { $xmlHelper = new XMLHelper( $dom ); $attachmentNodes = $xmlHelper->getObjectNodes( 'Attachment' ); @@ -540,15 +531,18 @@ private function buildAttachmentMaps( DOMDocument $dom ):void { } if ( $attachmentFilename !== '' && is_int( $attachmentId ) ) { - $this->customBuckets->addData( 'attachment-id-to-orig-filename-map', $attachmentId, $attachmentFilename, false, true ); + $this->customBuckets->addData( + 'attachment-id-to-orig-filename-map', $attachmentId, $attachmentFilename, false, true ); } $attachmentSpaceId = $xmlHelper->getPropertyValue( 'space', $attachmentNode ); if ( is_int( $attachmentId ) ) { - $this->customBuckets->addData( 'attachment-id-to-space-id-map', $attachmentId, $attachmentSpaceId, false, true ); + $this->customBuckets->addData( + 'attachment-id-to-space-id-map', $attachmentId, $attachmentSpaceId, false, true ); } $attachmentReference = $this->makeAttachmentReference( $xmlHelper, $attachmentNode ); if ( $attachmentReference !== '' ) { - $this->customBuckets->addData( 'attachment-id-to-reference-map', $attachmentId, $attachmentReference, false, true ); + $this->customBuckets->addData( + 'attachment-id-to-reference-map', $attachmentId, $attachmentReference, false, true ); } } @@ -602,14 +596,6 @@ private function buildUserMap( DOMDocument $dom ): void { /** * @param DOMDocument $dom - * @param array $spaceIdPrefixMap - * @param array $spaceIdHomepages - * @param array $pageIdParentPageIdMap - * @param array $pageIConfluenceTitledMap - * @param array $bodyContents - * @param array $attachmentIdToOrigFilenameMap - * @param array $attachmentIdToSpaceIdMap - * @param string $mainpage * @return void */ private function buildPageMaps( DOMDocument $dom ): void { @@ -623,10 +609,10 @@ private function buildPageMaps( DOMDocument $dom ): void { $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); $xmlHelper = new XMLHelper( $dom ); - + $pages = $xmlHelper->getObjectNodes( 'Page' ); if ( count( $pages ) < 1 ) { - + return; } $pageNode = $pages->item( 0 ); @@ -647,8 +633,11 @@ private function buildPageMaps( DOMDocument $dom ): void { } $pageId = $xmlHelper->getIDNodeValue( $pageNode ); - - $titleBuilder = new TitleBuilder( $spaceIdToPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIdConfluendTitleMap, $xmlHelper, $this->mainpage ); + + $titleBuilder = new TitleBuilder( + $spaceIdToPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, + $pageIdConfluendTitleMap, $xmlHelper, $this->mainpage + ); try { $targetTitle = $titleBuilder->buildTitle( $pageNode ); $this->output->writeln( $targetTitle ); @@ -720,7 +709,7 @@ private function buildPageMaps( DOMDocument $dom ): void { $attachmentId = $xmlHelper->getIDNodeValue( $attachmentRef ); if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { continue; - }; + } $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { @@ -728,9 +717,11 @@ private function buildPageMaps( DOMDocument $dom ): void { } else { $attachmentSpaceId = $spaceId; } - + $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( - $pageConfluenceTitle, $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, $targetTitle, $spaceIdToPrefixMap ); + $pageConfluenceTitle, $attachmentId, $attachmentSpaceId, + $attachmentOrigFilename, $targetTitle, $spaceIdToPrefixMap + ); if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { $this->output->writeln( @@ -745,9 +736,9 @@ private function buildPageMaps( DOMDocument $dom ): void { true ); continue; - }; + } $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; - + // In case of ERM34465 no files are added to title-attachments $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); $this->addFile( $attachmentTargetFilename, $attachmentReference ); @@ -763,12 +754,10 @@ private function buildPageMaps( DOMDocument $dom ): void { } /** - * @param array $spaceIdHomepages - * @param array $bodyContents - * @param string $mainpage + * @param DOMDocument $dom * @return void */ - private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { + private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); @@ -804,19 +793,23 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { } if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { return; - }; + } $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; } else { - $attachmentSpaceId = 0; // TODO: Is this wise? + // TODO: Is this wise? + $attachmentSpaceId = 0; } $confluenceKey = ''; if ( isset( $pageIdToConfluenceKey[$containerContentId] ) ) { $confluenceKey = $pageIdToConfluenceKey[$containerContentId]; } - $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( $confluenceKey, $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, $targetTitle, $spaceIdPrefixMap ); + $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( + $confluenceKey, $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, + $targetTitle, $spaceIdPrefixMap + ); if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { $this->output->writeln( //phpcs:ignore Generic.Files.LineLength.TooLong @@ -830,14 +823,13 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { true ); return; - }; + } $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; $this->output->writeln( "Add attachment $attachmentTargetFilename (fallback)" ); $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); $this->addFile( $attachmentTargetFilename, $attachmentReference ); $this->addedAttachmentIds[$attachmentId] = true; - $this->customBuckets->addData( 'attachment-orig-filename-target-filename-map', $attachmentOrigFilename, @@ -847,26 +839,15 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { /** * @param DOMDocument $dom - * @param array $spaceIdPrefixMap * @return void */ private function buildAdditionalFilesMap( DOMDocument $dom ) { - $titleAttachmentsMap = $this->buckets->getBucketData( 'title-attachments' ); $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); - $spaceIdHomepages = $this->customBuckets->getBucketData( 'space-id-homepages' ); - $pageIdParentPageIdMap = $this->customBuckets->getBucketData( 'page-id-to-parent-page-id-map' ); - $pageIdConfluendTitleMap = $this->customBuckets->getBucketData( 'page-id-to-confluence-title-map' ); - $bodyContents = $this->customBuckets->getBucketData( 'body-content-id-to-page-id-map' ); - $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); - $pageIdToTitleMap = $this->customBuckets->getBucketData( 'page-id-to-title-map' ); - $pageIdConflunceKey = $this->customBuckets->getBucketData( 'page-id-to-confluence-key-map' ); - $titleAttachmentsMap = $this->buckets->getBucketData( 'title-attachments' ); $xmlHelper = new XMLHelper( $dom ); - $this->output->writeln( "\nFinding attachments" ); $attachments = $xmlHelper->getObjectNodes( 'Attachment' ); foreach ( $attachments as $attachment ) { if ( $attachment instanceof DOMElement === false ) { @@ -893,30 +874,37 @@ private function buildAdditionalFilesMap( DOMDocument $dom ) { if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { continue; - }; + } $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; - $attachmetFilenameMap = $this->customBuckets->getBucketData( 'attachment-orig-filename-target-filename-map' ); + $attachmetFilenameMap = $this->customBuckets->getBucketData( + 'attachment-orig-filename-target-filename-map' ); if ( isset( $attachmetFilenameMap[$attachmentOrigFilename] ) ) { continue; - }; + } if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; } else { - $attachmentSpaceId = 0; // TODO: Is this wise? + // TODO: Is this wise? + $attachmentSpaceId = 0; } if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { continue; } $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; - $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( '', $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, '', $spaceIdPrefixMap ); + $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( + '', $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, '', $spaceIdPrefixMap ); $this->output->writeln( "- '$attachmentTargetFilename'" ); $this->addFile( $attachmentTargetFilename, $attachmentReference ); - $this->customBuckets->addData( 'debug-additional-files', $attachmentTargetFilename, $attachmentReference, false, true ); - $this->customBuckets->addData( 'attachment-orig-filename-target-filename-map', $attachmentOrigFilename, $attachmentTargetFilename ); + $this->customBuckets->addData( + 'debug-additional-files', $attachmentTargetFilename, $attachmentReference, false, true ); + $this->customBuckets->addData( + 'attachment-orig-filename-target-filename-map', $attachmentOrigFilename, $attachmentTargetFilename ); $this->addedAttachmentIds[$attachmentId] = true; + + $this->output->writeln( "\nAdd additional file $attachmentTargetFilename" ); } } @@ -995,23 +983,29 @@ private function hasNoExplicitFileExtension( $file ) { } /** - * @param integer $attachmentId - * @param integer $attachmentSpaceId + * @param string $pageConfluenceTitle + * @param int $attachmentId + * @param int $attachmentSpaceId * @param string $attachmentOrigFilename * @param string $containerTitle * @param array $spaceIdToPrefixMap * @return string */ - private function makeAttachmentTargetFilenameFromData( string $pageConfluenceTitle, int $attachmentId, int $attachmentSpaceId, string $attachmentOrigFilename, string $containerTitle, array $spaceIdToPrefixMap ): string { + private function makeAttachmentTargetFilenameFromData( + string $pageConfluenceTitle, int $attachmentId, int $attachmentSpaceId, + string $attachmentOrigFilename, string $containerTitle, array $spaceIdToPrefixMap + ): string { $filenameBuilder = new FilenameBuilder( $spaceIdToPrefixMap, null ); try { - $targetName = $filenameBuilder->buildFromAttachmentData( $attachmentSpaceId, $attachmentOrigFilename, $containerTitle ); + $targetName = $filenameBuilder->buildFromAttachmentData( + $attachmentSpaceId, $attachmentOrigFilename, $containerTitle ); } catch ( InvalidTitleException $e ) { try { // Probably it is just too long. Let's try to use a shortened variant // This is not ideal, but should be okay as a fallback in most cases. $shortTargetTitle = basename( $containerTitle ); - $targetName = $filenameBuilder->buildFromAttachmentData( $attachmentSpaceId, $attachmentOrigFilename, $shortTargetTitle ); + $targetName = $filenameBuilder->buildFromAttachmentData( + $attachmentSpaceId, $attachmentOrigFilename, $shortTargetTitle ); } catch ( InvalidTitleException $ex ) { $this->buckets->addData( 'title-invalids', $attachmentId, $ex->getInvalidTitle() ); $this->logger->error( $ex->getMessage() ); @@ -1041,8 +1035,9 @@ private function makeAttachmentTargetFilenameFromData( string $pageConfluenceTi } /** + * @param XMLHelper $xmlHelper * @param DOMElement $attachment - * @return string + * @return void */ private function makeAttachmentReference( XMLHelper $xmlHelper, DOMElement $attachment ) { $basePath = $this->currentFile->getPath() . '/attachments'; diff --git a/src/Command/Compose.php b/src/Command/Compose.php new file mode 100644 index 0000000..5047614 --- /dev/null +++ b/src/Command/Compose.php @@ -0,0 +1,67 @@ +getDefinition(); + $definition->addOption( + new InputOption( + 'config', + null, + InputOption::VALUE_REQUIRED, + 'Specifies the path to the config yaml file' + ) + ); + + return $config; + } + + /** + * @param array $config + * @return Compose + */ + public static function factory( $config ): Compose { + return new static( $config ); + } + + /** + * @return bool + */ + protected function doProcessFile(): bool { + $this->readConfigFile( $this->config ); + return parent::doProcessFile(); + } + + /** + * @param array &$config + * @return void + */ + private function readConfigFile( &$config ): void { + $filename = $this->input->getOption( 'config' ); + if ( is_file( $filename ) ) { + $content = file_get_contents( $filename ); + if ( $content ) { + try { + $yaml = Yaml::parse( $content ); + $config = array_merge( $config, $yaml ); + } catch ( ParseException $e ) { + } + } + } + } +} diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index 3bbb4b5..30ea9ce 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -29,6 +29,9 @@ class ConfluenceComposer extends ComposerBase implements IOutputAwareInterface { */ private $output = null; + /** @var array */ + private $advancedConfig = []; + /** * @param array $config * @param Workspace $workspace @@ -53,6 +56,10 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets ] ); $this->dataBuckets->loadFromWorkspace( $this->workspace ); + + if ( isset( $this->config['config'] ) ) { + $this->advancedConfig = $this->config['config']; + } } /** @@ -130,6 +137,11 @@ public function buildXML( Builder $builder ) { } } + $namespace = $this->getNamespace( $pageTitle ); + if ( isset( $this->advancedConfig['skipNamespace'][$namespace] ) ) { + $this->output->writeln( "Page {$pageTitle} skipped by configuration 'skipNamespace' ($namespace)" ); + continue; + } $builder->addRevision( $pageTitle, $pageContent, $timestamp ); // Append attachments @@ -163,6 +175,18 @@ public function buildXML( Builder $builder ) { $this->customBuckets->saveToWorkspace( $this->workspace ); } + /** + * @param string $title + * @return string + */ + private function getNamespace( string $title ): string { + $collonPos = strpos( $title, ':' ); + if ( !$collonPos ) { + return ''; + } + return substr( $title, 0, $collonPos ); + } + /** * @param Builder $builder * @return void diff --git a/src/Extractor/ConfluenceExtractor.php b/src/Extractor/ConfluenceExtractor.php index 5c8d19c..b8bb1ab 100644 --- a/src/Extractor/ConfluenceExtractor.php +++ b/src/Extractor/ConfluenceExtractor.php @@ -4,97 +4,219 @@ use DOMDocument; use DOMElement; +use HalloWelt\MediaWiki\Lib\Migration\DataBuckets; use HalloWelt\MediaWiki\Lib\Migration\ExtractorBase; +use HalloWelt\MediaWiki\Lib\Migration\Workspace; use HalloWelt\MigrateConfluence\Utility\XMLHelper; use SplFileInfo; +use XMLReader; class ConfluenceExtractor extends ExtractorBase { /** - * - * @var DOMDocument + * @var DataBuckets */ - private $dom = null; + private $customBuckets = null; /** - * @var XMLHelper + * @var array */ - private $helper = null; + private $categories = []; /** - * @var array + * @param array $config + * @param Workspace $workspace + * @param DataBuckets $buckets */ - private $categories = []; + public function __construct( $config, Workspace $workspace, DataBuckets $buckets ) { + parent::__construct( $config, $workspace, $buckets ); + $this->customBuckets = new DataBuckets( [ + 'labelling-id-to-label-id-map', + 'label-id-to-name-map', + ] ); + } /** * @param SplFileInfo $file * @return bool */ protected function doExtract( SplFileInfo $file ): bool { - $this->dom = new DOMDocument(); - $this->dom->load( $file->getPathname() ); - $this->helper = new XMLHelper( $this->dom ); + $this->customBuckets->loadFromWorkspace( $this->workspace ); if ( isset( $this->config['config']['categories'] ) ) { $this->categories = $this->config['config']['categories']; } - $this->extractBodyContents(); - $this->extractPageMetaData(); + $xmlReader = new XMLReader(); + + $xmlReader->open( $file->getPathname() ); + $read = $xmlReader->read(); + while ( $read ) { + if ( $xmlReader->name !== 'object' ) { + // Usually all root nodes should be objects. + $read = $xmlReader->read(); + continue; + } + + $objectXML = $xmlReader->readOuterXml(); + + $objectDom = new DOMDocument(); + $objectDom->loadXML( $objectXML ); + + $class = $xmlReader->getAttribute( 'class' ); + if ( $class === 'BodyContent' ) { + $this->extractBodyContents( $objectDom ); + } elseif ( $class === "Labelling" ) { + $this->buildLabellingMap( $objectDom ); + } elseif ( $class === "Label" ) { + $this->buildLabelMap( $objectDom ); + } + + $read = $xmlReader->next(); + } + $xmlReader->close(); + + $xmlReader->open( $file->getPathname() ); + $read = $xmlReader->read(); + while ( $read ) { + if ( $xmlReader->name !== 'object' ) { + // Usually all root nodes should be objects. + $read = $xmlReader->read(); + continue; + } + + $objectXML = $xmlReader->readOuterXml(); + + $objectDom = new DOMDocument(); + $objectDom->loadXML( $objectXML ); + + $class = $xmlReader->getAttribute( 'class' ); + if ( $class === 'Page' ) { + $this->extractPageMetaData( $objectDom ); + } + + $read = $xmlReader->next(); + } + $xmlReader->close(); + + $this->customBuckets->saveToWorkspace( $this->workspace ); return true; } - private function extractBodyContents() { - $bodyContents = $this->helper->getObjectNodes( 'BodyContent' ); + /** + * @param DOMDocument $dom + * @return void + */ + private function extractBodyContents( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); + + $bodyContents = $xmlHelper->getObjectNodes( 'BodyContent' ); foreach ( $bodyContents as $bodyContent ) { - $id = $this->helper->getIDNodeValue( $bodyContent ); - $bodyContentHTML = $this->getBodyContentHTML( $bodyContent ); + $id = $xmlHelper->getIDNodeValue( $bodyContent ); + $bodyContentHTML = $this->getBodyContentHTML( $xmlHelper, $bodyContent ); $targetFileName = $this->workspace->saveRawContent( $id, $bodyContentHTML ); $this->addRevisionContent( $id, $targetFileName ); } } /** - * + * @param DOMDocument $dom + * @return void + */ + private function buildLabellingMap( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); + + $labellingObjs = $xmlHelper->getObjectNodes( 'Labelling' ); + if ( count( $labellingObjs ) < 1 ) { + return; + } + $labelling = $labellingObjs->item( 0 ); + if ( $labelling instanceof DOMElement === false ) { + return; + } + + $id = $xmlHelper->getIDNodeValue( $labelling ); + + $labelProp = $xmlHelper->getPropertyNode( 'label', $labelling ); + $labelId = $xmlHelper->getIDNodeValue( $labelProp ); + if ( isset( $labelMap[$labelId] ) ) { + $categories[] = $labelMap[$labelId]; + } + + $this->customBuckets->addData( 'labelling-id-to-label-id-map', $id, $labelId, false, true ); + } + + /** + * @param DOMDocument $dom + * @return void + */ + private function buildLabelMap( DOMDocument $dom ): void { + $xmlHelper = new XMLHelper( $dom ); + + $labelObjs = $xmlHelper->getObjectNodes( 'Label' ); + if ( count( $labelObjs ) < 1 ) { + return; + } + $label = $labelObjs->item( 0 ); + if ( $label instanceof DOMElement === false ) { + return; + } + + $labelNamespace = $xmlHelper->getPropertyValue( 'namespace', $label ); + // There may be `my` or `team` also + if ( $labelNamespace !== 'global' ) { + return; + } + + $id = $xmlHelper->getIDNodeValue( $label ); + $name = $xmlHelper->getPropertyValue( 'name', $label ); + + $this->customBuckets->addData( 'label-id-to-name-map', $id, $name, false, true ); + } + + /** + * @param XMLHelper $xmlHelper * @param DOMElement $bodyContent - * @return string + * @return void */ - private function getBodyContentHTML( DOMElement $bodyContent ) { - $rawValue = $this->helper->getPropertyValue( 'body', $bodyContent ); + private function getBodyContentHTML( XMLHelper $xmlHelper, DOMElement $bodyContent ) { + $rawValue = $xmlHelper->getPropertyValue( 'body', $bodyContent ); // For a strange reason the CDATA blocks are not closed properly... $fixedValue = str_replace( ']] >', ']]>', $rawValue ); return '' . $fixedValue . ''; } - private function extractPageMetaData() { - $labels = $this->helper->getObjectNodes( 'Label' ); - $labelMap = []; - foreach ( $labels as $label ) { - $id = $this->helper->getIDNodeValue( $label ); - $labelValue = $this->helper->getPropertyValue( 'name', $label ); - $labelNamespace = $this->helper->getPropertyValue( 'namespace', $label ); + /** + * @param DOMDocument $dom + * @return void + */ + private function extractPageMetaData( DOMDocument $dom ) { + $labellingMap = $this->customBuckets->getBucketData( 'labelling-id-to-label-id-map' ); + $labelMap = $this->customBuckets->getBucketData( 'label-id-to-name-map' ); - // There may be `my` or `team` also - if ( $labelNamespace !== 'global' ) { - continue; - } + $xmlHelper = new XMLHelper( $dom ); - $labelMap[$id] = $labelValue; + $pageObjs = $xmlHelper->getObjectNodes( 'Page' ); + if ( count( $pageObjs ) < 1 ) { + return; } - $pages = $this->helper->getObjectNodes( 'Page' ); - foreach ( $pages as $page ) { - $id = $this->helper->getIDNodeValue( $page ); + foreach ( $pageObjs as $page ) { + if ( $page instanceof DOMElement === false ) { + continue; + } + $id = $xmlHelper->getIDNodeValue( $page ); // Currently we only extract "Categories" $categories = []; - $labellingEls = $this->helper->getElementsFromCollection( 'labellings', $page ); + $labellingEls = $xmlHelper->getElementsFromCollection( 'labellings', $page ); foreach ( $labellingEls as $labellingEl ) { - $labellingId = $this->helper->getIDNodeValue( $labellingEl ); - $labelling = $this->helper->getObjectNodeById( $labellingId, 'Labelling' ); - $labelProp = $this->helper->getPropertyNode( 'label', $labelling ); - $labelId = $this->helper->getIDNodeValue( $labelProp ); + $labellingId = $xmlHelper->getIDNodeValue( $labellingEl ); + if ( !isset( $labellingMap[$labellingId] ) ) { + continue; + } + $labelId = $labellingMap[$labellingId]; if ( isset( $labelMap[$labelId] ) ) { $categories[] = $labelMap[$labelId]; } @@ -106,7 +228,7 @@ private function extractPageMetaData() { 'categories' => $categories ]; - $this->addTitleMetaData( $id, $meta ); + $this->buckets->addData( 'title-metadata', $id, $meta, false ); } } } diff --git a/src/Utility/FilenameBuilder.php b/src/Utility/FilenameBuilder.php index a26386d..3422490 100644 --- a/src/Utility/FilenameBuilder.php +++ b/src/Utility/FilenameBuilder.php @@ -36,7 +36,7 @@ public function __construct( $spaceIdPrefixMap, $helper ) { } /** - * @param integer $spaceId + * @param int $spaceId * @param string $originalFilename * @param string $assocTitle * @return string diff --git a/src/Utility/TitleBuilder.php b/src/Utility/TitleBuilder.php index aaee2e0..2ee4cf8 100644 --- a/src/Utility/TitleBuilder.php +++ b/src/Utility/TitleBuilder.php @@ -52,13 +52,17 @@ class TitleBuilder { private $mainpage = ''; /** - * * @param array $spaceIdPrefixMap * @param array $spaceIdHomepages + * @param array $pageIdParentPageIdMap + * @param array $pageIConfluenceTitledMap * @param XMLHelper $helper * @param string $mainpage */ - public function __construct( $spaceIdPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIConfluenceTitledMap, $helper, $mainpage = 'Main_Page' ) { + public function __construct( + array $spaceIdPrefixMap, array $spaceIdHomepages, array $pageIdParentPageIdMap, + array $pageIConfluenceTitledMap, XMLHelper $helper, string $mainpage = 'Main_Page' + ) { $this->spaceIdPrefixMap = $spaceIdPrefixMap; $this->spaceIdHomepages = $spaceIdHomepages; $this->pageIdParentPageIdMap = $pageIdParentPageIdMap; From f5fd7c64f471cc351c205111118595e0bf27fe9e Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Wed, 1 Oct 2025 09:06:59 +0200 Subject: [PATCH 18/32] Resolve attachment issues --- src/Analyzer/ConfluenceAnalyzer.php | 288 ++++++++---------- src/Composer/ConfluenceComposer.php | 11 +- src/Converter/ConfluenceConverter.php | 3 +- src/Converter/Processor/Image.php | 8 + .../Processor/StructuredMacroDrawio.php | 4 + src/Utility/ConversionDataLookup.php | 21 +- 6 files changed, 175 insertions(+), 160 deletions(-) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 926e6cf..1c4eae1 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -55,7 +55,11 @@ class ConfluenceAnalyzer extends AnalyzerBase implements LoggerAwareInterface, I private $output = null; /** - * + * @var array + */ + private $availableAttachmentIds = []; + + /** * @var array */ private $addedAttachmentIds = []; @@ -105,13 +109,24 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets 'attachment-id-to-orig-filename-map', 'attachment-id-to-space-id-map', 'attachment-id-to-reference-map', + 'attachment-id-to-container-content-id-map', + 'attachment-id-to-content-status-map', 'userkey-to-username-map', 'pages-titles-map', 'page-id-to-confluence-key-map', 'page-id-to-title-map', 'page-id-to-space-id', 'title-files', + 'additional-files', 'attachment-orig-filename-target-filename-map', + 'attachment-id-to-target-filename-map', + 'attachment-confluence-file-key-to-target-filename-map', + + 'debug-attachment-id-to-target-filename', + 'debug-missing-attachment-id-to-filename', + 'debug-attachment-page-to-attachment-id', + 'debug-fallback-attachment-id-to-target-filename', + 'debug-additional-attachment-id-to-target-filename', ] ); $this->logger = new NullLogger(); @@ -270,31 +285,6 @@ protected function doAnalyze( SplFileInfo $file ): bool { } $xmlReader->close(); - // Process additional attachments - $xmlReader->open( $file->getPathname() ); - $read = $xmlReader->read(); - while ( $read ) { - if ( $xmlReader->name !== 'object' ) { - // Usually all root nodes should be objects. - $read = $xmlReader->read(); - continue; - } - - $nodeXML = $xmlReader->readOuterXml(); - - $objectDom = new DOMDocument(); - $objectDom->loadXML( $nodeXML ); - - $class = $xmlReader->getAttribute( 'class' ); - if ( $class === 'Attachment' ) { - $this->buildAdditionalFilesMap( $objectDom ); - } - - $read = $xmlReader->next(); - } - - $xmlReader->close(); - return true; } @@ -457,7 +447,6 @@ private function buildParentPageMap( DOMDocument $dom ): void { } $pageNode = $pages->item( 0 ); if ( $pageNode instanceof DOMElement === false ) { - $this->output->writeln( __LINE__ ); return; } $status = $xmlHelper->getPropertyValue( 'contentStatus', $pageNode ); @@ -470,7 +459,6 @@ private function buildParentPageMap( DOMDocument $dom ): void { } $originalVersionID = $xmlHelper->getPropertyValue( 'originalVersion', $pageNode ); if ( $originalVersionID !== null ) { - $this->output->writeln( __LINE__ ); return; } @@ -503,9 +491,9 @@ private function buildBodyContentMap( DOMDocument $dom ): void { } $bodyContentId = $xmlHelper->getIDNodeValue( $bodyContentObject ); - $contentPageId = $xmlHelper->getPropertyValue( 'content', $bodyContentObject ); + $pageId = $xmlHelper->getPropertyValue( 'content', $bodyContentObject ); $this->customBuckets->addData( 'body-content-id-to-page-id-map', - $bodyContentId, $contentPageId, false, true ); + $bodyContentId, $pageId, false, true ); } /** @@ -525,6 +513,11 @@ private function buildAttachmentMaps( DOMDocument $dom ): void { } $attachmentId = $xmlHelper->getIDNodeValue( $attachmentNode ); + if ( $attachmentId < 0 ) { + return; + } + $this->availableAttachmentIds[] = $attachmentId; + $attachmentFilename = $xmlHelper->getPropertyValue( 'fileName', $attachmentNode ); if ( $attachmentFilename === null ) { $attachmentFilename = $xmlHelper->getPropertyValue( 'title', $attachmentNode ); @@ -544,6 +537,17 @@ private function buildAttachmentMaps( DOMDocument $dom ): void { $this->customBuckets->addData( 'attachment-id-to-reference-map', $attachmentId, $attachmentReference, false, true ); } + $containerContent = $xmlHelper->getPropertyNode( 'containerContent', $attachmentNode ); + if ( $containerContent instanceof DOMElement ) { + $containerContentId = $xmlHelper->getIDNodeValue( $containerContent ); + if ( $containerContentId >= 0 ) { + $this->customBuckets->addData( + 'attachment-id-to-container-content-id-map', $attachmentId, $containerContentId, false, true ); + } + } + $attachmentNodeContentStatus = $xmlHelper->getPropertyValue( 'contentStatus', $attachmentNode ); + $this->customBuckets->addData( + 'attachment-id-to-content-status-map', $attachmentId, $attachmentNodeContentStatus, false, true ); } /** @@ -604,9 +608,6 @@ private function buildPageMaps( DOMDocument $dom ): void { $pageIdParentPageIdMap = $this->customBuckets->getBucketData( 'page-id-to-parent-page-id-map' ); $pageIdConfluendTitleMap = $this->customBuckets->getBucketData( 'page-id-to-confluence-title-map' ); $bodyContents = $this->customBuckets->getBucketData( 'body-content-id-to-page-id-map' ); - $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); - $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); - $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); $xmlHelper = new XMLHelper( $dom ); @@ -640,7 +641,6 @@ private function buildPageMaps( DOMDocument $dom ): void { ); try { $targetTitle = $titleBuilder->buildTitle( $pageNode ); - $this->output->writeln( $targetTitle ); } catch ( InvalidTitleException $ex ) { $this->buckets->addData( 'title-invalids', $pageId, $ex->getInvalidTitle() ); return; @@ -702,48 +702,77 @@ private function buildPageMaps( DOMDocument $dom ): void { $this->addTitleRevision( $targetTitle, implode( '/', $bodyContentIds ) . "@$version-$revisionTimestamp" ); + // Find attachments + + $this->getAttachmentsFromCollection( $xmlHelper, $pageNode, $spaceId ); + } + + /** + * @param XMLHelper $xmlHelper + * @param DOMElement $element + * @param int $spaceId + * @return void + */ + private function getAttachmentsFromCollection( XMLHelper $xmlHelper, DOMElement $element, int $spaceId ): void { + $pageIdConflueTitleMap = $this->customBuckets->getBucketData( 'page-id-to-confluence-title-map' ); + $pageIdConfluenKeyMap = $this->customBuckets->getBucketData( 'page-id-to-confluence-key-map' ); + $pagesTitlesMap = $this->customBuckets->getBucketData( 'pages-titles-map' ); + $spaceIdToPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); + $attachmentIdToOrigFilenameMap = $this->customBuckets->getBucketData( 'attachment-id-to-orig-filename-map' ); + $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); + $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); + + $pageId = $xmlHelper->getIDNodeValue( $element ); + $confluenceTitle = $pageIdConflueTitleMap[$pageId]; + $confluenceKey = $pageIdConfluenKeyMap[$pageId]; + $wikiTitle = $pagesTitlesMap[$confluenceKey]; + // In case of ERM34465 this seems to be empty because // title-attachments and debug-missing-attachment-id-to-filename are empty - $attachmentRefs = $xmlHelper->getElementsFromCollection( 'attachments', $pageNode ); + $attachmentRefs = $xmlHelper->getElementsFromCollection( 'attachments', $element ); foreach ( $attachmentRefs as $attachmentRef ) { $attachmentId = $xmlHelper->getIDNodeValue( $attachmentRef ); + if ( in_array( $attachmentId, $this->addedAttachmentIds ) ) { + continue; + } if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { continue; } $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; - if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; } else { $attachmentSpaceId = $spaceId; } - $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( - $pageConfluenceTitle, $attachmentId, $attachmentSpaceId, - $attachmentOrigFilename, $targetTitle, $spaceIdToPrefixMap + $confluenceTitle, $attachmentId, $attachmentSpaceId, + $attachmentOrigFilename, $wikiTitle, $spaceIdToPrefixMap ); - if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { - $this->output->writeln( - //phpcs:ignore Generic.Files.LineLength.TooLong - "\033[31m\t- File '$attachmentId' ($attachmentTargetFilename) not found\033[39m" - ); - $this->customBuckets->addData( - 'debug-missing-attachment-id-to-filename', - $attachmentId, - $attachmentTargetFilename, - false, - true - ); continue; } $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; // In case of ERM34465 no files are added to title-attachments - $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); + $this->addTitleAttachment( $wikiTitle, $attachmentTargetFilename ); $this->addFile( $attachmentTargetFilename, $attachmentReference ); - $this->customBuckets->addData( 'title-files', $targetTitle, $attachmentTargetFilename, false, true ); - $this->addedAttachmentIds[$attachmentId] = true; + $this->customBuckets->addData( 'title-files', $wikiTitle, $attachmentTargetFilename, false, true ); + $this->addedAttachmentIds[] = $attachmentId; + + $confluenceFileKey = str_replace( ' ', '_', "{$spaceId}---{$confluenceTitle}---{$attachmentOrigFilename}" ); + $this->customBuckets->addData( + 'attachment-confluence-file-key-to-target-filename-map', + $confluenceFileKey, + $attachmentTargetFilename, + false, + true + ); + + $this->customBuckets->addData( + 'attachment-id-to-target-filename-map', + $attachmentId, + $attachmentTargetFilename + ); $this->customBuckets->addData( 'attachment-orig-filename-target-filename-map', @@ -763,7 +792,7 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); $pageIdToTitleMap = $this->customBuckets->getBucketData( 'page-id-to-title-map' ); - $titleAttachmentsMap = $this->buckets->getBucketData( 'title-attachments' ); + $pageIdToConfluenceKey = $this->customBuckets->getBucketData( 'page-id-to-confluence-key-map' ); $xmlHelper = new XMLHelper( $dom ); @@ -775,60 +804,82 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { if ( $attachmentNode instanceof DOMElement === false ) { return; } - if ( !empty( $titleAttachmentsMap ) ) { + $attachmentNodeContentStatus = $xmlHelper->getPropertyValue( 'contentStatus', $attachmentNode ); + if ( strtolower( $attachmentNodeContentStatus ) !== 'current' ) { return; } $attachmentId = $xmlHelper->getIDNodeValue( $attachmentNode ); - $containerContent = $xmlHelper->getPropertyNode( 'containerContent', $attachmentNode ); - $containerContentId = $xmlHelper->getIDNodeValue( $containerContent ); - - if ( !isset( $pageIdToTitleMap[$containerContentId] ) ) { - $targetTitle = ''; - } else { - $targetTitle = $pageIdToTitleMap[$containerContentId]; + if ( in_array( $attachmentId, $this->addedAttachmentIds ) ) { + return; } - $attachmentNodeContentStatus = $xmlHelper->getPropertyValue( 'contentStatus', $attachmentNode ); - if ( strtolower( $attachmentNodeContentStatus ) !== 'current' ) { + if ( !in_array( $attachmentId, $this->availableAttachmentIds ) ) { return; } if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { return; } $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; - if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { - $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; - } else { - // TODO: Is this wise? - $attachmentSpaceId = 0; - } + // Check to which page attachment belongs + $targetTitle = ''; $confluenceKey = ''; - if ( isset( $pageIdToConfluenceKey[$containerContentId] ) ) { - $confluenceKey = $pageIdToConfluenceKey[$containerContentId]; + $containerContentId = $xmlHelper->getPropertyValue( 'containerContent', $attachmentNode ); + if ( $containerContentId !== null ) { + if ( isset( $pageIdToTitleMap[$containerContentId] ) ) { + $targetTitle = $pageIdToTitleMap[$containerContentId]; + } + if ( isset( $pageIdToConfluenceKey[$containerContentId] ) ) { + $confluenceKey = $pageIdToConfluenceKey[$containerContentId]; + } else { + return; + } + } + // TODO: Is this wise? + $attachmentSpaceId = 0; + if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { + $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; } $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( $confluenceKey, $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, $targetTitle, $spaceIdPrefixMap ); + if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { $this->output->writeln( //phpcs:ignore Generic.Files.LineLength.TooLong "\033[31m\t- File '$attachmentId' ($attachmentTargetFilename) not found\033[39m" ); - $this->customBuckets->addData( - 'debug-missing-attachment-id-to-filename', - $attachmentId, - $attachmentTargetFilename, - false, - true - ); return; } + $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; - $this->output->writeln( "Add attachment $attachmentTargetFilename (fallback)" ); - $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); + + if ( $confluenceKey !== '' ) { + $this->addTitleAttachment( $targetTitle, $attachmentTargetFilename ); + $this->output->writeln( "Add attachment $attachmentTargetFilename (fallback: {$confluenceKey})" ); + } else { + $this->customBuckets->addData( + 'additional-files', $attachmentTargetFilename, $attachmentReference, false, true ); + $this->output->writeln( "Add attachment $attachmentTargetFilename (additional)" ); + } + $this->addFile( $attachmentTargetFilename, $attachmentReference ); - $this->addedAttachmentIds[$attachmentId] = true; + $this->addedAttachmentIds[] = $attachmentId; + + $confluenceFileKey = str_replace( ' ', '', "{$confluenceKey}---{$attachmentOrigFilename}" ); + $this->customBuckets->addData( + 'attachment-confluence-file-key-to-target-filename-map', + $confluenceFileKey, + $attachmentTargetFilename, + false, + true + ); + + $this->customBuckets->addData( + 'attachment-id-to-target-filename-map', + $attachmentId, + $attachmentTargetFilename + ); $this->customBuckets->addData( 'attachment-orig-filename-target-filename-map', @@ -837,77 +888,6 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { ); } - /** - * @param DOMDocument $dom - * @return void - */ - private function buildAdditionalFilesMap( DOMDocument $dom ) { - $spaceIdPrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); - $attachmentIdToReferenceMap = $this->customBuckets->getBucketData( 'attachment-id-to-reference-map' ); - $attachmentIdToSpaceIdMap = $this->customBuckets->getBucketData( 'attachment-id-to-space-id-map' ); - - $xmlHelper = new XMLHelper( $dom ); - - $attachments = $xmlHelper->getObjectNodes( 'Attachment' ); - foreach ( $attachments as $attachment ) { - if ( $attachment instanceof DOMElement === false ) { - continue; - } - $originalVersionID = $xmlHelper->getPropertyValue( 'originalVersion', $attachment ); - - // Skip legacy versions - if ( $originalVersionID !== null ) { - continue; - } - - $sourceContentID = $xmlHelper->getPropertyValue( 'sourceContent', $attachment ); - if ( !empty( $sourceContentID ) ) { - // This has already been added as a page attachment - continue; - } - - $attachmentId = $xmlHelper->getIDNodeValue( $attachment ); - if ( isset( $this->addedAttachmentIds[$attachmentId] ) ) { - // This has already been added as a page attachment - continue; - } - - if ( !isset( $attachmentIdToOrigFilenameMap[$attachmentId] ) ) { - continue; - } - $attachmentOrigFilename = $attachmentIdToOrigFilenameMap[$attachmentId]; - - $attachmetFilenameMap = $this->customBuckets->getBucketData( - 'attachment-orig-filename-target-filename-map' ); - if ( isset( $attachmetFilenameMap[$attachmentOrigFilename] ) ) { - continue; - } - - if ( isset( $attachmentIdToSpaceIdMap[$attachmentId] ) ) { - $attachmentSpaceId = $attachmentIdToSpaceIdMap[$attachmentId]; - } else { - // TODO: Is this wise? - $attachmentSpaceId = 0; - } - - if ( !isset( $attachmentIdToReferenceMap[$attachmentId] ) ) { - continue; - } - $attachmentReference = $attachmentIdToReferenceMap[$attachmentId]; - $attachmentTargetFilename = $this->makeAttachmentTargetFilenameFromData( - '', $attachmentId, $attachmentSpaceId, $attachmentOrigFilename, '', $spaceIdPrefixMap ); - $this->output->writeln( "- '$attachmentTargetFilename'" ); - $this->addFile( $attachmentTargetFilename, $attachmentReference ); - $this->customBuckets->addData( - 'debug-additional-files', $attachmentTargetFilename, $attachmentReference, false, true ); - $this->customBuckets->addData( - 'attachment-orig-filename-target-filename-map', $attachmentOrigFilename, $attachmentTargetFilename ); - $this->addedAttachmentIds[$attachmentId] = true; - - $this->output->writeln( "\nAdd additional file $attachmentTargetFilename" ); - } - } - /** * @param int|string $spaceKey * @param string $spaceName diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index 30ea9ce..755c123 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -47,7 +47,8 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets 'body-contents-to-pages-map', 'title-attachments', 'title-revisions', - 'files' + 'files', + 'additional-files' ] ); $this->customBuckets = new DataBuckets( [ @@ -88,6 +89,8 @@ public function buildXML( Builder $builder ) { $filesMap = $this->dataBuckets->getBucketData( 'files' ); $pageAttachmentsMap = $this->dataBuckets->getBucketData( 'title-attachments' ); + $additionalFiles = $this->dataBuckets->getBucketData( 'additional-files' ); + $bodyContentIDMainpageID = []; $pagesToBodyContents = array_flip( $bodyContentsToPagesMap ); foreach ( $spaceIDHomepagesMap as $spaceID => $homepageID ) { @@ -172,6 +175,12 @@ public function buildXML( Builder $builder ) { } } } + + foreach ( $additionalFiles as $filename => $path ) { + $attachmentContent = file_get_contents( $path ); + $this->workspace->saveUploadFile( $filename, $attachmentContent ); + } + $this->customBuckets->saveToWorkspace( $this->workspace ); } diff --git a/src/Converter/ConfluenceConverter.php b/src/Converter/ConfluenceConverter.php index 63c09a0..a7404a4 100644 --- a/src/Converter/ConfluenceConverter.php +++ b/src/Converter/ConfluenceConverter.php @@ -126,7 +126,8 @@ public function __construct( $config, Workspace $workspace ) { 'files', 'userkey-to-username-map', 'space-description-id-to-body-id-map', - 'gliffy-map' + 'gliffy-map', + 'attachment-confluence-file-key-to-target-filename-map' ] ); $this->dataBuckets->loadFromWorkspace( $this->workspace ); diff --git a/src/Converter/Processor/Image.php b/src/Converter/Processor/Image.php index 9ba1d64..7e55a33 100644 --- a/src/Converter/Processor/Image.php +++ b/src/Converter/Processor/Image.php @@ -237,6 +237,10 @@ private function makeImageAttachmentReplacement( $node ): DOMNode { $confluenceFileKey = "$spaceId---$rawPageTitle---$filename"; $targetFilename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); + var_dump( __METHOD__ ); + var_dump( $filename ); + var_dump( $confluenceFileKey ); + var_dump( $targetFilename ); array_unshift( $params, $targetFilename ); $replacementNode = $this->makeImageLinkWithDebugInfo( $node->ownerDocument, $params, $confluenceFileKey ); @@ -275,6 +279,10 @@ private function makeImagePageLinkReplacement( DomElement $node ): DOMNode { $rawPageTitle = basename( $rawPageTitle ); $confluenceFileKey = "$spaceId---$rawPageTitle---$filename"; $targetFilename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); + var_dump( __METHOD__ ); + var_dump( $filename ); + var_dump( $confluenceFileKey ); + var_dump( $targetFilename ); array_unshift( $params, $targetFilename ); $linkBody = $node->parentNode; diff --git a/src/Converter/Processor/StructuredMacroDrawio.php b/src/Converter/Processor/StructuredMacroDrawio.php index 778f2e4..500d411 100644 --- a/src/Converter/Processor/StructuredMacroDrawio.php +++ b/src/Converter/Processor/StructuredMacroDrawio.php @@ -212,6 +212,10 @@ private function bakeDrawIODataInPNG( string $drawioDataFilename, string $drawio // PNG image file found, "bake" diagram data into it and replace file content $imageFileContent = $this->dataLookup->getConfluenceFileContent( $drawioImageFilename ); + if ( $dataFileContent === null || $imageFileContent === null ) { + echo ( "Drawio error $this->rawPageTitle: $drawioImageFilename, $drawioDataFilename" ); + return; + } $imageFileContent = $drawIoFileHandler->bakeDiagramDataIntoImage( $imageFileContent, $dataFileContent ); $this->conversionDataWriter->replaceConfluenceFileContent( $drawioImageFilename, $imageFileContent ); diff --git a/src/Utility/ConversionDataLookup.php b/src/Utility/ConversionDataLookup.php index d97468f..9435e45 100644 --- a/src/Utility/ConversionDataLookup.php +++ b/src/Utility/ConversionDataLookup.php @@ -47,6 +47,11 @@ class ConversionDataLookup { */ private $confluenceUserMap = []; + /** + * @var array + */ + private $attachmentConfluenceFileKeyToTargetTitlemap = []; + /** * * @param DataBuckets $buckets @@ -60,23 +65,24 @@ public static function newFromBuckets( DataBuckets $buckets ) { $buckets->getBucketData( 'attachment-orig-filename-target-filename-map' ), $buckets->getBucketData( 'files' ), $buckets->getBucketData( 'userkey-to-username-map' ), + $buckets->getBucketData( 'attachment-confluence-file-key-to-target-filename-map' ), $buckets->getBucketData( 'space-key-to-prefix-map' ), ); } /** - * * @param array $spaceIdPrefixMap * @param array $confluencePageKeyTargetTitleMap * @param array $confluenceFilenameTargetFiletitleMap * @param array $confluenceAttachmentOrigFilenameToTargetFilenameMap * @param array $confluenceFiles * @param array $confluenceUserMap + * @param array $attachmentConfluenceFileKeyToTargetTitlemap * @param array $spaceKeyPrefixMap */ public function __construct( $spaceIdPrefixMap, $confluencePageKeyTargetTitleMap, $confluenceFilenameTargetFiletitleMap, $confluenceAttachmentOrigFilenameToTargetFilenameMap, - $confluenceFiles, $confluenceUserMap, $spaceKeyPrefixMap = [] ) { + $confluenceFiles, $confluenceUserMap, $attachmentConfluenceFileKeyToTargetTitlemap, $spaceKeyPrefixMap = [] ) { $this->spaceIdPrefixMap = $spaceIdPrefixMap; $this->spaceKeyPrefixMap = $spaceKeyPrefixMap; @@ -98,6 +104,7 @@ public function __construct( $spaceIdPrefixMap, $confluencePageKeyTargetTitleMap } $this->confluenceFiles = $confluenceFiles; $this->confluenceUserMap = $confluenceUserMap; + $this->attachmentConfluenceFileKeyToTargetTitlemap = $attachmentConfluenceFileKeyToTargetTitlemap; } /** @@ -157,12 +164,18 @@ public function getTargetTitleFromConfluencePageKey( $confluencePageKey ) { * @return string */ public function getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ) { + if ( isset( $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey] ) ) { + return $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey]; + } + $confluenceFileKey = str_replace( ' ', '_', $confluenceFileKey ); - if ( isset( $this->confluenceFilenameTargetFiletitleMap[$confluenceFileKey] ) ) { - return $this->confluenceFilenameTargetFiletitleMap[$confluenceFileKey]; + if ( isset( $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey] ) ) { + return $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey]; } + $confluenceFileKeyParts = explode( '---', $confluenceFileKey ); $confluenceFilename = $confluenceFileKeyParts[2]; + $md5File = ''; $filename = ''; if ( isset( $this->confluenceAttachmentOrigFilenameToTargetFilenameMap[$confluenceFilename] ) ) { From 3308c20097d15c5c96f90e1fa55fc2ffc204b03f Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Wed, 1 Oct 2025 11:51:26 +0200 Subject: [PATCH 19/32] Fix title builder and unittests --- src/Analyzer/ConfluenceAnalyzer.php | 6 +- src/Converter/Processor/Image.php | 8 --- .../Processor/StructuredMacroViewFile.php | 2 +- src/Utility/ConversionDataLookup.php | 16 ++--- src/Utility/TitleBuilder.php | 30 +++++--- .../Utility/TitleBuilder/TitleBuilderTest.php | 71 +++++++++++++++---- .../Utility/TitleBuilder/entities_test.xml | 4 +- 7 files changed, 88 insertions(+), 49 deletions(-) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 1c4eae1..8c9433a 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -120,7 +120,7 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets 'additional-files', 'attachment-orig-filename-target-filename-map', 'attachment-id-to-target-filename-map', - 'attachment-confluence-file-key-to-target-filename-map', + 'filenames-to-filetitles-map', 'debug-attachment-id-to-target-filename', 'debug-missing-attachment-id-to-filename', @@ -761,7 +761,7 @@ private function getAttachmentsFromCollection( XMLHelper $xmlHelper, DOMElement $confluenceFileKey = str_replace( ' ', '_', "{$spaceId}---{$confluenceTitle}---{$attachmentOrigFilename}" ); $this->customBuckets->addData( - 'attachment-confluence-file-key-to-target-filename-map', + 'filenames-to-filetitles-map', $confluenceFileKey, $attachmentTargetFilename, false, @@ -868,7 +868,7 @@ private function buildTitleAttachmentsFallbackMaps( DOMDocument $dom ): void { $confluenceFileKey = str_replace( ' ', '', "{$confluenceKey}---{$attachmentOrigFilename}" ); $this->customBuckets->addData( - 'attachment-confluence-file-key-to-target-filename-map', + 'filenames-to-filetitles-map', $confluenceFileKey, $attachmentTargetFilename, false, diff --git a/src/Converter/Processor/Image.php b/src/Converter/Processor/Image.php index 7e55a33..9ba1d64 100644 --- a/src/Converter/Processor/Image.php +++ b/src/Converter/Processor/Image.php @@ -237,10 +237,6 @@ private function makeImageAttachmentReplacement( $node ): DOMNode { $confluenceFileKey = "$spaceId---$rawPageTitle---$filename"; $targetFilename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); - var_dump( __METHOD__ ); - var_dump( $filename ); - var_dump( $confluenceFileKey ); - var_dump( $targetFilename ); array_unshift( $params, $targetFilename ); $replacementNode = $this->makeImageLinkWithDebugInfo( $node->ownerDocument, $params, $confluenceFileKey ); @@ -279,10 +275,6 @@ private function makeImagePageLinkReplacement( DomElement $node ): DOMNode { $rawPageTitle = basename( $rawPageTitle ); $confluenceFileKey = "$spaceId---$rawPageTitle---$filename"; $targetFilename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); - var_dump( __METHOD__ ); - var_dump( $filename ); - var_dump( $confluenceFileKey ); - var_dump( $targetFilename ); array_unshift( $params, $targetFilename ); $linkBody = $node->parentNode; diff --git a/src/Converter/Processor/StructuredMacroViewFile.php b/src/Converter/Processor/StructuredMacroViewFile.php index 7722e8b..753c18e 100644 --- a/src/Converter/Processor/StructuredMacroViewFile.php +++ b/src/Converter/Processor/StructuredMacroViewFile.php @@ -109,7 +109,7 @@ private function makeFilename( string $name ) { $spaceId = $this->currentSpaceId; $rawPageTitle = basename( $this->rawPageTitle ); - $confluenceFileKey = "$spaceId---$rawPageTitle---" . str_replace( ' ', '_', $name ); + $confluenceFileKey = str_replace( ' ', '_', "$spaceId---$rawPageTitle---" . $name ); $filename = $this->dataLookup->getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ); if ( $this->nsFileRepoCompat && $this->currentSpaceId !== 0 ) { diff --git a/src/Utility/ConversionDataLookup.php b/src/Utility/ConversionDataLookup.php index 9435e45..3bc887e 100644 --- a/src/Utility/ConversionDataLookup.php +++ b/src/Utility/ConversionDataLookup.php @@ -65,7 +65,6 @@ public static function newFromBuckets( DataBuckets $buckets ) { $buckets->getBucketData( 'attachment-orig-filename-target-filename-map' ), $buckets->getBucketData( 'files' ), $buckets->getBucketData( 'userkey-to-username-map' ), - $buckets->getBucketData( 'attachment-confluence-file-key-to-target-filename-map' ), $buckets->getBucketData( 'space-key-to-prefix-map' ), ); } @@ -77,12 +76,11 @@ public static function newFromBuckets( DataBuckets $buckets ) { * @param array $confluenceAttachmentOrigFilenameToTargetFilenameMap * @param array $confluenceFiles * @param array $confluenceUserMap - * @param array $attachmentConfluenceFileKeyToTargetTitlemap * @param array $spaceKeyPrefixMap */ public function __construct( $spaceIdPrefixMap, $confluencePageKeyTargetTitleMap, $confluenceFilenameTargetFiletitleMap, $confluenceAttachmentOrigFilenameToTargetFilenameMap, - $confluenceFiles, $confluenceUserMap, $attachmentConfluenceFileKeyToTargetTitlemap, $spaceKeyPrefixMap = [] ) { + $confluenceFiles, $confluenceUserMap, $spaceKeyPrefixMap = [] ) { $this->spaceIdPrefixMap = $spaceIdPrefixMap; $this->spaceKeyPrefixMap = $spaceKeyPrefixMap; @@ -95,8 +93,7 @@ public function __construct( $spaceIdPrefixMap, $confluencePageKeyTargetTitleMap $this->confluencePageKeyTargetTitleMap[$normalConfluencePageKey] = $targetTitle; } foreach ( $confluenceFilenameTargetFiletitleMap as $confluenceFileKey => $targetTitle ) { - $normalConfluenceFileKey = str_replace( ' ', '_', $confluenceFileKey ); - $this->confluenceFilenameTargetFiletitleMap[$normalConfluenceFileKey] = $targetTitle; + $this->confluenceFilenameTargetFiletitleMap = $confluenceFilenameTargetFiletitleMap; } foreach ( $confluenceAttachmentOrigFilenameToTargetFilenameMap as $origFilename => $filenames ) { $filename = str_replace( ' ', '_', $origFilename ); @@ -104,7 +101,6 @@ public function __construct( $spaceIdPrefixMap, $confluencePageKeyTargetTitleMap } $this->confluenceFiles = $confluenceFiles; $this->confluenceUserMap = $confluenceUserMap; - $this->attachmentConfluenceFileKeyToTargetTitlemap = $attachmentConfluenceFileKeyToTargetTitlemap; } /** @@ -164,13 +160,13 @@ public function getTargetTitleFromConfluencePageKey( $confluencePageKey ) { * @return string */ public function getTargetFileTitleFromConfluenceFileKey( $confluenceFileKey ) { - if ( isset( $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey] ) ) { - return $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey]; + if ( isset( $this->confluenceFilenameTargetFiletitleMap[$confluenceFileKey] ) ) { + return $this->confluenceFilenameTargetFiletitleMap[$confluenceFileKey]; } $confluenceFileKey = str_replace( ' ', '_', $confluenceFileKey ); - if ( isset( $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey] ) ) { - return $this->attachmentConfluenceFileKeyToTargetTitlemap[$confluenceFileKey]; + if ( isset( $this->confluenceFilenameTargetFiletitleMap[$confluenceFileKey] ) ) { + return $this->confluenceFilenameTargetFiletitleMap[$confluenceFileKey]; } $confluenceFileKeyParts = explode( '---', $confluenceFileKey ); diff --git a/src/Utility/TitleBuilder.php b/src/Utility/TitleBuilder.php index 2ee4cf8..2fdd4b1 100644 --- a/src/Utility/TitleBuilder.php +++ b/src/Utility/TitleBuilder.php @@ -126,26 +126,34 @@ private function getSpaceId( $pageNode ) { * @return array */ private function addParentTitles( $pageNode ) { + $pageId = $this->helper->getIDNodeValue( $pageNode ); $title = $this->helper->getPropertyValue( 'title', $pageNode ); - $titles = [ $title ]; - - $parentPageId = $this->helper->getPropertyValue( 'parent', $pageNode ); - if ( $parentPageId === $this->currentTitlesSpaceHomePageId ) { - $parentPageId = null; + $titles = []; + if ( $pageId === $this->currentTitlesSpaceHomePageId ) { + $title[] = $this->mainpage; + } else { + $titles[] = $title; } - if ( !is_int( $parentPageId ) ) { - return $titles; + if ( isset( $this->pageIdParentPageIdMap[$pageId] ) ) { + $parentPageId = $this->pageIdParentPageIdMap[$pageId]; + } else { + $parentPageId = null; } - while ( isset( $this->pageIdParentPageIdMap[$parentPageId] ) ) { - $parentTitle = $this->pageIConfluenceTitledMap[$parentPageId]; + while ( $parentPageId !== null ) { + if ( $parentPageId === $this->currentTitlesSpaceHomePageId ) { + break; + } else { + $parentTitle = $this->pageIConfluenceTitledMap[$parentPageId]; + } $titles[] = $parentTitle; - $parentPageId = $this->pageIdParentPageIdMap[$parentPageId]; - if ( $parentPageId === $this->currentTitlesSpaceHomePageId ) { + if ( isset( $this->pageIdParentPageIdMap[$parentPageId] ) ) { + $parentPageId = $this->pageIdParentPageIdMap[$parentPageId]; + } else { $parentPageId = null; } } diff --git a/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php b/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php index b74e0d7..3e35f84 100644 --- a/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php +++ b/tests/phpunit/Utility/TitleBuilder/TitleBuilderTest.php @@ -17,7 +17,7 @@ public function testBuildTitle() { $helper = new XMLHelper( $dom ); $spaceIdHomepages = [ - 32973 => 32974, + 32973 => 32974567, 99999 => -1 ]; @@ -27,8 +27,27 @@ public function testBuildTitle() { 99999 => 'TestNS_NoMain_Page:' ]; - $this->useDefaultMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); - $this->useCustomMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, 'CustomMainpage' ); + $pageIdParentPageIdMap = [ + 229472 => 32974567, + 262231 => 229472, + 999902 => 999901 + ]; + + $pageIdConfluenceTitleMap = [ + 999902 => 'Roadmap', + 999901 => 'Dokumentation', + 262231 => 'Detailed_planning', + 229472 => 'Roadmap', + 262211 => 'Roadmap', + 32974567 => 'Dokumentation' + ]; + + $this->useDefaultMainpage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $pageIdParentPageIdMap, + $pageIdConfluenceTitleMap ); + $this->useCustomMainpage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, 'CustomMainpage', + $pageIdParentPageIdMap, $pageIdConfluenceTitleMap ); $spaceIdPrefixMap = [ 32973 => 'TestNS:32973/', @@ -36,19 +55,28 @@ public function testBuildTitle() { 99999 => 'TestNS_NoMain_Page:' ]; - $this->useDefaultMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); - $this->useCustomMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, 'CustomMainpage' ); + $this->useDefaultMainpageWithRootPage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $pageIdParentPageIdMap, + $pageIdConfluenceTitleMap ); + $this->useCustomMainpageWithRootPage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, 'CustomMainpage', + $pageIdParentPageIdMap, $pageIdConfluenceTitleMap ); } /** * @param array $spaceIdPrefixMap * @param array $spaceIdHomepages * @param XMLHelper $helper + * @param array $pageIdParentPageIdMap + * @param array $pageIdConfluenceTitleMap * @return void */ - private function useDefaultMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ): void { - $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); - $actualTitles = $this->buildTitles( $titleBuilder, $helper ); + private function useDefaultMainpage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $pageIdParentPageIdMap, $pageIdConfluenceTitleMap ): void { + $titleBuilder = new TitleBuilder( + $spaceIdPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIdConfluenceTitleMap, $helper ); + $actualTitles = $this->buildTitles( + $titleBuilder, $helper ); $expectedTitles = [ "TestNS:Main_Page", @@ -66,10 +94,16 @@ private function useDefaultMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $help * @param array $spaceIdHomepages * @param XMLHelper $helper * @param string $customMainpage + * @param array $pageIdParentPageIdMap + * @param array $pageIdConfluenceTitleMap * @return void */ - private function useCustomMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ): void { - $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ); + private function useCustomMainpage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage, $pageIdParentPageIdMap, + $pageIdConfluenceTitleMap ): void { + $titleBuilder = new TitleBuilder( + $spaceIdPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIdConfluenceTitleMap, + $helper, $customMainpage ); $actualTitles = $this->buildTitles( $titleBuilder, $helper ); $expectedTitles = [ @@ -87,10 +121,14 @@ private function useCustomMainpage( $spaceIdPrefixMap, $spaceIdHomepages, $helpe * @param array $spaceIdPrefixMap * @param array $spaceIdHomepages * @param XMLHelper $helper + * @param array $pageIdParentPageIdMap + * @param array $pageIdConfluenceTitleMap * @return void */ - private function useDefaultMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHomepages, $helper ): void { - $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper ); + private function useDefaultMainpageWithRootPage( + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $pageIdParentPageIdMap, $pageIdConfluenceTitleMap ): void { + $titleBuilder = new TitleBuilder( + $spaceIdPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIdConfluenceTitleMap, $helper ); $actualTitles = $this->buildTitles( $titleBuilder, $helper ); $expectedTitles = [ @@ -109,12 +147,17 @@ private function useDefaultMainpageWithRootPage( $spaceIdPrefixMap, $spaceIdHome * @param array $spaceIdHomepages * @param XMLHelper $helper * @param string $customMainpage + * @param array $pageIdParentPageIdMap + * @param array $pageIdConfluenceTitleMap * @return void */ private function useCustomMainpageWithRootPage( - $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage + $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage, $pageIdParentPageIdMap, + $pageIdConfluenceTitleMap ): void { - $titleBuilder = new TitleBuilder( $spaceIdPrefixMap, $spaceIdHomepages, $helper, $customMainpage ); + $titleBuilder = new TitleBuilder( + $spaceIdPrefixMap, $spaceIdHomepages, $pageIdParentPageIdMap, $pageIdConfluenceTitleMap, + $helper, $customMainpage ); $actualTitles = $this->buildTitles( $titleBuilder, $helper ); $expectedTitles = [ diff --git a/tests/phpunit/Utility/TitleBuilder/entities_test.xml b/tests/phpunit/Utility/TitleBuilder/entities_test.xml index b3d5cf3..ce7baff 100644 --- a/tests/phpunit/Utility/TitleBuilder/entities_test.xml +++ b/tests/phpunit/Utility/TitleBuilder/entities_test.xml @@ -4,7 +4,7 @@ - 32974 + 32974567 35 @@ -208,7 +208,7 @@ 32973 692580904 - 32974 + 32974567 262231 From 7cf74176b6d2ac3549b2d1f254eb068abee16c32 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 2 Oct 2025 08:09:56 +0200 Subject: [PATCH 20/32] Remove uploading additional files again --- src/Composer/ConfluenceComposer.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index 755c123..a461af4 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -89,8 +89,6 @@ public function buildXML( Builder $builder ) { $filesMap = $this->dataBuckets->getBucketData( 'files' ); $pageAttachmentsMap = $this->dataBuckets->getBucketData( 'title-attachments' ); - $additionalFiles = $this->dataBuckets->getBucketData( 'additional-files' ); - $bodyContentIDMainpageID = []; $pagesToBodyContents = array_flip( $bodyContentsToPagesMap ); foreach ( $spaceIDHomepagesMap as $spaceID => $homepageID ) { @@ -141,7 +139,7 @@ public function buildXML( Builder $builder ) { } $namespace = $this->getNamespace( $pageTitle ); - if ( isset( $this->advancedConfig['skipNamespace'][$namespace] ) ) { + if ( isset( $this->advancedConfig['skip-namespace'][$namespace] ) ) { $this->output->writeln( "Page {$pageTitle} skipped by configuration 'skipNamespace' ($namespace)" ); continue; } @@ -176,11 +174,6 @@ public function buildXML( Builder $builder ) { } } - foreach ( $additionalFiles as $filename => $path ) { - $attachmentContent = file_get_contents( $path ); - $this->workspace->saveUploadFile( $filename, $attachmentContent ); - } - $this->customBuckets->saveToWorkspace( $this->workspace ); } From f555ccb7378d804cd22c6277f6b417058ce78c29 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 7 Oct 2025 08:39:14 +0200 Subject: [PATCH 21/32] Fix skip-namespace option --- src/Command/Compose.php | 4 ++-- src/Composer/ConfluenceComposer.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Command/Compose.php b/src/Command/Compose.php index 5047614..7b33337 100644 --- a/src/Command/Compose.php +++ b/src/Command/Compose.php @@ -42,9 +42,9 @@ public static function factory( $config ): Compose { /** * @return bool */ - protected function doProcessFile(): bool { + protected function processFiles() { $this->readConfigFile( $this->config ); - return parent::doProcessFile(); + parent::processFiles(); } /** diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index a461af4..fb71dc5 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -58,8 +58,8 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets $this->dataBuckets->loadFromWorkspace( $this->workspace ); - if ( isset( $this->config['config'] ) ) { - $this->advancedConfig = $this->config['config']; + if ( isset( $config['config'] ) ) { + $this->advancedConfig = $config['config']; } } @@ -139,8 +139,8 @@ public function buildXML( Builder $builder ) { } $namespace = $this->getNamespace( $pageTitle ); - if ( isset( $this->advancedConfig['skip-namespace'][$namespace] ) ) { - $this->output->writeln( "Page {$pageTitle} skipped by configuration 'skipNamespace' ($namespace)" ); + if ( in_array( $namespace, $this->advancedConfig['skip-namespace'] ) ) { + $this->output->writeln( "Page {$pageTitle} skipped by configuration" ); continue; } $builder->addRevision( $pageTitle, $pageContent, $timestamp ); From 45eb3086e34d3f5a8b077d850f7c38636834db26 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 13 Oct 2025 07:25:24 +0200 Subject: [PATCH 22/32] Fix body contents issue --- src/Analyzer/ConfluenceAnalyzer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 8c9433a..3c303ef 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -116,6 +116,7 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets 'page-id-to-confluence-key-map', 'page-id-to-title-map', 'page-id-to-space-id', + 'body-contents-to-pages-map', 'title-files', 'additional-files', 'attachment-orig-filename-target-filename-map', From f7335805143b92a314fafc8d0d151bf260b7ad9f Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 16 Oct 2025 07:28:55 +0200 Subject: [PATCH 23/32] Fix skip-namespace flag --- src/Composer/ConfluenceComposer.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index fb71dc5..6aabb40 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -139,7 +139,10 @@ public function buildXML( Builder $builder ) { } $namespace = $this->getNamespace( $pageTitle ); - if ( in_array( $namespace, $this->advancedConfig['skip-namespace'] ) ) { + if ( + isset( $this->advancedConfig['skip-namespace'] ) + && in_array( $namespace, $this->advancedConfig['skip-namespace'] ) + ) { $this->output->writeln( "Page {$pageTitle} skipped by configuration" ); continue; } From 4f98e2d9611f4143f9968a036db4d6211bbead3f Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 16 Oct 2025 08:58:06 +0200 Subject: [PATCH 24/32] CC --- src/Composer/ConfluenceComposer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index 6aabb40..3144c0e 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -140,7 +140,7 @@ public function buildXML( Builder $builder ) { $namespace = $this->getNamespace( $pageTitle ); if ( - isset( $this->advancedConfig['skip-namespace'] ) + isset( $this->advancedConfig['skip-namespace'] ) && in_array( $namespace, $this->advancedConfig['skip-namespace'] ) ) { $this->output->writeln( "Page {$pageTitle} skipped by configuration" ); From 2b346f101f3430352443dbe93fe62f46fbbe4e34 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Mon, 20 Oct 2025 11:05:32 +0200 Subject: [PATCH 25/32] Add validity checks to analyzer --- src/Analyzer/ConfluenceAnalyzer.php | 94 +++++++++++++++++++++++++++++ src/Utility/TitleBuilder.php | 6 ++ 2 files changed, 100 insertions(+) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 3c303ef..36ee5b8 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -123,6 +123,9 @@ public function __construct( $config, Workspace $workspace, DataBuckets $buckets 'attachment-id-to-target-filename-map', 'filenames-to-filetitles-map', + 'invalid-titles', + 'invalid-namespaces', + 'debug-attachment-id-to-target-filename', 'debug-missing-attachment-id-to-filename', 'debug-attachment-page-to-attachment-id', @@ -188,6 +191,8 @@ public function analyze( SplFileInfo $file ): bool { $this->customBuckets->loadFromWorkspace( $this->workspace ); $result = parent::analyze( $file ); + $this->checkTitles(); + $this->customBuckets->saveToWorkspace( $this->workspace ); return $result; } @@ -647,6 +652,11 @@ private function buildPageMaps( DOMDocument $dom ): void { return; } + if ( $targetTitle === '' ) { + $this->buckets->addData( 'title-invalids', $pageId, $targetTitle ); + return; + } + $this->output->writeln( "Add page '$targetTitle' (ID:$pageId)" ); /** @@ -1047,4 +1057,88 @@ private function makeAttachmentReference( XMLHelper $xmlHelper, DOMElement $atta return $path; } + + private function checkTitles(): void { + $spacePrefixMap = $this->customBuckets->getBucketData( 'space-id-to-prefix-map' ); + $pagesTitlesMap = $this->customBuckets->getBucketData( 'pages-titles-map' ); + + $hasInvalidTitles = false; + $hasInvalidNamespaces = false; + foreach ( $pagesTitlesMap as $key => $title ) { + if ( str_ends_with( 'title', '_' ) ) { + $this->customBuckets->addData( + 'invalid-titles', + 'invalid_ending', $title, + true, true + ); + $hasInvalidTitles = true; + } + if ( str_contains( $title, ':' ) ) { + if ( strpos( $title, ':' ) !== strrpos( $title, ':' ) ) { + $this->customBuckets->addData( + 'invalid-titles', + 'multiple_collons', $title, + true, true + ); + $hasInvalidTitles = true; + } + $namespace = substr( $title, 0, strpos( $title, ':' ) ); + $text = substr( $title, strpos( $title, ':' ) + 1 ); + + if ( !in_array( $namespace, $spacePrefixMap ) ) { + $this->customBuckets->addData( + 'invalid-namespaces', + 'unknown', $namespace, + true, true + ); + $hasInvalidNamespaces = true; + } + + $matches = []; + preg_match( '#(\d*)([a-zA-Z0-9_]*)#', $namespace, $matches ); + if ( empty( $matches ) || $matches[1] !== '' ) { + $this->customBuckets->addData( + 'invalid-namespaces', + 'invalid_char', $namespace, + true, true + ); + $hasInvalidNamespaces = true; + } + + if ( mb_strlen( $text ) > 255 ) { + $this->customBuckets->addData( + 'invalid-titles', + 'length', $title, + true, true + ); + $hasInvalidTitles = true; + } + } else { + if ( mb_strlen( $title ) > 255 ) { + $this->customBuckets->addData( + 'invalid-titles', + 'length', $title, + true, true + ); + $hasInvalidTitles = true; + } + } + } + + if ( $hasInvalidNamespaces === true || $hasInvalidTitles === true ) { + $this->output->writeln( "\n\nWarning:\n" ); + + if ( $hasInvalidNamespaces === true ) { + $this->output->writeln( ' - Analyze process found invalid namespaces' ); + } + + if ( $hasInvalidTitles === true ) { + $this->output->writeln( ' - Analyze process found invalid titles' ); + } + + $this->output->writeln( + "\nPlease check invalid-namespaces.php and/or invalid-titles.php before continuing with extract step" + ); + } + } } diff --git a/src/Utility/TitleBuilder.php b/src/Utility/TitleBuilder.php index 2fdd4b1..737fef5 100644 --- a/src/Utility/TitleBuilder.php +++ b/src/Utility/TitleBuilder.php @@ -95,6 +95,12 @@ public function buildTitle( $pageNode ) { $titles = $this->addParentTitles( $pageNode ); foreach ( $titles as $title ) { + $title = str_replace( + [ ':', '%', '?', '#', '<', '>', '+', '[', ']', '{', '}', '|' ], + '_', + $title + ); + $title = str_replace( '__', '_', $title ); $this->builder->appendTitleSegment( $title ); } From aea4a45b2671cfc4c2da24ff515104177abc2537 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Tue, 21 Oct 2025 09:01:10 +0200 Subject: [PATCH 26/32] Abort script if config.yaml is invalid --- src/Analyzer/ConfluenceAnalyzer.php | 9 --------- src/Command/Analyze.php | 2 ++ src/Command/Compose.php | 2 ++ src/Command/Convert.php | 2 ++ src/Command/Extract.php | 2 ++ 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 36ee5b8..a3ba0f1 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -1085,15 +1085,6 @@ private function checkTitles(): void { $namespace = substr( $title, 0, strpos( $title, ':' ) ); $text = substr( $title, strpos( $title, ':' ) + 1 ); - if ( !in_array( $namespace, $spacePrefixMap ) ) { - $this->customBuckets->addData( - 'invalid-namespaces', - 'unknown', $namespace, - true, true - ); - $hasInvalidNamespaces = true; - } - $matches = []; preg_match( '#(\d*)([a-zA-Z0-9_]*)#', $namespace, $matches ); if ( empty( $matches ) || $matches[1] !== '' ) { diff --git a/src/Command/Analyze.php b/src/Command/Analyze.php index 3e9802f..110ee54 100644 --- a/src/Command/Analyze.php +++ b/src/Command/Analyze.php @@ -60,6 +60,8 @@ private function readConfigFile( &$config ): void { $yaml = Yaml::parse( $content ); $config = array_merge( $config, $yaml ); } catch ( ParseException $e ) { + $this->output->writeln( 'Invalid config file provided' ); + exit( true ); } } } diff --git a/src/Command/Compose.php b/src/Command/Compose.php index 7b33337..b2a3a9c 100644 --- a/src/Command/Compose.php +++ b/src/Command/Compose.php @@ -60,6 +60,8 @@ private function readConfigFile( &$config ): void { $yaml = Yaml::parse( $content ); $config = array_merge( $config, $yaml ); } catch ( ParseException $e ) { + $this->output->writeln( 'Invalid config file provided' ); + exit( true ); } } } diff --git a/src/Command/Convert.php b/src/Command/Convert.php index fc47ac6..09cea35 100644 --- a/src/Command/Convert.php +++ b/src/Command/Convert.php @@ -58,6 +58,8 @@ private function readConfigFile( &$config ): void { $yaml = Yaml::parse( $content ); $config = array_merge( $config, $yaml ); } catch ( ParseException $e ) { + $this->output->writeln( 'Invalid config file provided' ); + exit( true ); } } } diff --git a/src/Command/Extract.php b/src/Command/Extract.php index 8fe2f52..59369be 100644 --- a/src/Command/Extract.php +++ b/src/Command/Extract.php @@ -63,6 +63,8 @@ private function readConfigFile( &$config ): void { $yaml = Yaml::parse( $content ); $config = array_merge( $config, $yaml ); } catch ( ParseException $e ) { + $this->output->writeln( 'Invalid config file provided' ); + exit( true ); } } } From 5fbba03528d22c7ee48cfe2c386ec19f12e5ff38 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Wed, 29 Oct 2025 09:18:29 +0100 Subject: [PATCH 27/32] Make StatusMacro inline wikitext --- .../Processor/ConvertMacroToTemplateBase.php | 43 ++++++++++++++++--- .../Processor/ConvertStatusMacro.php | 14 ++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/Converter/Processor/ConvertMacroToTemplateBase.php b/src/Converter/Processor/ConvertMacroToTemplateBase.php index 34e8f70..26ef2cf 100644 --- a/src/Converter/Processor/ConvertMacroToTemplateBase.php +++ b/src/Converter/Processor/ConvertMacroToTemplateBase.php @@ -37,8 +37,12 @@ public function process( DOMDocument $dom ): void { foreach ( $actualMacros as $actualMacro ) { $parentNode = $actualMacro->parentNode; + $openTemplate = "{{" . "$wikiTextTemplateName"; + if ( $this->addLinebreakInsideTemplate() ) { + $openTemplate .= "###BREAK###\n"; + } $wikitextTemplateStartTextNode = $dom->createTextNode( - "{{" . "$wikiTextTemplateName###BREAK###\n" + $openTemplate ); $parentNode->insertBefore( $wikitextTemplateStartTextNode, $actualMacro ); @@ -46,12 +50,17 @@ public function process( DOMDocument $dom ): void { $parameterEls = $actualMacro->getElementsByTagName( 'parameter' ); foreach ( $parameterEls as $parameterEl ) { $paramName = $parameterEl->getAttribute( 'ac:name' ); + if ( trim( $paramName ) === '' ) { + continue; + } $paramValue = $parameterEl->nodeValue; // We add a "###BREAK###", as `pandoc` will eat up regular line breaks. // They will be restored in a "Postprocessor" - $paramTextNode = $dom->createTextNode( - " |$paramName = $paramValue###BREAK###\n" - ); + $praramString = " |$paramName = $paramValue"; + if ( $this->addLinebreakInsideTemplate() ) { + $praramString .= '###BREAK###\n'; + } + $paramTextNode = $dom->createTextNode( "$praramString" ); $parentNode->insertBefore( $paramTextNode, $actualMacro ); } @@ -64,7 +73,11 @@ public function process( DOMDocument $dom ): void { } if ( !empty( $richTextBodyEls ) ) { - $wikitextTemplateEndTextNode = $dom->createTextNode( " |body = ###BREAK###\n" ); + $bodyString = " |body = "; + if ( $this->addLinebreakInsideTemplate() ) { + $bodyString .= '###BREAK###\n'; + } + $wikitextTemplateEndTextNode = $dom->createTextNode( "$bodyString" ); $parentNode->insertBefore( $wikitextTemplateEndTextNode, $actualMacro ); foreach ( $richTextBodyEls as $richTextBodyEl ) { @@ -81,7 +94,11 @@ public function process( DOMDocument $dom ): void { } } - $wikitextTemplateEndTextNode = $dom->createTextNode( "}}###BREAK###\n" ); + $closeTemplate = "}}"; + if ( $this->addLinebreakAfterTemplate() ) { + $closeTemplate .= "###BREAK###"; + } + $wikitextTemplateEndTextNode = $dom->createTextNode( "$closeTemplate\n" ); $parentNode->insertBefore( $wikitextTemplateEndTextNode, $actualMacro ); $parentNode->removeChild( $actualMacro ); @@ -99,4 +116,18 @@ abstract protected function getMacroName(): string; * @return string */ abstract protected function getWikiTextTemplateName(): string; + + /** + * @return boolean + */ + protected function addLinebreakInsideTemplate(): bool { + return true; + } + + /** + * @return boolean + */ + protected function addLinebreakAfterTemplate(): bool { + return true; + } } diff --git a/src/Converter/Processor/ConvertStatusMacro.php b/src/Converter/Processor/ConvertStatusMacro.php index 9025140..f50a280 100644 --- a/src/Converter/Processor/ConvertStatusMacro.php +++ b/src/Converter/Processor/ConvertStatusMacro.php @@ -23,4 +23,18 @@ protected function getMacroName(): string { protected function getWikiTextTemplateName(): string { return 'Status'; } + + /** + * @return boolean + */ + protected function addLinebreakInsideTemplate(): bool { + return false; + } + + /** + * @return boolean + */ + protected function addLinebreakAfterTemplate(): bool { + return false; + } } From b11472308bfcb668a2c63a26f74033f32fb8336c Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Wed, 29 Oct 2025 09:23:57 +0100 Subject: [PATCH 28/32] Fix unittest --- .../Processor/ConvertMacroToTemplateBase.php | 16 ++++++++-------- src/Converter/Processor/ConvertStatusMacro.php | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Converter/Processor/ConvertMacroToTemplateBase.php b/src/Converter/Processor/ConvertMacroToTemplateBase.php index 26ef2cf..bc8380e 100644 --- a/src/Converter/Processor/ConvertMacroToTemplateBase.php +++ b/src/Converter/Processor/ConvertMacroToTemplateBase.php @@ -58,9 +58,9 @@ public function process( DOMDocument $dom ): void { // They will be restored in a "Postprocessor" $praramString = " |$paramName = $paramValue"; if ( $this->addLinebreakInsideTemplate() ) { - $praramString .= '###BREAK###\n'; + $praramString .= "###BREAK###\n"; } - $paramTextNode = $dom->createTextNode( "$praramString" ); + $paramTextNode = $dom->createTextNode( $praramString ); $parentNode->insertBefore( $paramTextNode, $actualMacro ); } @@ -75,9 +75,9 @@ public function process( DOMDocument $dom ): void { if ( !empty( $richTextBodyEls ) ) { $bodyString = " |body = "; if ( $this->addLinebreakInsideTemplate() ) { - $bodyString .= '###BREAK###\n'; + $bodyString .= "###BREAK###\n"; } - $wikitextTemplateEndTextNode = $dom->createTextNode( "$bodyString" ); + $wikitextTemplateEndTextNode = $dom->createTextNode( $bodyString ); $parentNode->insertBefore( $wikitextTemplateEndTextNode, $actualMacro ); foreach ( $richTextBodyEls as $richTextBodyEl ) { @@ -96,9 +96,9 @@ public function process( DOMDocument $dom ): void { $closeTemplate = "}}"; if ( $this->addLinebreakAfterTemplate() ) { - $closeTemplate .= "###BREAK###"; + $closeTemplate .= "###BREAK###\n"; } - $wikitextTemplateEndTextNode = $dom->createTextNode( "$closeTemplate\n" ); + $wikitextTemplateEndTextNode = $dom->createTextNode( "$closeTemplate" ); $parentNode->insertBefore( $wikitextTemplateEndTextNode, $actualMacro ); $parentNode->removeChild( $actualMacro ); @@ -118,14 +118,14 @@ abstract protected function getMacroName(): string; abstract protected function getWikiTextTemplateName(): string; /** - * @return boolean + * @return bool */ protected function addLinebreakInsideTemplate(): bool { return true; } /** - * @return boolean + * @return bool */ protected function addLinebreakAfterTemplate(): bool { return true; diff --git a/src/Converter/Processor/ConvertStatusMacro.php b/src/Converter/Processor/ConvertStatusMacro.php index f50a280..5ffbedf 100644 --- a/src/Converter/Processor/ConvertStatusMacro.php +++ b/src/Converter/Processor/ConvertStatusMacro.php @@ -25,14 +25,14 @@ protected function getWikiTextTemplateName(): string { } /** - * @return boolean + * @return bool */ protected function addLinebreakInsideTemplate(): bool { return false; } /** - * @return boolean + * @return bool */ protected function addLinebreakAfterTemplate(): bool { return false; From b26fa4f66cd45efe8883fb57ec2a64d36afffdd4 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 27 Nov 2025 08:50:52 +0100 Subject: [PATCH 29/32] Add execution time and prefix/namespace selection --- src/Analyzer/ConfluenceAnalyzer.php | 10 ++++++ src/Command/Analyze.php | 12 +++++++ src/Command/Compose.php | 6 ++++ src/Command/Convert.php | 12 +++++++ src/Command/Extract.php | 12 +++++++ src/Composer/ConfluenceComposer.php | 52 ++++++++++++++-------------- src/Utility/ExecutionTime.php | 53 +++++++++++++++++++++++++++++ 7 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 src/Utility/ExecutionTime.php diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index a3ba0f1..4b442b0 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -634,6 +634,16 @@ private function buildPageMaps( DOMDocument $dom ): void { if ( $spaceId === null ) { return; } + if ( !isset( $spaceIdToPrefixMap[$spaceId] ) ) { + return; + } + $prefix = $spaceIdToPrefixMap[$spaceId]; + if ( + isset( $this->config['analyzer-include-spacekey'] ) + && !in_array( $prefix, $this->config['analyzer-include-spacekey'] ) + ) { + return; + } $originalVersionID = $xmlHelper->getPropertyValue( 'originalVersion', $pageNode ); if ( $originalVersionID !== null ) { return; diff --git a/src/Command/Analyze.php b/src/Command/Analyze.php index 110ee54..2fb3094 100644 --- a/src/Command/Analyze.php +++ b/src/Command/Analyze.php @@ -3,6 +3,7 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Analyze as CommandAnalyze; +use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; @@ -47,6 +48,17 @@ protected function doProcessFile(): bool { return parent::doProcessFile(); } + protected function processFiles() { + $executionTime = new ExecutionTime(); + + $returnValue = parent::processFiles(); + + $executionTime = $executionTime->getHumanReadableExecutionTime(); + $this->output( $executionTime ); + + return $returnValue; + } + /** * @param array &$config * @return void diff --git a/src/Command/Compose.php b/src/Command/Compose.php index b2a3a9c..1e21a72 100644 --- a/src/Command/Compose.php +++ b/src/Command/Compose.php @@ -3,6 +3,7 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Compose as CommandCompose; +use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; @@ -43,8 +44,13 @@ public static function factory( $config ): Compose { * @return bool */ protected function processFiles() { + $executionTime = new ExecutionTime(); + $this->readConfigFile( $this->config ); parent::processFiles(); + + $executionTime = $executionTime->getHumanReadableExecutionTime(); + $this->output( $executionTime ); } /** diff --git a/src/Command/Convert.php b/src/Command/Convert.php index 09cea35..09a0505 100644 --- a/src/Command/Convert.php +++ b/src/Command/Convert.php @@ -3,6 +3,7 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Convert as CommandConvert; +use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; @@ -40,6 +41,17 @@ public static function factory( $config ): Convert { return new static( $config ); } + protected function processFiles() { + $executionTime = new ExecutionTime(); + + $returnValue = parent::processFiles(); + + $executionTime = $executionTime->getHumanReadableExecutionTime(); + $this->output( $executionTime ); + + return $returnValue; + } + protected function doProcessFile(): bool { $this->readConfigFile( $this->config ); return parent::doProcessFile(); diff --git a/src/Command/Extract.php b/src/Command/Extract.php index 59369be..821ac3b 100644 --- a/src/Command/Extract.php +++ b/src/Command/Extract.php @@ -3,6 +3,7 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Extract as CommandExtract; +use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use HalloWelt\MediaWiki\Lib\Migration\IExtractor; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; @@ -50,6 +51,17 @@ protected function beforeProcessFiles() { parent::beforeProcessFiles(); } + protected function processFiles() { + $executionTime = new ExecutionTime(); + + $returnValue = parent::processFiles(); + + $executionTime = $executionTime->getHumanReadableExecutionTime(); + $this->output( $executionTime ); + + return $returnValue; + } + /** * @param array &$config * @return void diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php index 3144c0e..b5b8d09 100644 --- a/src/Composer/ConfluenceComposer.php +++ b/src/Composer/ConfluenceComposer.php @@ -140,40 +140,40 @@ public function buildXML( Builder $builder ) { $namespace = $this->getNamespace( $pageTitle ); if ( - isset( $this->advancedConfig['skip-namespace'] ) - && in_array( $namespace, $this->advancedConfig['skip-namespace'] ) + isset( $this->advancedConfig['composer-include-namespace'] ) + && in_array( $namespace, $this->advancedConfig['composer-include-namespace'] ) ) { - $this->output->writeln( "Page {$pageTitle} skipped by configuration" ); - continue; - } - $builder->addRevision( $pageTitle, $pageContent, $timestamp ); + $builder->addRevision( $pageTitle, $pageContent, $timestamp ); - // Append attachments - if ( !empty( $pageAttachmentsMap[$pageTitle] ) ) { - $this->output->writeln( "\nPage has attachments. Adding them...\n" ); + // Append attachments + if ( !empty( $pageAttachmentsMap[$pageTitle] ) ) { + $this->output->writeln( "\nPage has attachments. Adding them...\n" ); - $attachments = $pageAttachmentsMap[$pageTitle]; - foreach ( $attachments as $attachment ) { - $this->output->writeln( "Attachment: $attachment" ); + $attachments = $pageAttachmentsMap[$pageTitle]; + foreach ( $attachments as $attachment ) { + $this->output->writeln( "Attachment: $attachment" ); - $drawIoFileHandler = new DrawIOFileHandler(); + $drawIoFileHandler = new DrawIOFileHandler(); - // We do not need DrawIO data files in our wiki, just PNG image - if ( $drawIoFileHandler->isDrawIODataFile( $attachment ) ) { - continue; - } + // We do not need DrawIO data files in our wiki, just PNG image + if ( $drawIoFileHandler->isDrawIODataFile( $attachment ) ) { + continue; + } - if ( isset( $filesMap[$attachment] ) ) { - $filePath = $filesMap[$attachment][0]; - $attachmentContent = file_get_contents( $filePath ); + if ( isset( $filesMap[$attachment] ) ) { + $filePath = $filesMap[$attachment][0]; + $attachmentContent = file_get_contents( $filePath ); - $this->workspace->saveUploadFile( $attachment, $attachmentContent ); - $this->customBuckets->addData( 'title-uploads', $pageTitle, $attachment ); - } else { - $this->output->writeln( "Attachment file was not found!" ); - $this->customBuckets->addData( 'title-uploads-fail', $pageTitle, $attachment ); + $this->workspace->saveUploadFile( $attachment, $attachmentContent ); + $this->customBuckets->addData( 'title-uploads', $pageTitle, $attachment ); + } else { + $this->output->writeln( "Attachment file was not found!" ); + $this->customBuckets->addData( 'title-uploads-fail', $pageTitle, $attachment ); + } } } + } else { + $this->output->writeln( "Page {$pageTitle} skipped by configuration" ); } } @@ -187,7 +187,7 @@ public function buildXML( Builder $builder ) { private function getNamespace( string $title ): string { $collonPos = strpos( $title, ':' ); if ( !$collonPos ) { - return ''; + return 'NS_MAIN'; } return substr( $title, 0, $collonPos ); } diff --git a/src/Utility/ExecutionTime.php b/src/Utility/ExecutionTime.php new file mode 100644 index 0000000..b80b4b2 --- /dev/null +++ b/src/Utility/ExecutionTime.php @@ -0,0 +1,53 @@ +executionTimeStart = $this->getMicrotime(); + } + + /** + * @return string + */ + public function getHumanReadableTime(): string { + $executionTimeEnd = $this->getMicrotime(); + $executionTime = $executionTimeEnd - $this->executionTimeStart; + + $s = $executionTime%60; + $m = floor( ( $executionTime%3600 )/60 ); + $h = floor( ( $executionTime%86400 )/3600 ); + $d = floor( ( $executionTime%2592000 )/86400 ); + + $time = ''; + if ( $d > 1 ) { + $time .= "{$d} days "; + } else if ( $d > 0 ) { + $time .= "{$d} day "; + } + $time .= "{$h}h {$m}m {$s}s"; + + return $time; + } + + /** + * @return string + */ + public function getHumanReadableExecutionTime(): string { + $time = $this->getHumanReadableTime(); + return "Execution time: {$time}"; + } + + /** + * @return float + */ + private function getMicrotime(): float { + return microtime( true ); + } +} \ No newline at end of file From 7e45ee49bd2e8db32fae390fde1083b8edd5ed8c Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 27 Nov 2025 09:21:20 +0100 Subject: [PATCH 30/32] Fix title length check and improve analyzer-include-spacekey config --- src/Analyzer/ConfluenceAnalyzer.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Analyzer/ConfluenceAnalyzer.php b/src/Analyzer/ConfluenceAnalyzer.php index 4b442b0..1063a0a 100644 --- a/src/Analyzer/ConfluenceAnalyzer.php +++ b/src/Analyzer/ConfluenceAnalyzer.php @@ -155,6 +155,15 @@ private function setConfigVars(): void { if ( isset( $this->advancedConfig['mainpage'] ) ) { $this->mainpage = $this->advancedConfig['mainpage']; } + + if ( isset( $this->advancedConfig['analyzer-include-spacekey'] ) ) { + $analyzerIncludeSpacekey = $this->advancedConfig['analyzer-include-spacekey']; + $normalizedAnalyzerIncludeSpacekey = []; + foreach ( $analyzerIncludeSpacekey as $key ) { + $normalizedAnalyzerIncludeSpacekey[] = strtolower( $key ); + } + $this->advancedConfig['analyzer-include-spacekey'] = $normalizedAnalyzerIncludeSpacekey; + } } /** @@ -639,8 +648,8 @@ private function buildPageMaps( DOMDocument $dom ): void { } $prefix = $spaceIdToPrefixMap[$spaceId]; if ( - isset( $this->config['analyzer-include-spacekey'] ) - && !in_array( $prefix, $this->config['analyzer-include-spacekey'] ) + isset( $this->advancedConfig['analyzer-include-spacekey'] ) + && !in_array( strtolower( $prefix ), $this->advancedConfig['analyzer-include-spacekey'] ) ) { return; } @@ -1106,7 +1115,7 @@ private function checkTitles(): void { $hasInvalidNamespaces = true; } - if ( mb_strlen( $text ) > 255 ) { + if ( mb_strlen( urlencode( $text ) ) > 255 ) { $this->customBuckets->addData( 'invalid-titles', 'length', $title, @@ -1115,7 +1124,7 @@ private function checkTitles(): void { $hasInvalidTitles = true; } } else { - if ( mb_strlen( $title ) > 255 ) { + if ( mb_strlen( urlencode( $title ) ) > 255 ) { $this->customBuckets->addData( 'invalid-titles', 'length', $title, From 887759684d6c430d909f8f943dd6478f839fade1 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 27 Nov 2025 09:42:43 +0100 Subject: [PATCH 31/32] CC --- src/Command/Analyze.php | 2 +- src/Command/Compose.php | 2 +- src/Command/Convert.php | 2 +- src/Command/Extract.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Command/Analyze.php b/src/Command/Analyze.php index 2fb3094..56f2119 100644 --- a/src/Command/Analyze.php +++ b/src/Command/Analyze.php @@ -54,7 +54,7 @@ protected function processFiles() { $returnValue = parent::processFiles(); $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output( $executionTime ); + $this->output->writeln( $executionTime ); return $returnValue; } diff --git a/src/Command/Compose.php b/src/Command/Compose.php index 1e21a72..08ac477 100644 --- a/src/Command/Compose.php +++ b/src/Command/Compose.php @@ -50,7 +50,7 @@ protected function processFiles() { parent::processFiles(); $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output( $executionTime ); + $this->output->writeln( $executionTime ); } /** diff --git a/src/Command/Convert.php b/src/Command/Convert.php index 09a0505..10c834f 100644 --- a/src/Command/Convert.php +++ b/src/Command/Convert.php @@ -47,7 +47,7 @@ protected function processFiles() { $returnValue = parent::processFiles(); $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output( $executionTime ); + $this->output->writeln( $executionTime ); return $returnValue; } diff --git a/src/Command/Extract.php b/src/Command/Extract.php index 821ac3b..68619f8 100644 --- a/src/Command/Extract.php +++ b/src/Command/Extract.php @@ -57,7 +57,7 @@ protected function processFiles() { $returnValue = parent::processFiles(); $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output( $executionTime ); + $this->output->writeln( $executionTime ); return $returnValue; } From 1c8e3260af919a35959aeca31f16560b953d1517 Mon Sep 17 00:00:00 2001 From: Daniel Vogel Date: Thu, 27 Nov 2025 11:11:30 +0100 Subject: [PATCH 32/32] Move exection time to lib --- src/Command/Analyze.php | 13 ------- src/Command/Compose.php | 6 --- src/Command/Convert.php | 12 ------ src/Command/Extract.php | 12 ------ src/Extractor/ConfluenceExtractor.php | 1 + src/Utility/ExecutionTime.php | 53 --------------------------- 6 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 src/Utility/ExecutionTime.php diff --git a/src/Command/Analyze.php b/src/Command/Analyze.php index 56f2119..b4e4de7 100644 --- a/src/Command/Analyze.php +++ b/src/Command/Analyze.php @@ -3,14 +3,12 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Analyze as CommandAnalyze; -use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; class Analyze extends CommandAnalyze { - /** * * @inheritDoc @@ -48,17 +46,6 @@ protected function doProcessFile(): bool { return parent::doProcessFile(); } - protected function processFiles() { - $executionTime = new ExecutionTime(); - - $returnValue = parent::processFiles(); - - $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output->writeln( $executionTime ); - - return $returnValue; - } - /** * @param array &$config * @return void diff --git a/src/Command/Compose.php b/src/Command/Compose.php index 08ac477..b2a3a9c 100644 --- a/src/Command/Compose.php +++ b/src/Command/Compose.php @@ -3,7 +3,6 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Compose as CommandCompose; -use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; @@ -44,13 +43,8 @@ public static function factory( $config ): Compose { * @return bool */ protected function processFiles() { - $executionTime = new ExecutionTime(); - $this->readConfigFile( $this->config ); parent::processFiles(); - - $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output->writeln( $executionTime ); } /** diff --git a/src/Command/Convert.php b/src/Command/Convert.php index 10c834f..09cea35 100644 --- a/src/Command/Convert.php +++ b/src/Command/Convert.php @@ -3,7 +3,6 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Convert as CommandConvert; -use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; @@ -41,17 +40,6 @@ public static function factory( $config ): Convert { return new static( $config ); } - protected function processFiles() { - $executionTime = new ExecutionTime(); - - $returnValue = parent::processFiles(); - - $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output->writeln( $executionTime ); - - return $returnValue; - } - protected function doProcessFile(): bool { $this->readConfigFile( $this->config ); return parent::doProcessFile(); diff --git a/src/Command/Extract.php b/src/Command/Extract.php index 68619f8..59369be 100644 --- a/src/Command/Extract.php +++ b/src/Command/Extract.php @@ -3,7 +3,6 @@ namespace HalloWelt\MigrateConfluence\Command; use HalloWelt\MediaWiki\Lib\Migration\Command\Extract as CommandExtract; -use HalloWelt\MigrateConfluence\Utility\ExecutionTime; use HalloWelt\MediaWiki\Lib\Migration\IExtractor; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Exception\ParseException; @@ -51,17 +50,6 @@ protected function beforeProcessFiles() { parent::beforeProcessFiles(); } - protected function processFiles() { - $executionTime = new ExecutionTime(); - - $returnValue = parent::processFiles(); - - $executionTime = $executionTime->getHumanReadableExecutionTime(); - $this->output->writeln( $executionTime ); - - return $returnValue; - } - /** * @param array &$config * @return void diff --git a/src/Extractor/ConfluenceExtractor.php b/src/Extractor/ConfluenceExtractor.php index b8bb1ab..f6b437c 100644 --- a/src/Extractor/ConfluenceExtractor.php +++ b/src/Extractor/ConfluenceExtractor.php @@ -140,6 +140,7 @@ private function buildLabellingMap( DOMDocument $dom ): void { $labelProp = $xmlHelper->getPropertyNode( 'label', $labelling ); $labelId = $xmlHelper->getIDNodeValue( $labelProp ); + $labelMap = $this->customBuckets->getBucketData( 'label-id-to-name-map' ); if ( isset( $labelMap[$labelId] ) ) { $categories[] = $labelMap[$labelId]; } diff --git a/src/Utility/ExecutionTime.php b/src/Utility/ExecutionTime.php deleted file mode 100644 index b80b4b2..0000000 --- a/src/Utility/ExecutionTime.php +++ /dev/null @@ -1,53 +0,0 @@ -executionTimeStart = $this->getMicrotime(); - } - - /** - * @return string - */ - public function getHumanReadableTime(): string { - $executionTimeEnd = $this->getMicrotime(); - $executionTime = $executionTimeEnd - $this->executionTimeStart; - - $s = $executionTime%60; - $m = floor( ( $executionTime%3600 )/60 ); - $h = floor( ( $executionTime%86400 )/3600 ); - $d = floor( ( $executionTime%2592000 )/86400 ); - - $time = ''; - if ( $d > 1 ) { - $time .= "{$d} days "; - } else if ( $d > 0 ) { - $time .= "{$d} day "; - } - $time .= "{$h}h {$m}m {$s}s"; - - return $time; - } - - /** - * @return string - */ - public function getHumanReadableExecutionTime(): string { - $time = $this->getHumanReadableTime(); - return "Execution time: {$time}"; - } - - /** - * @return float - */ - private function getMicrotime(): float { - return microtime( true ); - } -} \ No newline at end of file