diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..174a22a2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +/temp/joomla +/vendor +/node_modules diff --git a/.env.4.4.example b/.env.4.4.example new file mode 100644 index 00000000..1eefab92 --- /dev/null +++ b/.env.4.4.example @@ -0,0 +1,5 @@ +JOOMLA_VERSION=4.4-php8.1 +JOOMLA_DB_NAME=joomla + +MARIADB_VERSION=12 +JOOMLA_FRAMEWORK_VERSION=^2.0 \ No newline at end of file diff --git a/.env.5.4.example b/.env.5.4.example new file mode 100644 index 00000000..6769ac63 --- /dev/null +++ b/.env.5.4.example @@ -0,0 +1,5 @@ +JOOMLA_VERSION=5.4-php8.3 +JOOMLA_DB_NAME=joomla + +MARIADB_VERSION=12 +JOOMLA_FRAMEWORK_VERSION=^3.0 \ No newline at end of file diff --git a/.env.6.example b/.env.6.example new file mode 100644 index 00000000..221bd74d --- /dev/null +++ b/.env.6.example @@ -0,0 +1,5 @@ +JOOMLA_VERSION=6-php8.3 +JOOMLA_DB_NAME=joomla + +MARIADB_VERSION=12 +JOOMLA_FRAMEWORK_VERSION=^4.0 \ No newline at end of file diff --git a/.gitignore b/.gitignore index b0b53e6b..32924f47 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,9 @@ /unerrnum /watch* /vendor -/node_modules \ No newline at end of file +/node_modules +/.phpunit.result.cache +/.phpunit.cache +/.env +/cypress/screenshots +/cypress/videos \ No newline at end of file diff --git a/.svnignore b/.svnignore deleted file mode 100644 index 84682692..00000000 --- a/.svnignore +++ /dev/null @@ -1,12 +0,0 @@ -*.zip -*.xcf -.workspace -.git -.gitignore -.directory -_Checklist.txt -_attachments.ppr -base_nums.py -base_nums.pickle -fixerrnums.py - diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5f8d4a48 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.enableFiletypes": [ + "ini" + ] +} \ No newline at end of file diff --git a/FOR_DEVELOPERS.md b/FOR_DEVELOPERS.md index 8f574308..8b3d7812 100644 --- a/FOR_DEVELOPERS.md +++ b/FOR_DEVELOPERS.md @@ -2,6 +2,7 @@ - [How to update release](#how-to-update-release-version) - [How to update release package](#how-to-update-release-package) +- [Testing](#testing) ### How to update release version @@ -24,3 +25,22 @@ create a release with v as name upload the package file attachments-.zip into this release + +### Testing + +This project uses PHPUnit for testing. To run the tests: + +1. Install dependencies: `composer install` +2. Run all tests: `composer test` or `make test` +3. Run tests with coverage: `composer test-coverage` or `make test-coverage` + +The test suite includes: +- Unit tests for core functionality in `tests/unit/` +- Integration tests in `tests/integration/` (when available) +- Helper tests for the AttachmentsPermissions class and related functionality + +To run specific tests: +- `vendor/bin/phpunit tests/unit/Helper/` - Run all helper tests +- `vendor/bin/phpunit --testdox` - Run with human-readable output +- `vendor/bin/phpunit --coverage-html coverage/` - Run with coverage report + diff --git a/Makefile b/Makefile index c052382a..47554289 100644 --- a/Makefile +++ b/Makefile @@ -119,14 +119,19 @@ purge: @find . -name '*.bak' -exec rm {} \; @rm -f .tx/*.bak -unittests: +test: @echo @echo "Running unit tests..." - @cd test; phing -Droot=/var/www/test/joomla25/ unit_tests + @php vendor/bin/phpunit -c phpunit.xml @echo -unittests_show: unittests - @firefox test/coverage_result/index.html +test-coverage: + @echo + @echo "Running unit tests with coverage..." + @php vendor/bin/phpunit -c phpunit.xml --coverage-html ./tests/coverage-report + @echo + @echo "Coverage report generated in tests/coverage-report/index.html" + @echo manual: extensions_manual/manual.rst @echo "Creating Attachments Extension Manual" diff --git a/attachments_component/admin/src/Helper/AttachmentsImport.php b/attachments_component/admin/src/Helper/AttachmentsImport.php index 57ee6630..651015d7 100644 --- a/attachments_component/admin/src/Helper/AttachmentsImport.php +++ b/attachments_component/admin/src/Helper/AttachmentsImport.php @@ -134,12 +134,12 @@ public static function importAttachmentsFromCSVFile( $ids_ok = array(); } - iconv_set_encoding("internal_encoding", "UTF-8"); + iconv_set_encoding("default_charset", "UTF-8"); setlocale(LC_ALL, 'en_US.UTF-8'); while (!feof($f)) { // Read the next line - $adata = fgetcsv($f); + $adata = fgetcsv($f, escape: '\\'); $line_num += 1; $line_str = ' [LINE: ' . $line_num . '] '; @@ -169,10 +169,11 @@ public static function importAttachmentsFromCSVFile( if (!$apm->attachmentsPluginInstalled($parent_type)) { return Text::sprintf('ATTACH_ERROR_UNKNOWN_PARENT_TYPE_S', $parent_type) . $line_str . ' (ERR 87)'; } - $parent = $apm->getAttachmentsPlugin($parent_type); - + // Does the parent exist? if ($verify_parent) { + $parent = $apm->getAttachmentsPlugin($parent_type); + // Make sure a parent with the specified ID exists if (!$parent->parentExists($parent_id, $parent_entity)) { return Text::sprintf('ATTACH_ERROR_UNKNOWN_PARENT_ID_N', $parent_id) . $line_str . ' (ERR 88)'; @@ -317,7 +318,7 @@ protected static function parseFieldNames($file) { // Load the field names from the file $field = array(); - $header_line = fgetcsv($file); + $header_line = fgetcsv($file, escape: '\\'); // Strip of the leading BOM, if present $header_line = filter_var_array($header_line, FILTER_DEFAULT , FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH); for ($i = 0; $i < count($header_line); $i++) { diff --git a/attachments_component/admin/src/Helper/AttachmentsPermissions.php b/attachments_component/admin/src/Helper/AttachmentsPermissions.php index ef1d93cc..1881af4c 100644 --- a/attachments_component/admin/src/Helper/AttachmentsPermissions.php +++ b/attachments_component/admin/src/Helper/AttachmentsPermissions.php @@ -35,7 +35,7 @@ class AttachmentsPermissions /** * Get the actions * - * @return an array of which actions are permitted for this user + * @return Registry a registry object of which actions are permitted for this user */ public static function getActions($user_id = null) { @@ -65,9 +65,9 @@ public static function getActions($user_id = null) ); foreach ($actions as $action) { + /** @var \Joomla\CMS\User\User $user */ $result->set($action, $user->authorise($action, $assetName)); } - return $result; } diff --git a/attachments_component/admin/src/Helper/ImportFromCSV.php b/attachments_component/admin/src/Helper/ImportFromCSV.php index 7a67913a..bf8d4be5 100644 --- a/attachments_component/admin/src/Helper/ImportFromCSV.php +++ b/attachments_component/admin/src/Helper/ImportFromCSV.php @@ -199,7 +199,7 @@ public function readNextRecord() // Keep reading until we get a non-blank line while (!feof($this->file)) { // Read the line - $this->data = fgetcsv($this->file); + $this->data = fgetcsv($this->file, escape: '\\'); $this->line_number += 1; // Do we have data? @@ -290,7 +290,7 @@ protected function parseFieldNames($file) $bad_fields = array(); // Load the field names from the file - $header_line = fgetcsv($file); + $header_line = fgetcsv($file, escape: '\\'); $this->line_number += 1; for ($i = 0; $i < count($header_line); $i++) { $field_name = strtolower(trim($header_line[$i])); diff --git a/attachments_component/attachments.xml b/attachments_component/attachments.xml index e4dc0fa5..b4bf9ddd 100644 --- a/attachments_component/attachments.xml +++ b/attachments_component/attachments.xml @@ -176,8 +176,6 @@ index.html css/index.html - css/add_attachment_button.css - css/add_attachment_button_rtl.css css/attachments_admin.css css/attachments_admin_dark.css css/attachments_admin_form.css @@ -193,9 +191,6 @@ css/attachments_list.css css/attachments_list_dark.css css/attachments_list_rtl.css - css/attachments_quickicon.css - css/insert_attachments_token_button.css - css/insert_attachments_token_button_rtl.css images/index.html images/add_attachment.gif images/add_attachment_button.png diff --git a/attachments_component/install.attachments.php b/attachments_component/install.attachments.php index 9e96b9b0..20ce1a08 100644 --- a/attachments_component/install.attachments.php +++ b/attachments_component/install.attachments.php @@ -200,27 +200,6 @@ public function preflight(string $type, InstallerAdapter $adapter): bool return false; } - // If there is debris from a previous failed attempt to install Attachments, delete it - // NOTE: Creating custom query because using JComponentHelper::isEnabled insists on - // printing a warning if the component is not installed - $db = Factory::getContainer()->get('DatabaseDriver'); - $query = $db->getQuery(true); - $query->select('extension_id AS id, enabled'); - $query->from('#__extensions'); - $query->where($query->qn('type') . ' = ' . $db->quote('component')); - $query->where($query->qn('element') . ' = ' . $db->quote('com_attachments')); - $db->setQuery($query); - if ($db->loadResult() == 0) { - if ( - Folder::exists(JPATH_ROOT . '/components/com_attachments') - or Folder::exists(JPATH_ROOT . '/administrator/components/com_attachments') - ) { - $msg = Text::_('ATTACH_ERROR_UINSTALL_OLD_VERSION'); - $app->enqueueMessage($msg, 'error'); - return false; - } - } - // Temporarily move the attachments directory out of the way to avoid conflicts $attachdir = JPATH_ROOT . '/attachments'; if (Folder::exists($attachdir)) { diff --git a/composer.json b/composer.json index fa5eb968..0248141b 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,33 @@ { "require-dev": { - "phpunit/phpunit": "^10.3", - "phpcompatibility/php-compatibility": "^10.0@dev" + "phpunit/phpunit": "^10.0", + "phpcompatibility/php-compatibility": "^9.0", + "joomla/test": "^4.0", + "joomla/string": "^4.0", + "joomla/registry": "^4.0", + "joomla/database": "^4.0", + "joomla/application": "^4.0", + "joomla/di": "^4.0", + "joomla/session": "^4.0", + "joomla/language": "^4.0", + "joomla/filter": "^4.0", + "joomla/filesystem": "^4.1", + "joomla/input": "^4.0", + "joomla/uri": "^4.0", + "algo26-matthias/idna-convert": "^4.2" + }, + "autoload": { + "psr-4": { + "JMCameron\\Component\\Attachments\\Administrator\\": "attachments_component/admin/src/", + "JMCameron\\Component\\Attachments\\Site\\": "attachments_component/site/src/", + "JMCameron\\Plugin\\AttachmentsPluginFramework\\": "attachments_plugin_framework/src" + } }, "autoload-dev": { - "Tests\\": "tests", - "JMCameron\\Component\\Attachments\\Administrator\\": "attachments_component/admin/src/", - "JMCameron\\Component\\Attachments\\Site\\": "attachments_component/site/src/" + "psr-4": { + "Tests\\": "tests/", + "Joomla\\CMS\\": "temp/joomla/libraries/src/" + } }, "config": { "allow-plugins": { @@ -14,6 +35,8 @@ } }, "scripts": { - "check-compatibility": "phpcs -p . --standard=PHPCompatibility --runtime-set testVersion 7.4- --ignore=*/vendor/*,temp/*" + "check-compatibility": "phpcs -p . --standard=PHPCompatibility --runtime-set testVersion 7.4- --ignore=*/vendor/*,temp/*", + "test": "phpunit -c ./phpunit.xml || pwd", + "test-coverage": "phpunit -c phpunit.xml --coverage-html ./tests/coverage-report" } } diff --git a/composer.lock b/composer.lock index 4106a965..7694c774 100644 --- a/composer.lock +++ b/composer.lock @@ -4,99 +4,991 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "67e137657ce526e75ee7b279e377073d", + "content-hash": "73f1f5b2a87cc68f89672f5df4dc6534", "packages": [], "packages-dev": [ { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.0.0", + "name": "algo26-matthias/idna-convert", + "version": "v4.2.1", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "4be43904336affa5c2f70744a348312336afd0da" + "url": "https://github.com/algo26-matthias/idna-convert.git", + "reference": "bc263b9d9dd6bf82845cda763a4abea3481d0f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", - "reference": "4be43904336affa5c2f70744a348312336afd0da", + "url": "https://api.github.com/repos/algo26-matthias/idna-convert/zipball/bc263b9d9dd6bf82845cda763a4abea3481d0f83", + "reference": "bc263b9d9dd6bf82845cda763a4abea3481d0f83", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + "ext-pcre": "*", + "jakeasmith/http_build_url": "^1", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^10" + }, + "suggest": { + "ext-iconv": "Install ext/iconv for using input / output other than UTF-8 or ISO-8859-1", + "ext-mbstring": "Install ext/mbstring for using input / output other than UTF-8 or ISO-8859-1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Algo26\\IdnaConvert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1+" + ], + "authors": [ + { + "name": "Matthias Sommerfeld", + "email": "matthias.sommerfeld@algo26.de", + "role": "Developer" + } + ], + "description": "A library for encoding and decoding internationalized domain names", + "homepage": "https://github.com/algo26-matthias/idna-convert", + "keywords": [ + "idn", + "idna", + "php" + ], + "support": { + "issues": "https://github.com/algo26-matthias/idna-convert/issues", + "source": "https://github.com/algo26-matthias/idna-convert/tree/v4.2.1" + }, + "time": "2025-08-11T09:03:12+00:00" + }, + { + "name": "jakeasmith/http_build_url", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/jakeasmith/http_build_url.git", + "reference": "93c273e77cb1edead0cf8bcf8cd2003428e74e37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jakeasmith/http_build_url/zipball/93c273e77cb1edead0cf8bcf8cd2003428e74e37", + "reference": "93c273e77cb1edead0cf8bcf8cd2003428e74e37", + "shasum": "" + }, + "type": "library", + "autoload": { + "files": [ + "src/http_build_url.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jake A. Smith", + "email": "theman@jakeasmith.com" + } + ], + "description": "Provides functionality for http_build_url() to environments without pecl_http.", + "support": { + "issues": "https://github.com/jakeasmith/http_build_url/issues", + "source": "https://github.com/jakeasmith/http_build_url" + }, + "time": "2017-05-01T15:36:40+00:00" + }, + { + "name": "joomla/application", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/application.git", + "reference": "878d1f4a2dc03b6197d0e6cfb4a969ec06a3314d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/application/zipball/878d1f4a2dc03b6197d0e6cfb4a969ec06a3314d", + "reference": "878d1f4a2dc03b6197d0e6cfb4a969ec06a3314d", + "shasum": "" + }, + "require": { + "joomla/event": "^4.0", + "joomla/registry": "^4.0", + "laminas/laminas-diactoros": "^3.6.0", + "php": "^8.3.0", + "psr/http-message": "^2.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "require-dev": { + "ext-json": "*", + "joomla/controller": "^4.0", + "joomla/di": "^4.0", + "joomla/input": "^4.0", + "joomla/router": "^4.0", + "joomla/session": "^4.0", + "joomla/test": "^4.0", + "joomla/uri": "^4.0", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "~3.10.2", + "symfony/phpunit-bridge": "^7.0" + }, + "suggest": { + "ext-json": "To use JSON format, ext-json is required", + "joomla/controller": "^4.0 To support resolving ControllerInterface objects in ControllerResolverInterface, install joomla/controller", + "joomla/input": "^4.0 To use WebApplicationInterface, install joomla/input", + "joomla/router": "^4.0 To use WebApplication or ControllerResolverInterface implementations, install joomla/router", + "joomla/session": "^4.0 To use SessionAwareWebApplicationInterface, install joomla/session", + "joomla/uri": "^4.0 To use AbstractWebApplication, install joomla/uri", + "psr/container": "^2.0 To use the ContainerControllerResolver, install any PSR-11 compatible container" + }, + "type": "library", + "autoload": { + "psr-4": { + "Joomla\\Application\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The Joomla! Project", + "homepage": "https://framework.joomla.org/" + } + ], + "description": "Joomla Application Package", + "homepage": "https://github.com/joomla-framework/application", + "keywords": [ + "application", + "framework", + "joomla", + "joomla-package" + ], + "support": { + "docs": "https://developer.joomla.org/framework/documentation.html", + "forum": "https://groups.google.com/g/joomla-dev-framework", + "issues": "https://github.com/joomla-framework/application/issues", + "source": "https://github.com/joomla-framework/application", + "wiki": "https://github.com/joomla-framework/application/wiki" + }, + "funding": [ + { + "url": "https://community.joomla.org/sponsorship-campaigns.html", + "type": "custom" + }, + { + "url": "https://github.com/sponsors/joomla", + "type": "github" + } + ], + "time": "2025-07-24T09:55:44+00:00" + }, + { + "name": "joomla/database", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/database.git", + "reference": "2e744e7959368891fe52ccb35790e201251f6f39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/database/zipball/2e744e7959368891fe52ccb35790e201251f6f39", + "reference": "2e744e7959368891fe52ccb35790e201251f6f39", + "shasum": "" + }, + "require": { + "joomla/event": "^4.0", + "php": "^8.3.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "require-dev": { + "colinodell/psr-testlogger": "^1.3.0", + "joomla/archive": "^4.0", + "joomla/console": "^4.0", + "joomla/di": "^4.0", + "joomla/filesystem": "^4.0", + "joomla/registry": "^4.0", + "joomla/test": "^4.0", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.0", + "psr/log": "^3.0.2", + "squizlabs/php_codesniffer": "^3.10.2", + "symfony/phpunit-bridge": "^8.0" + }, + "suggest": { + "ext-mysqli": "To connect to a MySQL database via MySQLi", + "ext-pdo": "To connect to a MySQL, PostgreSQL, or SQLite database via PDO", + "ext-sqlsrv": "To connect to a SQL Server database", + "joomla/archive": "To use the ExportCommand class, install joomla/archive", + "joomla/console": "To use the ExportCommand and ImportCommand classes, install joomla/console", + "joomla/di": "To use the Database ServiceProviderInterface objects, install joomla/di.", + "joomla/filesystem": "To use the ExportCommand and ImportCommand classes, install joomla/filesystem", + "joomla/registry": "To use the Database ServiceProviderInterface objects, install joomla/registry.", + "psr/log": "To use the LoggingMonitor, install psr/log." + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Database\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Database Package", + "homepage": "https://github.com/joomla-framework/database", + "keywords": [ + "database", + "framework", + "joomla" + ], + "support": { + "issues": "https://github.com/joomla-framework/database/issues", + "source": "https://github.com/joomla-framework/database/tree/4.0.0" + }, + "time": "2025-07-24T09:46:18+00:00" + }, + { + "name": "joomla/di", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/di.git", + "reference": "e8511111e2b5b239f75116f9ff75b43c18809868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/di/zipball/e8511111e2b5b239f75116f9ff75b43c18809868", + "reference": "e8511111e2b5b239f75116f9ff75b43c18809868", + "shasum": "" + }, + "require": { + "php": "^8.3.0", + "psr/container": "^2.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "provide": { + "psr/container-implementation": "~1.0" + }, + "require-dev": { + "joomla/test": "^4.0", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.6", + "squizlabs/php_codesniffer": "~3.7.2" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla DI Package", + "homepage": "https://github.com/joomla-framework/di", + "keywords": [ + "container", + "dependency injection", + "di", + "framework", + "ioc", + "joomla" + ], + "support": { + "issues": "https://github.com/joomla-framework/di/issues", + "source": "https://github.com/joomla-framework/di/tree/4.0.0" + }, + "time": "2025-07-24T07:51:22+00:00" + }, + { + "name": "joomla/event", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/event.git", + "reference": "4bbbfb0a3444cb3e3f9abd57d623caa06e9207b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/event/zipball/4bbbfb0a3444cb3e3f9abd57d623caa06e9207b5", + "reference": "4bbbfb0a3444cb3e3f9abd57d623caa06e9207b5", + "shasum": "" + }, + "require": { + "php": "^8.3.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "require-dev": { + "joomla/console": "^4.0", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.7", + "psr/container": "^2.0", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "suggest": { + "joomla/console": "If you want to use the DebugEventDispatcherCommand class, please install joomla/console", + "psr/container-implementation": "If you want to use the LazyServiceEventListener class, please install a PSR-11 container" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Event Package", + "homepage": "https://github.com/joomla-framework/event", + "keywords": [ + "event", + "framework", + "joomla" + ], + "support": { + "issues": "https://github.com/joomla-framework/event/issues", + "source": "https://github.com/joomla-framework/event/tree/4.0.0" + }, + "time": "2025-07-24T09:34:33+00:00" + }, + { + "name": "joomla/filesystem", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/filesystem.git", + "reference": "cfcda2935605f14f396ccda7535f4417e27af868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/filesystem/zipball/cfcda2935605f14f396ccda7535f4417e27af868", + "reference": "cfcda2935605f14f396ccda7535f4417e27af868", + "shasum": "" + }, + "require": { + "php": "^8.3.0" + }, + "require-dev": { + "joomla/test": "^4.0", + "mikey179/vfsstream": "^1.6.11", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.7", + "squizlabs/php_codesniffer": "^3.13.2" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Filesystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Filesystem Package", + "homepage": "https://github.com/joomla/joomla-framework-filesystem", + "keywords": [ + "filesystem", + "framework", + "joomla" + ], + "support": { + "issues": "https://github.com/joomla-framework/filesystem/issues", + "source": "https://github.com/joomla-framework/filesystem/tree/4.1.0" + }, + "time": "2025-08-27T18:49:28+00:00" + }, + { + "name": "joomla/filter", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/filter.git", + "reference": "fcde280785f188e93530f7da68102f7dd8f9f723" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/filter/zipball/fcde280785f188e93530f7da68102f7dd8f9f723", + "reference": "fcde280785f188e93530f7da68102f7dd8f9f723", + "shasum": "" + }, + "require": { + "joomla/string": "^4.0", + "php": "^8.3.0" + }, + "require-dev": { + "joomla/language": "^4.0", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.7", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "suggest": { + "joomla/language": "Required only if you want to use `OutputFilter::stringURLSafe`." + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Filter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Filter Package", + "homepage": "https://github.com/joomla-framework/filter", + "keywords": [ + "filter", + "framework", + "joomla" + ], + "support": { + "issues": "https://github.com/joomla-framework/filter/issues", + "source": "https://github.com/joomla-framework/filter/tree/4.0.1" + }, + "time": "2025-09-30T15:42:06+00:00" + }, + { + "name": "joomla/input", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/input.git", + "reference": "f366f97b143bd9d15f57dad74b21f72ac99d89ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/input/zipball/f366f97b143bd9d15f57dad74b21f72ac99d89ec", + "reference": "f366f97b143bd9d15f57dad74b21f72ac99d89ec", + "shasum": "" + }, + "require": { + "joomla/filter": "^4.0", + "php": "^8.3.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "require-dev": { + "joomla/test": "^4.0", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpunit/phpunit": "^12.0", + "squizlabs/php_codesniffer": "^3.10.2" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Input\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Input Package", + "homepage": "https://github.com/joomla-framework/input", + "keywords": [ + "framework", + "input", + "joomla" + ], + "support": { + "issues": "https://github.com/joomla-framework/input/issues", + "source": "https://github.com/joomla-framework/input/tree/4.0.0" + }, + "time": "2025-07-23T19:52:44+00:00" + }, + { + "name": "joomla/language", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/language.git", + "reference": "371507d84103e9e7b48253e72b3bcc8d91cf5a63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/language/zipball/371507d84103e9e7b48253e72b3bcc8d91cf5a63", + "reference": "371507d84103e9e7b48253e72b3bcc8d91cf5a63", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "joomla/string": "^4.0", + "php": "^8.3.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "require-dev": { + "joomla/di": "^4.0", + "joomla/registry": "^4.0", + "joomla/test": "^4.0", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.6", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "suggest": { + "joomla/di": "To use the Language ServiceProviderInterface objects, install joomla/di." + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Language\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Language Package", + "homepage": "https://github.com/joomla-framework/language", + "keywords": [ + "framework", + "joomla", + "language" + ], + "support": { + "issues": "https://github.com/joomla-framework/language/issues", + "source": "https://github.com/joomla-framework/language/tree/4.0.0" + }, + "time": "2025-07-23T19:48:37+00:00" + }, + { + "name": "joomla/registry", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/registry.git", + "reference": "5692d8423c9b568627df701e9fd505eae5ea8f72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/registry/zipball/5692d8423c9b568627df701e9fd505eae5ea8f72", + "reference": "5692d8423c9b568627df701e9fd505eae5ea8f72", + "shasum": "" + }, + "require": { + "joomla/utilities": "^4.0", + "php": "^8.3.0" }, "require-dev": { - "composer/composer": "*", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0", - "yoast/phpunit-polyfills": "^1.0" + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.6", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/yaml": "^7.3" }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "suggest": { + "ext-json": "ext-json is needed for JSON support", + "ext-simplexml": "ext-simplexml is needed for XML support", + "symfony/yaml": "Install symfony/yaml if you require YAML support." }, + "type": "joomla-package", "autoload": { "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "Joomla\\Registry\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "GPL-2.0-or-later" ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + "description": "Joomla Registry Package", + "homepage": "https://github.com/joomla-framework/registry", + "keywords": [ + "framework", + "joomla", + "registry" + ], + "support": { + "issues": "https://github.com/joomla-framework/registry/issues", + "source": "https://github.com/joomla-framework/registry/tree/4.0.0" + }, + "time": "2025-07-23T19:26:22+00:00" + }, + { + "name": "joomla/session", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/session.git", + "reference": "3d1d8bc093d659053c46eec4962bef4c1e9e0206" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/session/zipball/3d1d8bc093d659053c46eec4962bef4c1e9e0206", + "reference": "3d1d8bc093d659053c46eec4962bef4c1e9e0206", + "shasum": "" + }, + "require": { + "php": "^8.3.0", + "symfony/deprecation-contracts": "^2|^3" + }, + "require-dev": { + "joomla/console": "^4.0", + "joomla/database": "^4.0", + "joomla/event": "^4.0", + "joomla/input": "^4.0", + "joomla/test": "^4.0", + "joomla/utilities": "^4.0", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpunit/phpunit": "^12.0", + "squizlabs/php_codesniffer": "^3.10.2" + }, + "suggest": { + "ext-apcu": "To use APCu cache as a session handler", + "ext-memcached": "To use a Memcached server as a session handler", + "ext-redis": "To use a Redis server as a session handler", + "ext-session": "To use the Joomla\\Session\\Storage\\NativeStorage storage class.", + "ext-wincache": "To use WinCache as a session handler", + "joomla/console": "Install joomla/console if you want to use the CreateSessionTableCommand class.", + "joomla/database": "Install joomla/database if you want to use a database connection managed with Joomla\\Database\\DatabaseDriver as a session handler.", + "joomla/event": "The joomla/event package is required to use Joomla\\Session\\Session.", + "joomla/input": "The joomla/input package is required to use Address and Forwarded session validators." + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Session\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Session Package", + "homepage": "https://github.com/joomla-framework/session", + "keywords": [ + "framework", + "joomla", + "session" + ], + "support": { + "issues": "https://github.com/joomla-framework/session/issues", + "source": "https://github.com/joomla-framework/session/tree/4.0.0" + }, + "time": "2025-07-23T19:16:20+00:00" + }, + { + "name": "joomla/string", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/string.git", + "reference": "da2329e05f1f5fc98b709f8638f279513bcd1108" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/string/zipball/da2329e05f1f5fc98b709f8638f279513bcd1108", + "reference": "da2329e05f1f5fc98b709f8638f279513bcd1108", + "shasum": "" + }, + "require": { + "php": "^8.3.0", + "symfony/deprecation-contracts": "^2|^3", + "symfony/polyfill-mbstring": "^1.31.0" + }, + "require-dev": { + "doctrine/inflector": "^2.0.10", + "joomla/test": "^4.0", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpunit/phpunit": "^12.2.6", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "suggest": { + "doctrine/inflector": "To use the string inflector", + "ext-mbstring": "For improved processing" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\String\\": "src/" } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", + "description": "Joomla String Package", + "homepage": "https://github.com/joomla-framework/string", "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" + "framework", + "joomla", + "string" + ], + "support": { + "issues": "https://github.com/joomla-framework/string/issues", + "source": "https://github.com/joomla-framework/string/tree/4.0.0" + }, + "time": "2025-07-23T18:42:26+00:00" + }, + { + "name": "joomla/test", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/test.git", + "reference": "cfc3ca98f363dc76c4e3cf7a0cd089eeb93e2480" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/test/zipball/cfc3ca98f363dc76c4e3cf7a0cd089eeb93e2480", + "reference": "cfc3ca98f363dc76c4e3cf7a0cd089eeb93e2480", + "shasum": "" + }, + "require": { + "php": "^8.3.0" + }, + "require-dev": { + "joomla/database": "^4.0", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpunit/phpunit": "^12.0", + "squizlabs/php_codesniffer": "^3.10.2" + }, + "suggest": { + "joomla/database": "To use the database test case, install joomla/database", + "phpunit/phpunit": "To use the database test case, install phpunit/phpunit" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Test\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Test Helper Package", + "homepage": "https://github.com/joomla-framework/test", + "keywords": [ + "framework", + "joomla", + "phpunit", + "reflection", + "unit test" + ], + "support": { + "issues": "https://github.com/joomla-framework/test/issues", + "source": "https://github.com/joomla-framework/test/tree/4.0.1" + }, + "time": "2025-10-17T08:21:47+00:00" + }, + { + "name": "joomla/uri", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/uri.git", + "reference": "9fa4fac811bf63e830d46d83031c353cf2765bcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/uri/zipball/9fa4fac811bf63e830d46d83031c353cf2765bcd", + "reference": "9fa4fac811bf63e830d46d83031c353cf2765bcd", + "shasum": "" + }, + "require": { + "php": "^8.3.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.6", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "suggest": { + "ext-mbstring": "Used to speed up url parsing" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Uri Package", + "homepage": "https://github.com/joomla-framework/uri", + "keywords": [ + "framework", + "joomla", + "uri" + ], + "support": { + "issues": "https://github.com/joomla-framework/uri/issues", + "source": "https://github.com/joomla-framework/uri/tree/4.0.0" + }, + "time": "2025-07-23T19:02:38+00:00" + }, + { + "name": "joomla/utilities", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/joomla-framework/utilities.git", + "reference": "5f234527f7dad7111830b091aef52787c3c07cc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/joomla-framework/utilities/zipball/5f234527f7dad7111830b091aef52787c3c07cc8", + "reference": "5f234527f7dad7111830b091aef52787c3c07cc8", + "shasum": "" + }, + "require": { + "joomla/string": "^4.0", + "php": "^8.3.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^12.2.6", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "type": "joomla-package", + "autoload": { + "psr-4": { + "Joomla\\Utilities\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Joomla Utilities Package", + "homepage": "https://github.com/joomla-framework/utilities", + "keywords": [ + "framework", + "joomla", + "utilities" + ], + "support": { + "issues": "https://github.com/joomla-framework/utilities/issues", + "source": "https://github.com/joomla-framework/utilities/tree/4.0.0" + }, + "time": "2025-07-23T18:52:12+00:00" + }, + { + "name": "laminas/laminas-diactoros", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "60c182916b2749480895601649563970f3f12ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/60c182916b2749480895601649563970f3f12ec4", + "reference": "60c182916b2749480895601649563970f3f12ec4", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "conflict": { + "amphp/amp": "<2.6.4" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.1 || ^2.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^2.2.0", + "laminas/laminas-coding-standard": "~3.1.0", + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\Diactoros", + "config-provider": "Laminas\\Diactoros\\ConfigProvider" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" ], "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "source": "https://github.com/PHPCSStandards/composer-installer" + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" }, - "time": "2023-01-05T11:28:13+00:00" + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-10-12T15:31:36+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -104,11 +996,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "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", @@ -134,7 +1027,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -142,29 +1035,31 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -172,7 +1067,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -196,26 +1091,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "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", @@ -256,9 +1152,15 @@ "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.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -296,140 +1198,60 @@ { "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": "phpcompatibility/php-compatibility", - "version": "dev-develop", - "source": { - "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "34a67f7eb4ec715df26d90429ea8cce88e0b38ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/34a67f7eb4ec715df26d90429ea8cce88e0b38ff", - "reference": "34a67f7eb4ec715df26d90429ea8cce88e0b38ff", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.0.5", - "squizlabs/php_codesniffer": "^3.7.1" - }, - "replace": { - "wimg/php-compatibility": "*" - }, - "require-dev": { - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcsstandards/phpcsdevcs": "^1.1.3", - "phpcsstandards/phpcsdevtools": "^1.2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4 || ^10.1.0", - "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" - }, - "suggest": { - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." - }, - "default-branch": true, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "9.x-dev", - "dev-develop": "10.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Wim Godden", - "homepage": "https://github.com/wimg", - "role": "lead" - }, - { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" - } - ], - "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", - "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", - "keywords": [ - "compatibility", - "phpcs", - "standards", - "static analysis" + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } ], + "description": "Library for handling version information and constraints", "support": { - "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", - "security": "https://github.com/PHPCompatibility/PHPCompatibility/security/policy", - "source": "https://github.com/PHPCompatibility/PHPCompatibility" + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2023-12-04T17:28:03+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpcsstandards/phpcsutils", - "version": "1.0.8", + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", - "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", "shasum": "" }, "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" }, - "require-dev": { - "ext-filter": "*", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcsstandards/phpcsdevcs": "^1.1.6", - "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-stable": "1.x-dev", - "dev-develop": "1.x-dev" - } + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" }, - "autoload": { - "classmap": [ - "PHPCSUtils/" - ] + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, + "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ "LGPL-3.0-or-later" ], "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, { "name": "Juliette Reinders Folmer", "homepage": "https://github.com/jrfnl", @@ -437,57 +1259,50 @@ }, { "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" } ], - "description": "A suite of utility functions for use with PHP_CodeSniffer", - "homepage": "https://phpcsutils.com/", + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", "keywords": [ - "PHP_CodeSniffer", - "phpcbf", - "phpcodesniffer-standard", + "compatibility", "phpcs", - "phpcs3", - "standards", - "static analysis", - "tokens", - "utility" + "standards" ], "support": { - "docs": "https://phpcsutils.com/", - "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", - "source": "https://github.com/PHPCSStandards/PHPCSUtils" + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" }, - "time": "2023-07-16T21:39:41+00:00" + "time": "2019-12-27T09:44:58+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.4", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "cd59bb34756a16ca8253ce9b2909039c227fff71" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cd59bb34756a16ca8253ce9b2909039c227fff71", - "reference": "cd59bb34756a16ca8253ce9b2909039c227fff71", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -499,7 +1314,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -528,7 +1343,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.4" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -536,7 +1351,7 @@ "type": "github" } ], - "time": "2023-08-31T14:04:38+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -704,196 +1519,415 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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": "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.58", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca", + "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.4", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "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/10.5.58" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-09-28T12:04:46+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "template" + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/php-fig/http-factory" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { - "name": "phpunit/php-timer", - "version": "6.0.0", + "name": "psr/http-message", + "version": "2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "timer" + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { - "name": "phpunit/phpunit", - "version": "10.3.2", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0dafb1175c366dd274eaa9a625e914451506bcd1" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0dafb1175c366dd274eaa9a625e914451506bcd1", - "reference": "0dafb1175c366dd274eaa9a625e914451506bcd1", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.0", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "php": ">=8.0.0" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-main": "10.3-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "phpunit", - "testing", - "xunit" + "log", + "psr", + "psr-3" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.2" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "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": "2023-08-15T05:34:23+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { @@ -928,7 +1962,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, "funding": [ { @@ -936,7 +1971,7 @@ "type": "github" } ], - "time": "2023-02-03T06:58:15+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", @@ -1051,16 +2086,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", "shasum": "" }, "require": { @@ -1071,7 +2106,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -1116,32 +2151,44 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2025-09-07T05:25:07+00:00" }, { "name": "sebastian/complexity", - "version": "3.0.1", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "c70b73893e10757af9c6a48929fa6a333b56a97a" + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c70b73893e10757af9c6a48929fa6a333b56a97a", - "reference": "c70b73893e10757af9c6a48929fa6a333b56a97a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "nikic/php-parser": "^4.10", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { @@ -1150,7 +2197,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -1174,7 +2221,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" }, "funding": [ { @@ -1182,20 +2229,20 @@ "type": "github" } ], - "time": "2023-08-31T09:55:53+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { "name": "sebastian/diff", - "version": "5.0.3", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { @@ -1203,12 +2250,12 @@ }, "require-dev": { "phpunit/phpunit": "^10.0", - "symfony/process": "^4.2 || ^5" + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1241,7 +2288,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -1249,20 +2296,20 @@ "type": "github" } ], - "time": "2023-05-01T07:48:21+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/environment", - "version": "6.0.1", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951" + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951", - "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { @@ -1277,7 +2324,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -1305,7 +2352,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" }, "funding": [ { @@ -1313,20 +2360,20 @@ "type": "github" } ], - "time": "2023-04-11T05:39:26+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { "name": "sebastian/exporter", - "version": "5.0.0", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0" + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", - "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { @@ -1335,12 +2382,12 @@ "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1382,28 +2429,41 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2023-02-03T07:06:49+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { @@ -1437,14 +2497,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -1452,24 +2512,24 @@ "type": "github" } ], - "time": "2023-07-19T07:19:23+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d" + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/649e40d279e243d985aa8fb6e74dd5bb28dc185d", - "reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.10", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { @@ -1502,7 +2562,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.1" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" }, "funding": [ { @@ -1510,7 +2570,7 @@ "type": "github" } ], - "time": "2023-08-31T09:25:50+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { "name": "sebastian/object-enumerator", @@ -1626,23 +2686,23 @@ }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -1677,15 +2737,28 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { "name": "sebastian/type", @@ -1798,16 +2871,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.13.5", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", "shasum": "" }, "require": { @@ -1817,18 +2890,13 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -1836,35 +2904,214 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "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", + "homepage": "https://github.com/PHPCSStandards/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" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/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" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, - "time": "2023-02-22T23:07:41+00:00" + "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-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -1893,7 +3140,7 @@ "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.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -1901,17 +3148,15 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "phpcompatibility/php-compatibility": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000..48832bf1 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,69 @@ +import * as fs from "fs"; +import * as mysql from 'cypress-mysql'; +import { defineConfig } from "cypress"; + +const db = { + host: process.env.JOOMLA_DB_HOST, + user: process.env.JOOMLA_DB_USER, + password: process.env.JOOMLA_DB_PASSWORD, + database: process.env.JOOMLA_DB_NAME, +}; + +export default defineConfig({ + e2e: { + baseUrl: process.env.JOOMLA_URL, + env: { + db: db, + chromeWebSecurity: false, + }, + setupNodeEvents(on, config) { + mysql.configurePlugin(on); + + on("task", { + // Use mysql client to restore from a clean backup + resetDatabase: () => { + const { execSync } = require("child_process"); + execSync( + `mysql -h ${db.host} -u ${db.user} -p${db.password} ${db.database} < /tmp/clean_backup.sql` + ); + return null; + }, + + // Use mysqldump to create a backup of the database + dumpDatabase: () => { + const { execSync } = require("child_process"); + execSync( + `mysqldump -h ${db.host} -u ${db.user} -p${db.password} ${db.database} > /tmp/clean_backup.sql` + ); + return null; + }, + + // Find the built attachment file in the specified directory + findAttachmentFile(directory) { + const files = fs.readdirSync(directory); + const attachmentFile = files.find( + (file) => file.startsWith("attachments-") && file.endsWith(".zip") + ); + return attachmentFile || null; + }, + + // Run make to build the Joomla extension package + makePackage: () => { + const { execSync } = require("child_process"); + execSync( + "cd /app && make veryclean && make && mv attachments-*.zip /app/cypress/fixtures/" + ); + return null; + }, + + clearAttachmentsDir() { + const attachmentsDir = "/var/www/html/attachments"; + if (fs.existsSync(attachmentsDir)) { + fs.rmSync(attachmentsDir, { recursive: true, force: true }); + } + return null; + } + }); + }, + }, +}); diff --git a/cypress/e2e/0-sanity-checks/joomlaIsReady.cy.ts b/cypress/e2e/0-sanity-checks/joomlaIsReady.cy.ts new file mode 100644 index 00000000..1df168dd --- /dev/null +++ b/cypress/e2e/0-sanity-checks/joomlaIsReady.cy.ts @@ -0,0 +1,21 @@ +import "../../support" + +describe("Joomla is ready", () => { + it("should load the Joomla admin login page", () => { + cy.visit("/administrator/"); + cy.get("#form-login").should("be.visible"); + }); + + it("should log in to the Joomla admin panel", () => { + cy.adminLogin().visit("/administrator/"); + cy.get(".admin.com_cpanel").should("be.visible"); + }); + + it("should log out from the Joomla admin panel", () => { + cy.adminLogin().visit("/administrator/"); + cy.get(".admin.com_cpanel").should("be.visible"); + + cy.doAdministratorLogout(); + cy.get("#form-login").should("be.visible"); + }); +}); diff --git a/cypress/e2e/1-attachments-install/installationTests.cy.ts b/cypress/e2e/1-attachments-install/installationTests.cy.ts new file mode 100644 index 00000000..7a77542c --- /dev/null +++ b/cypress/e2e/1-attachments-install/installationTests.cy.ts @@ -0,0 +1,118 @@ +import "../../support" + +describe("Attachments package installation tests", () => { + it("should appear in the installed extensions list after installation", () => { + const extensionName = "Attachments"; + + // Log in as administrator + cy.adminLogin(); + + cy.isExtensionInstalled('com_attachments').then((result) => { + if (result.length > 0) { + cy.log(`Extension ${extensionName} is already installed, uninstalling before reinstalling.`); + cy.uninstallExtension(`Package: ${extensionName}`); + } else { + cy.log(`Extension ${extensionName} is not currently installed, proceeding with installation test.`); + } + }); + + // Verify that no extension with this name exists in the installed extensions list + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem(extensionName); + cy.contains(extensionName).should("not.exist"); + + // Install the Attachments extension from a local file + cy.task("findAttachmentFile", "/app/cypress/fixtures").then((filename) => { + if (filename) { + cy.log(`Found file: ${filename}`); + // Use the filename for further operations + cy.installExtensionFromFileUpload(filename); + } else { + throw new Error("Attachment file not found"); + } + }); + + // Verify that the extension appears in the installed extensions list + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem(extensionName); + cy.contains(extensionName).should("exist"); + // Verify that there are 11 items listed (Attachments component, 9 plugins and 1 package) + cy.get('tbody > tr').should('have.length', 11); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Component")').should('have.length', 1); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Package")').should('have.length', 1); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Plugin")').should('have.length', 9); + }); + + it("should reinstall without errors", () => { + const extensionName = "Attachments"; + + // Log in as administrator + cy.adminLogin(); + + cy.isExtensionInstalled('com_attachments').then((result) => { + cy.log(result); + if (result.length > 0) { + cy.log(`Extension ${extensionName} is already installed, uninstalling before reinstalling.`); + cy.uninstallExtension(`Package: ${extensionName}`); + } else { + cy.log(`Extension ${extensionName} is not currently installed, proceeding with installation test.`); + } + }); + + // Verify that no extension with this name exists in the installed extensions list + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem(extensionName); + cy.contains(extensionName).should("not.exist"); + + // Install the Attachments extension from a local file + cy.task("findAttachmentFile", "/app/cypress/fixtures").then((filename) => { + if (filename) { + cy.log(`Found file: ${filename}`); + // Use the filename for further operations + cy.installExtensionFromFileUpload(filename); + } else { + throw new Error("Attachment file not found"); + } + }); + + // Verify that the extension appears in the installed extensions list + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem(extensionName); + cy.contains(extensionName).should("exist"); + + // Verify that there are 11 items listed (Attachments component, 9 plugins and 1 package) + cy.get('tbody > tr').should('have.length', 11); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Component")').should('have.length', 1); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Package")').should('have.length', 1); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Plugin")').should('have.length', 9); + + // Clean up by uninstalling the extension + cy.uninstallExtension(`Package: ${extensionName}`); + + // Reinstall a second time to verify no errors occur + cy.task("findAttachmentFile", "/app/cypress/fixtures").then((filename) => { + if (filename) { + cy.log(`Found file: ${filename}`); + // Use the filename for further operations + cy.installExtensionFromFileUpload(filename); + } else { + throw new Error("Attachment file not found"); + } + }); + + // Verify that the extension appears in the installed extensions list + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem(extensionName); + cy.contains(extensionName).should("exist"); + + // Verify that there are 11 items listed (Attachments component, 9 plugins and 1 package) + cy.get('tbody > tr').should('have.length', 11); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Component")').should('have.length', 1); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Package")').should('have.length', 1); + cy.get('tbody > tr > :nth-child(5)').filter(':contains("Plugin")').should('have.length', 9); + }); +}).beforeAll(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); +});; diff --git a/cypress/e2e/10-plugins-insert-attachments-id-token-btn/insertAttachmentsIdTokenBtnPluginTests.cy.ts b/cypress/e2e/10-plugins-insert-attachments-id-token-btn/insertAttachmentsIdTokenBtnPluginTests.cy.ts new file mode 100644 index 00000000..5b0a0b3c --- /dev/null +++ b/cypress/e2e/10-plugins-insert-attachments-id-token-btn/insertAttachmentsIdTokenBtnPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Insert Attachments ID Token Button plugin (plg_editors-xtd_insert_attachments_id_token_btn) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Editor Button - Insert Attachments ID Token"); + cy.contains("Editor Button - Insert Attachments ID Token").should("exist"); + }); + + it("should be in the editors-xtd plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'editors-xtd'); + cy.searchForItem("Editor Button - Insert Attachments ID Token"); + cy.contains("Editor Button - Insert Attachments ID Token").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/11-plugins-show-attachments-in-editor/showAttachmentsInEditorPluginTests.cy.ts b/cypress/e2e/11-plugins-show-attachments-in-editor/showAttachmentsInEditorPluginTests.cy.ts new file mode 100644 index 00000000..a7c47c29 --- /dev/null +++ b/cypress/e2e/11-plugins-show-attachments-in-editor/showAttachmentsInEditorPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Show Attachments in Editor plugin (plg_system_show_attachments_in_editor) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("System - Show attachments in editor"); + cy.contains("System - Show attachments in editor").should("exist"); + }); + + it("should be in the system plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'system'); + cy.searchForItem("System - Show attachments in editor"); + cy.contains("System - Show attachments in editor").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/2-attachments-component/attachmentsComponentTests.cy.ts b/cypress/e2e/2-attachments-component/attachmentsComponentTests.cy.ts new file mode 100644 index 00000000..4fd84ed0 --- /dev/null +++ b/cypress/e2e/2-attachments-component/attachmentsComponentTests.cy.ts @@ -0,0 +1,82 @@ +import "../../support" + +describe("Attachments component tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should install and display in the admin menu", () => { + cy.visit("/administrator/"); + cy.get(".item-level-2 > a > .sidebar-item-title").should("contain", "Attachments"); + cy.get(".item-level-3 > a > .sidebar-item-title").should("contain", "Attachments"); + cy.get(".item-level-3 > a > .sidebar-item-title").should("contain", "Add new attachment"); + cy.get(".item-level-3 > a > .sidebar-item-title").should("contain", "Options"); + }); + + it("should be accessible from the admin menu", () => { + cy.visit("/administrator/index.php?option=com_attachments"); + cy.get("h1").should("contain", "Attachments"); + }); + + it("should have an accessible 'Add new attachment' page", () => { + cy.visit("/administrator/index.php?option=com_attachments&task=attachment.add"); + cy.get("h1").should("contain", "Add attachment"); + }); + + it("should be able to add attachments to an article through the editor", () => { + cy.removeAllArticles(); + cy.removeAllAttachments(); + + cy.dbEnableExtension("com_attachments"); + cy.dbEnableExtension("plg_attachments_plugin_framework"); + cy.dbEnableExtension("plg_system_show_attachments_in_editor"); + cy.dbEnableExtension("plg_editors-xtd_add_attachment_btn"); + cy.isExtensionInstalled("com_attachments").should("have.length", 1); + cy.isExtensionInstalled("plg_attachments_plugin_framework").should("have.length", 1); + cy.isExtensionInstalled("plg_system_show_attachments_in_editor").should("have.length", 1); + cy.isExtensionInstalled("plg_editors-xtd_add_attachment_btn").should("have.length", 1); + + cy.addArticleWithAttachment(); + }); + + it("should be able to add attachments to an article though the component", () => { + cy.removeAllArticles(); + cy.removeAllAttachments(); + + cy.dbEnableExtension("com_attachments"); + cy.dbEnableExtension("plg_attachments_plugin_framework"); + cy.dbEnableExtension("plg_system_show_attachments_in_editor"); + cy.isExtensionInstalled("com_attachments").should("have.length", 1); + cy.isExtensionInstalled("plg_attachments_plugin_framework").should("have.length", 1); + cy.isExtensionInstalled("plg_system_show_attachments_in_editor").should("have.length", 1); + + cy.addArticleWithoutAttachment().then((articleId) => { + cy.visit("/administrator/index.php?option=com_attachments&task=attachment.add"); + cy.get("button[data-bs-target='#modal-attachment']").click(); + cy.get(".iframe") + .iframe() + .then($iframeBody => { + cy.wait(1000); // Wait for the iframe to load and the article list to be populated + cy.wrap($iframeBody).find(`a[data-id="${articleId}"]`).click(); + }); + cy.get("#upload").attachFile("test-image.png", { subjectType: "input" }); + cy.clickToolbarButton("save & close"); + cy.get(".alert-message").should("contain", "Uploaded attachment"); + cy.visit(`/administrator/index.php?option=com_content&task=article.edit&id=${articleId}`); + cy.get(".attachmentsList").should("contain", "test-image.png"); + cy.clickToolbarButton("cancel"); + }); + }); + + it("should display the configuration page", () => { + cy.visit("/administrator/index.php?option=com_attachments&task=params.edit"); + cy.get("h1").should("contain", "Options"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/3-plugins-content-attachments/contentPluginTests.cy.ts b/cypress/e2e/3-plugins-content-attachments/contentPluginTests.cy.ts new file mode 100644 index 00000000..4503ad64 --- /dev/null +++ b/cypress/e2e/3-plugins-content-attachments/contentPluginTests.cy.ts @@ -0,0 +1,54 @@ +import "../../support" + +describe("Content plugin (plg_content_attachments) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Content - Attachments"); + cy.contains("Content - Attachments").should("exist"); + }); + + it("should be in the content plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'content'); + cy.searchForItem("Content - Attachments"); + cy.contains("Content - Attachments").should("exist"); + }); + + it("should be able to enable the plugin", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.searchForItem("Content - Attachments"); + + cy.isExtensionInstalled('plg_content_attachments').then((result) => { + if (Array.isArray(result) && result.length > 0) { + cy.get("#cb0").click(); + cy.log(`Plugin is already enabled.`); + // cy.clickToolbarButton("disable"); + cy.get("#toolbar-unpublish button").click(); + cy.checkForSystemMessage("disabled"); + } else { + cy.log(`Plugin is currently disabled, enabling it.`); + } + }); + + cy.get("#cb0").click(); + cy.clickToolbarButton("enable"); + cy.checkForSystemMessage("enabled"); + }); + + it("should display correct plugin description", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.searchForItem("Content - Attachments"); + cy.get("tr.row0").should("contain", "Attachments"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/4-plugins-quickicon-attachments/quickiconPluginTests.cy.ts b/cypress/e2e/4-plugins-quickicon-attachments/quickiconPluginTests.cy.ts new file mode 100644 index 00000000..7208bab4 --- /dev/null +++ b/cypress/e2e/4-plugins-quickicon-attachments/quickiconPluginTests.cy.ts @@ -0,0 +1,48 @@ +import "../../support" + +describe("Quickicon plugin (plg_quickicon_attachments) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Quick Icon - Attachments"); + cy.contains("Quick Icon - Attachments").should("exist"); + }); + + it("should be in the quickicon plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'quickicon'); + cy.searchForItem("Quick Icon - Attachments"); + cy.contains("Quick Icon - Attachments").should("exist"); + }); + + it("should be able to enable the plugin", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.searchForItem("Quick Icon - Attachments"); + + cy.isExtensionInstalled('plg_quickicon_attachments').then((result) => { + if (Array.isArray(result) && result.length > 0) { + cy.get("#cb0").click(); + cy.log(`Plugin is already enabled.`); + // cy.clickToolbarButton("disable"); + cy.get("#toolbar-unpublish button").click(); + cy.checkForSystemMessage("disabled"); + } else { + cy.log(`Plugin is currently disabled, enabling it.`); + } + }); + + cy.get("#cb0").click(); + cy.clickToolbarButton("enable"); + cy.checkForSystemMessage("enabled"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/5-plugins-attachments-for-content/attachmentsForContentPluginTests.cy.ts b/cypress/e2e/5-plugins-attachments-for-content/attachmentsForContentPluginTests.cy.ts new file mode 100644 index 00000000..c33f3101 --- /dev/null +++ b/cypress/e2e/5-plugins-attachments-for-content/attachmentsForContentPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Attachments for Content plugin (plg_attachments_attachments_for_content) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Attachments for Content"); + cy.contains("Attachments for Content").should("exist"); + }); + + it("should be in the attachments plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'attachments'); + cy.searchForItem("Attachments for Content"); + cy.contains("Attachments for Content").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/5-plugins-attachments-for-content/pluginFrameworkPluginTests.cy.ts b/cypress/e2e/5-plugins-attachments-for-content/pluginFrameworkPluginTests.cy.ts new file mode 100644 index 00000000..94f0a53c --- /dev/null +++ b/cypress/e2e/5-plugins-attachments-for-content/pluginFrameworkPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Plugin Framework plugin (plg_attachments_plugin_framework) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Attachments - Plugin Framework"); + cy.contains("Attachments - Plugin Framework").should("exist"); + }); + + it("should be in the attachments plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'attachments'); + cy.searchForItem("Attachments - Plugin Framework"); + cy.contains("Attachments - Plugin Framework").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/6-plugins-finder-attachments/finderPluginTests.cy.ts b/cypress/e2e/6-plugins-finder-attachments/finderPluginTests.cy.ts new file mode 100644 index 00000000..bd1f13bf --- /dev/null +++ b/cypress/e2e/6-plugins-finder-attachments/finderPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Finder plugin (plg_finder_attachments) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Smart Search - Attachments"); + cy.contains("Smart Search - Attachments").should("exist"); + }); + + it("should be in the finder plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'finder'); + cy.searchForItem("Smart Search - Attachments"); + cy.contains("Smart Search - Attachments").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/7-plugins-search-attachments/searchPluginTests.cy.ts b/cypress/e2e/7-plugins-search-attachments/searchPluginTests.cy.ts new file mode 100644 index 00000000..200431c3 --- /dev/null +++ b/cypress/e2e/7-plugins-search-attachments/searchPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Search plugin (plg_search_attachments) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Search - Attachments"); + cy.contains("Search - Attachments").should("exist"); + }); + + it("should be in the search plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'search'); + cy.searchForItem("Search - Attachments"); + cy.contains("Search - Attachments").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/8-plugins-add-attachment-btn/addAttachmentBtnPluginTests.cy.ts b/cypress/e2e/8-plugins-add-attachment-btn/addAttachmentBtnPluginTests.cy.ts new file mode 100644 index 00000000..063247ea --- /dev/null +++ b/cypress/e2e/8-plugins-add-attachment-btn/addAttachmentBtnPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Add Attachment Button plugin (plg_editors-xtd_add_attachment_btn) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Editor Button - Add Attachment"); + cy.contains("Editor Button - Add Attachment").should("exist"); + }); + + it("should be in the editors-xtd plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'editors-xtd'); + cy.searchForItem("Editor Button - Add Attachment"); + cy.contains("Editor Button - Add Attachment").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/e2e/9-plugins-insert-attachments-token-btn/insertAttachmentsTokenBtnPluginTests.cy.ts b/cypress/e2e/9-plugins-insert-attachments-token-btn/insertAttachmentsTokenBtnPluginTests.cy.ts new file mode 100644 index 00000000..a3b24fde --- /dev/null +++ b/cypress/e2e/9-plugins-insert-attachments-token-btn/insertAttachmentsTokenBtnPluginTests.cy.ts @@ -0,0 +1,27 @@ +import "../../support" + +describe("Insert Attachments Token Button plugin (plg_editors-xtd_insert_attachments_token_btn) tests", () => { + before(() => { + cy.task("makePackage").then(() => { + cy.log("Package created"); + }); + }); + + beforeEach(() => { + cy.adminLogin(); + cy.installAttachmentsIfNeeded(); + }); + + it("should appear in the extensions list", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Editor Button - Insert Attachments Token"); + cy.contains("Editor Button - Insert Attachments Token").should("exist"); + }); + + it("should be in the editors-xtd plugin group", () => { + cy.visit("/administrator/index.php?option=com_plugins"); + cy.setFilter('folder', 'editors-xtd'); + cy.searchForItem("Editor Button - Insert Attachments Token"); + cy.contains("Editor Button - Insert Attachments Token").should("exist"); + }); +}); \ No newline at end of file diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 00000000..02e42543 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/fixtures/test-image.png b/cypress/fixtures/test-image.png new file mode 100644 index 00000000..1095f645 Binary files /dev/null and b/cypress/fixtures/test-image.png differ diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 00000000..3af1a9b5 --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,29 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +import "cypress-file-upload"; +import * as mysql from 'cypress-mysql'; +mysql.addCommands(); diff --git a/cypress/support/common.ts b/cypress/support/common.ts new file mode 100644 index 00000000..0f28f40a --- /dev/null +++ b/cypress/support/common.ts @@ -0,0 +1,21 @@ +declare global { + namespace Cypress { + interface Chainable { + // joomla-cypress function declarations - user.js + + /** + * Wait for iframe to load, and call callback + * + * Some hints taken and adapted from: + * https://gitlab.com/kgroat/cypress-iframe/-/blob/master/src/index.ts + */ + iframe($iframes?: HTMLIFrameElement[]): Chainable + } + } +} + +import { commonCommands } from 'joomla-cypress/src/common' + +commonCommands(); + +export {} \ No newline at end of file diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000..68e7e038 --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,181 @@ +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import "./commands"; + +before(() => { + // Disable the Joomla! Statistics plugin to avoid issues with tests + cy.dbDisableExtension('plg_system_stats').then(() => { + cy.log("Disabled System - Joomla! Statistics plugin"); + }); + // Disable the EOS Quickicon plugin to avoid issues with tests + cy.dbDisableExtension('plg_quickicon_eos').then(() => { + cy.log("Disabled EOS Quickicon plugin"); + }); + + // Dump the database to create a clean backup before any test runs + // cy.task("dumpDatabase"); +}); + +beforeEach(() => { + // Reset the database before each test + // cy.task("resetDatabase"); + Cypress.session.clearAllSavedSessions(); +}); + +Cypress.Commands.add("adminLogin", () => { + return cy.doAdministratorLogin( + Cypress.env("JOOMLA_ADMIN_USERNAME"), + Cypress.env("JOOMLA_ADMIN_PASSWORD") + ); +}); + +// Disable an extension via direct database query +Cypress.Commands.add("dbDisableExtension", (extensionName) => { + cy.log('Extension Name: ' + extensionName) + const query = `UPDATE joom_extensions SET enabled = 0 WHERE name = ?;`; + return cy.query(query, [extensionName]); +}); + +Cypress.Commands.add("dbEnableExtension", (extensionName) => { + cy.log('Extension Name: ' + extensionName) + const query = `UPDATE joom_extensions SET enabled = 1 WHERE name = ?;`; + return cy.query(query, [extensionName]); +}); + +Cypress.Commands.add("isExtensionInstalled", (extensionName) => { + cy.log('is extension ' + extensionName + ' installed?') + const query = 'SELECT * FROM joom_extensions WHERE name = ? AND enabled = 1'; + return cy.query(query, [extensionName]); +}); + +Cypress.Commands.add("installAttachmentsIfNeeded", () => { + cy.visit("/administrator/index.php?option=com_installer&view=manage"); + cy.searchForItem("Attachments"); + cy.get("body").then(($body) => { + if ($body.find('tbody > tr').length > 0 && $body.text().includes("Attachments")) { + cy.log("Attachments already installed"); + } else { + cy.task("findAttachmentFile", "/app/cypress/fixtures").then((filename) => { + if (filename) { + cy.log(`Found file: ${filename}`); + cy.installExtensionFromFileUpload(filename); + } + }); + } + }); +}); + +Cypress.Commands.add("removeAllArticles", () => { + const query = 'DELETE FROM joom_content'; + return cy.query(query); +}); + +Cypress.Commands.add("removeAllAttachments", () => { + cy.task("clearAttachmentsDir"); + + const query = 'DELETE FROM joom_attachments'; + return cy.query(query); +}); + +Cypress.Commands.add("showAddAttachmentDialogThroughEditor", () => { + cy.get('body').then(($body) => { + if ($body.find('button.tox-tbtn').length > 0) { + // Joomla 4.4+ with TinyMCE 6 + cy.log("Found editor toolbar button"); + cy.get('button.tox-tbtn').contains("CMS Content").click({ + waitForAnimations: false, + }); + cy.get('div[title="Add attachment"]').click(); + } else { + throw new Error("Editor toolbar button not found"); + } + }); +}); + +Cypress.Commands.add("setEditorContent", (content: string) => { + cy.window().then((win) => { + if (win.tinyMCE) { + win.tinyMCE.activeEditor.setContent(content); + } else { + throw new Error("TinyMCE not found"); + } + }); +}); + +Cypress.Commands.add("addArticleWithAttachment", (title: string = "Test Article for Attachments", content: string = "This is a test article to verify attachments functionality.", attachmentPath: string = "test-image.png") => { + // Create a new article + cy.visit("/administrator/index.php?option=com_content&task=article.add"); + cy.get("#jform_title").type(title); + cy.setEditorContent(content); + + // Go to the attachments tab and add an attachment + cy.showAddAttachmentDialogThroughEditor(); + cy.wait(1000); // Wait for the upload to complete and the page to update + cy.get(".iframe").then(($iframe) => { + const $body = $iframe.contents().find("body"); + cy.wrap($body) + .find("#upload") + .attachFile(attachmentPath, { subjectType: "input" }); + cy.wrap($body) + .find("form.attachmentsBackend") + .contains("button", "Upload") + .click(); + }); + + // Verify the attachment is listed + cy.get(".attachmentsList").should("contain", "test-image.png"); + + cy.clickToolbarButton("save"); + cy.get(".alert-message").should("contain", "Article saved"); + + // Verify the attachment is still listed + cy.get(".attachmentsList").should("contain", "test-image.png"); + + cy.clickToolbarButton("cancel"); + + cy.query("SELECT id FROM joom_content order by id desc limit 1").then((result) => { + const articleId = result[0].id; + cy.wrap(articleId).as("articleId"); + }); + + return cy.get("@articleId"); + }); + +Cypress.Commands.add("addArticleWithoutAttachment", (title: string = "Test Article for Attachments", content: string = "This is a test article to verify attachments functionality.") => { + // Create a new article + cy.visit("/administrator/index.php?option=com_content&task=article.add"); + cy.get("#jform_title").type(title); + cy.setEditorContent(content); + + cy.clickToolbarButton("save & close"); + cy.get(".alert-message").should("contain", "Article saved"); + cy.query("SELECT id FROM joom_content order by id desc limit 1").then((result) => { + const articleId = result[0].id; + cy.wrap(articleId).as("articleId"); + }); + + return cy.get("@articleId"); +}); + +// Fix for "Cannot read properties of undefined (reading 'addEventListener')" error caused by Joomla's toolbar in 4.4 +Cypress.on('uncaught:exception', (err, runnable) => { + // returning false prevents Cypress from failing the test + if (err.message.includes('Cannot read properties of undefined (reading \'addEventListener\')')) { + console.log('Caught expected exception:', err.message); + return false + } +}) \ No newline at end of file diff --git a/cypress/support/extensions.ts b/cypress/support/extensions.ts new file mode 100644 index 00000000..695edea8 --- /dev/null +++ b/cypress/support/extensions.ts @@ -0,0 +1,22 @@ +declare global { + namespace Cypress { + interface Chainable { + // joomla-cypress function declarations - extensions.js + installExtensionFromFolder(path: string, type?: string): Chainable + installExtensionFromUrl(url: string, type?: string): Chainable + installExtensionFromFileUpload(file: FixtureData, type?: string): Chainable + uninstallExtension(extensionName: string): Chainable + installLanguage(languageName: string): Chainable + enablePlugin(pluginName: string): Chainable + setModulePosition(module: string, position?: string): Chainable + publishModule(module: string): Chainable + displayModuleOnAllPages(module: string): Chainable + } + } +} + +import { extensionsCommands } from 'joomla-cypress/src/extensions' + +extensionsCommands(); + +export {} \ No newline at end of file diff --git a/cypress/support/index.ts b/cypress/support/index.ts new file mode 100644 index 00000000..ca59fac4 --- /dev/null +++ b/cypress/support/index.ts @@ -0,0 +1,25 @@ +import "./common" +import "./extensions" +import "./joomla" +import "./support" +import "./user" + +declare global { + namespace Cypress { + interface Chainable { + dbDisableExtension(extensionName: string): Chainable + dbEnableExtension(extensionName: string): Chainable + isExtensionInstalled(extensionName: string): Chainable + installAttachmentsIfNeeded(): Chainable + removeAllArticles(): Chainable + removeAllAttachments(): Chainable + setEditorContent(content: string): Chainable + showAddAttachmentDialogThroughEditor(): Chainable + adminLogin(): Chainable + addArticleWithAttachment(title?: string, content?: string, attachmentPath?: string): Chainable + addArticleWithoutAttachment(title?: string, content?: string): Chainable + } + } +} + +export {} \ No newline at end of file diff --git a/cypress/support/joomla.ts b/cypress/support/joomla.ts new file mode 100644 index 00000000..af3088e8 --- /dev/null +++ b/cypress/support/joomla.ts @@ -0,0 +1,33 @@ +export type JoomlaInstallConfig = { + sitename: string; + name: string; + username: string; + password: string; + email: string; + db_host: string; + db_port?: string; + db_type: string; + db_user: string; + db_password: string; + db_name: string; + db_prefix: string; +} + +declare global { + namespace Cypress { + interface Chainable { + // joomla-cypress function declarations - joomla.js + installJoomla(config: JoomlaInstallConfig): Chainable + cancelTour(): Chainable + disableStatistics(): Chainable + setErrorReportingToDevelopment(): Chainable + installJoomlaMultilingualSite(config: JoomlaInstallConfig, languages?: string[]): Chainable + } + } +} + +import { joomlaCommands } from 'joomla-cypress/src/joomla' + +joomlaCommands(); + +export {} \ No newline at end of file diff --git a/cypress/support/support.ts b/cypress/support/support.ts new file mode 100644 index 00000000..0c0c73ba --- /dev/null +++ b/cypress/support/support.ts @@ -0,0 +1,150 @@ +type JoomlaToolbarButton = "new" | + "enable" | + "publish" | + "unpublish" | + "archive" | + "check-in" | + "batch" | + "rebuild" | + "trash" | + "save" | + "save & close" | + "save & new" | + "cancel" | + "options" | + "empty trash" | + "delete" | + "feature" | + "unfeature" | + "action" | + "transition" | + "versions" + +declare global { + namespace Cypress { + interface Chainable { + // joomla-cypress function declarations - support.js + + /** + * Clicks on a button in the toolbar + * + * @memberof cy + * @method clickToolbarButton + * @param {string} button + * @param {string} subselector + * @returns Chainable + */ + clickToolbarButton(button: JoomlaToolbarButton, subselector?): Chainable + + /** + * Check for PHP notices and warnings in the HTML page source. + * This command looks for keywords such as 'Deprecated' in bold followed by a colon. + * If such a styled keyword is found, the test fails with the PHP problem message. + * + * Looking for: + * - Warning: + * - Deprecated: + * - Notice: + * - Strict standards: + * + * @memberof Cypress.Commands + * @method checkForPhpNoticesOrWarnings + * @returns {void} - It does not return any value, but it is chainable with other Cypress commands. + */ + checkForPhpNoticesOrWarnings(): Chainable + + /** + * @memberof cy + * @method checkForSystemMessage + * @param {string} contain - what we are looking for, e.g. 'published' + * @returns Chainable + */ + checkForSystemMessage(contain: string): Chainable + + /** + * Search for an item + * TODO: deletes search field doesn't make sense to me in this context; RD) + * + * @memberof cy + * @method searchForItem + * @param {string} name + * @returns Chainable + */ + searchForItem(name?: string): Chainable + + /** + * set filter on list view + * + * @memberof cy + * @method setFilter + * @param {string} name + * @param {string} value + * @returns Chainable + */ + setFilter(name: string, value: string): Chainable + + /** + * Check all filtered results + * + * @memberof cy + * @method checkAllResults + * @returns Chainable + */ + checkAllResults(): Chainable + + /** + * Custom Cypress command to create a menu item in Joomla. + * + * @memberof Cypress.Commands + * @method createMenuItem + * @param {string} menuTitle – The title of the menu item to be created. + * @param {string} menuCategory – The category of the menu item. + * @param {string} menuItem – The used menu item type (e.g. 'Articles'). + * @param {string} [menu='Main Menu'] – The used menu item destination (e.g. 'Featured Articles'). + * @param {string} [language='All'] - Menu item language as name (e.g. 'Czech (Čeština)') or tag (e.g. 'cs-CZ'). + * + * The 'language' parameter is only used for multilingual websites where the language selection is visible. + * + * @returns {void} - It does not return any value, but it is chainable with other Cypress commands. + */ + createMenuItem(menuTitle: string, menuCategory: string, menuItem: string, menu?: string, language?: string): Chainable + + /** + * @memberof cy + * @method createCategory + * @param {string} title + * @param {string} extension - Default com_content + * @returns Chainable + */ + createCategory(title: string, extension?: string): Chainable + + /** + * Selects an option in a fancy select field + * + * @memberof cy + * @method selectOptionInFancySelect + * @param {string} selectId - The name of the field like #jform_countries + * @param {string} option - The name of the value like 'Germany' + * @returns Chainable + */ + selectOptionInFancySelect(selectId: string, option: string): Chainable + + /** + * Toggles a switch field + * + * @memberof cy + * @method toggleSwitch + * @param {string} fieldName - The name of the field like 'Published + * @param {string} valueName - The name of the value like 'Yes' + * @returns Chainable + */ + toggleSwitch(fieldName: string, valueName: string): Chainable + } + } +} + +import { supportCommands } from 'joomla-cypress/src/support' + +supportCommands(); + +export {} \ No newline at end of file diff --git a/cypress/support/user.ts b/cypress/support/user.ts new file mode 100644 index 00000000..0903e474 --- /dev/null +++ b/cypress/support/user.ts @@ -0,0 +1,69 @@ +declare global { + namespace Cypress { + interface Chainable { + // joomla-cypress function declarations - user.js + + /** + * Do administrator login + * + * @memberof cy + * @method doAdministratorLogin + * @param {string} user + * @param {string} password + * @param {boolean} useSnapshot + * @returns Chainable + */ + doAdministratorLogin(user: string, password: string, useSnapshot?: boolean): Chainable + + /** + * Do administrator logout + * + * @memberof cy + * @method doAdministratorLogout + * @returns Chainable + */ + doAdministratorLogout(): Chainable + + /** + * Do frontend logout + * + * @memberof cy + * @method doFrontendLogin + * @param {string} user + * @param {string} password + * @param {boolean} useSnapshot + * @returns Chainable + */ + doFrontendLogin(user: string, password: string, useSnapshot?: boolean): Chainable + + /** + * Do frontend logout + * + * @memberof cy + * @method doFrontendLogout + * @returns Chainable + */ + doFrontendLogout(): Chainable + + /** + * Create a user + * + * @memberof cy + * @method createUser + * @param {string} name + * @param {string} username + * @param {string} password + * @param {string} email + * @param {string} userGroup + * @returns Chainable + */ + createUser(name: string, username: string, password: string, email: string, userGroup?: string): Chainable + } + } +} + +import { userCommands } from 'joomla-cypress/src/user' + +userCommands(); + +export {} \ No newline at end of file diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 00000000..996ff798 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "types": [ + "cypress", + "node" + ], + "moduleResolution": "nodenext", + "module": "nodenext" + }, + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..5511efde --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,108 @@ +services: + joomla: + build: + context: ./ + dockerfile: docker/joomla/Dockerfile + args: + JOOMLA_VERSION: ${JOOMLA_VERSION:-latest} + JOOMLA_FRAMEWORK_VERSION: ${JOOMLA_FRAMEWORK_VERSION:-^4.0} + ports: + - "8080:80" + environment: + JOOMLA_DB_HOST: db + JOOMLA_DB_USER: ${JOOMLA_DB_USER:-joomla_user} + JOOMLA_DB_PASSWORD: ${JOOMLA_DB_PASSWORD:-joomla_pass} + JOOMLA_DB_NAME: ${JOOMLA_DB_NAME:-joomla_db} + JOOMLA_VERSION: ${JOOMLA_VERSION:-latest} + JOOMLA_SITE_NAME: Joomla + JOOMLA_ADMIN_USER: Joomla Hero + JOOMLA_ADMIN_USERNAME: ${JOOMLA_ADMIN_USERNAME:-joomla} + # Admin password must be at least 12 characters long + JOOMLA_ADMIN_PASSWORD: ${JOOMLA_ADMIN_PASSWORD:-joomla_password} + JOOMLA_ADMIN_EMAIL: ${JOOMLA_ADMIN_EMAIL:-joomla@example.com} + #JOOMLA_EXTENSIONS_PATHS: ${JOOMLA_EXTENSIONS_PATHS:-/var/www/extensions/attachments.zip} + volumes: + - joomla_data:/var/www/html + depends_on: + db: + condition: service_healthy + networks: + - joomla + + db: + image: mariadb:${MARIADB_VERSION:-latest} + environment: + MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-password} + MYSQL_DATABASE: ${JOOMLA_DB_NAME:-joomla_db} + MYSQL_USER: ${JOOMLA_DB_USER:-joomla_user} + MYSQL_PASSWORD: ${JOOMLA_DB_PASSWORD:-joomla_pass} + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + timeout: 20s + retries: 10 + volumes: + - db_data:/var/lib/mysql + networks: + - joomla + + cypress: + profiles: ["testing"] + build: + context: ./ + dockerfile: docker/cypress/Dockerfile + args: + CYPRESS_VERSION: ${CYPRESS_VERSION:-13.17.0} + entrypoint: ["/usr/local/bin/cypress", "open", '--project', '/app'] + volumes: + - .:/app + - joomla_data:/var/www/html + environment: + DISPLAY: novnc:0.0 + JOOMLA_URL: http://joomla/ + JOOMLA_DB_HOST: db + JOOMLA_DB_USER: ${JOOMLA_DB_USER:-joomla_user} + JOOMLA_DB_PASSWORD: ${JOOMLA_DB_PASSWORD:-joomla_pass} + JOOMLA_DB_NAME: ${JOOMLA_DB_NAME:-joomla_db} + CYPRESS_JOOMLA_ADMIN_USERNAME: ${JOOMLA_ADMIN_USERNAME:-joomla} + CYPRESS_JOOMLA_ADMIN_PASSWORD: ${JOOMLA_ADMIN_PASSWORD:-joomla_password} + CYPRESS_JOOMLA_ADMIN_EMAIL: ${JOOMLA_ADMIN_EMAIL:-joomla@example.com} + depends_on: + - joomla + - novnc + networks: + - joomla + + novnc: + profiles: ["testing"] + build: + context: ./docker/novnc + dockerfile: Dockerfile + healthcheck: + test: ["CMD", "pidof", "novnc_server"] + timeout: 20s + retries: 10 + ports: + - "8090:8080" + networks: + - joomla + + phpmyadmin: + profiles: ["only_on_demand"] + image: phpmyadmin/phpmyadmin + ports: + - "8888:80" + environment: + PMA_HOST: db + PMA_USER: ${JOOMLA_DB_USER:-joomla_user} + PMA_PASSWORD: ${JOOMLA_DB_PASSWORD:-joomla_pass} + depends_on: + - db + networks: + - joomla + +volumes: + joomla_data: + db_data: + +networks: + joomla: diff --git a/docker/cypress/Dockerfile b/docker/cypress/Dockerfile new file mode 100644 index 00000000..62a86a4b --- /dev/null +++ b/docker/cypress/Dockerfile @@ -0,0 +1,7 @@ +ARG CYPRESS_VERSION + +FROM cypress/included:${CYPRESS_VERSION:-13.17.0} + +RUN apt-get update && \ + apt-get install -y mariadb-client make zip && \ + rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/docker/joomla/Dockerfile b/docker/joomla/Dockerfile new file mode 100644 index 00000000..255d7fa6 --- /dev/null +++ b/docker/joomla/Dockerfile @@ -0,0 +1,47 @@ +ARG JOOMLA_VERSION + +FROM joomla:${JOOMLA_VERSION:-latest} + +ARG JOOMLA_FRAMEWORK_VERSION + +RUN apt-get update && \ + # Install Node.js and Composer + apt-get install -y curl && \ + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ + apt-get install -y nodejs&& \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ + # Install necessary build tools + apt-get install -y make zip && \ + # Clean up + apt clean && rm -rf /var/lib/apt/lists/* + +COPY . /attachments + +WORKDIR /attachments + +# Adjust Composer autoload paths for Joomla CMS source location +RUN sed -Ei 's/"Joomla\\\\CMS\\\\": "temp\/joomla\/libraries\/src\/"/"Joomla\\\\CMS\\\\": "\/var\/www\/html\/libraries\/src\/"/' composer.json && \ + composer dump-autoload + +RUN sed -Ei 's/"joomla\/test": ".*"/"joomla\/test": "'"${JOOMLA_FRAMEWORK_VERSION}"'"/' composer.json && \ + sed -Ei 's/"joomla\/string": ".*"/"joomla\/string": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/registry": ".*"/"joomla\/registry": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/database": ".*"/"joomla\/database": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/application": ".*"/"joomla\/application": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/di": ".*"/"joomla\/di": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/session": ".*"/"joomla\/session": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/language": ".*"/"joomla\/language": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/filter": ".*"/"joomla\/filter": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/filesystem": ".*"/"joomla\/filesystem": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json &&\ + sed -Ei 's/"joomla\/input": ".*"/"joomla\/input": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json && \ + sed -Ei 's/"joomla\/uri": ".*"/"joomla\/uri": "'${JOOMLA_FRAMEWORK_VERSION}'"/' composer.json + +RUN composer update && \ + npm ci + +RUN rm -f attachments-*.zip && make && \ + mkdir /var/www/extensions/ && \ + mv `ls -1 attachments-*.zip` /var/www/extensions/attachments.zip && \ + chown -R www-data:www-data /var/www/extensions + +WORKDIR /var/www/html \ No newline at end of file diff --git a/docker/novnc/Dockerfile b/docker/novnc/Dockerfile new file mode 100644 index 00000000..7af97a4d --- /dev/null +++ b/docker/novnc/Dockerfile @@ -0,0 +1,30 @@ +FROM alpine:3.19 + +# Setup demo environment variables +ENV LANG=en_US.UTF-8 \ + LANGUAGE=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + DISPLAY=:0.0 + +RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories +RUN apk update && \ + apk add \ + novnc@testing \ + bash \ + fluxbox \ + terminus-font \ + supervisor \ + tigervnc \ + websockify && \ + # py3-pip && \ + rm /var/cache/apk/* + +# RUN pip3 install websockify + +RUN ln -s /usr/share/novnc/vnc.html /usr/share/novnc/index.html + +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +EXPOSE 8080 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/docker/novnc/supervisord.conf b/docker/novnc/supervisord.conf new file mode 100644 index 00000000..28062816 --- /dev/null +++ b/docker/novnc/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true + +[program:xvnc] +command=/usr/bin/Xvnc -SecurityTypes=None -localhost=0 :0.0 -listen tcp -ac +autorestart=true + +[program:novnc] +command=/usr/bin/novnc_server --vnc localhost:5900 --listen 8080 +autorestart=true + +[program:fluxbox] +command=fluxbox +autorestart=true diff --git a/fixsha.sh b/fixsha.sh old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..031adb4b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2373 @@ +{ + "name": "attachments", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "attachments", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "devDependencies": { + "cypress-mysql": "^1.0.4", + "joomla-cypress": "^1.3.1", + "typescript": "^5.9.3" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cypress": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.6", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.5.3", + "supports-color": "^8.1.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + } + }, + "node_modules/cypress-file-upload": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz", + "integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.2.1" + }, + "peerDependencies": { + "cypress": ">3.0.0" + } + }, + "node_modules/cypress-mysql": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cypress-mysql/-/cypress-mysql-1.0.4.tgz", + "integrity": "sha512-OZj5IFLFDqeDxQKFS3muA0tm3DzC9CC8QSzxZJHnPXlhTDvCrRDp7XZwD1DCwBn6v/vZeuKJ65bna6Xorp9p9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "mysql2": "3.11.0" + }, + "peerDependencies": { + "cypress": ">=12" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/joomla-cypress": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/joomla-cypress/-/joomla-cypress-1.3.1.tgz", + "integrity": "sha512-8ww37fHL2yVTld8f7BwZuIFc5g1Ba0bCgaxlMGZhqBl89BO+iGaiAXR9uKT5pufjaZEJDBVd92ScTJZLaoeRwQ==", + "dev": true, + "license": "GPL-2.0+", + "dependencies": { + "cypress": "^13.16.0", + "cypress-file-upload": "^5.0.8" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "> 0.8" + } + }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16.14" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz", + "integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..d4243837 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "attachments", + "version": "1.0.0", + "description": "Download latest version of package", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jmcameron/attachments.git" + }, + "author": "", + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/jmcameron/attachments/issues" + }, + "homepage": "https://github.com/jmcameron/attachments#readme", + "devDependencies": { + "cypress-mysql": "^1.0.4", + "joomla-cypress": "^1.3.1", + "typescript": "^5.9.3" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..f816770f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,30 @@ + + + + + tests/Unit + + + tests/Integration + + + + + + + + + + + attachments_component/admin/src + attachments_component/site/src + attachments_plugin/src + attachments_for_content/src + attachments_search/src + attachments_plugin_framework/src + add_attachment_btn_plugin/src + insert_attachments_token_btn_plugin/src + show_attachments_in_editor_plugin/src + + + diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index 3ff3709a..00000000 --- a/test/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -config.php -joomla_db.xml -test1.php -coverage_db -coverage_result diff --git a/test/attachments_component/admin/import/testImportAttachments.php b/test/attachments_component/admin/import/testImportAttachments.php deleted file mode 100644 index 828e9a68..00000000 --- a/test/attachments_component/admin/import/testImportAttachments.php +++ /dev/null @@ -1,113 +0,0 @@ -load('com_attachments', JPATH_BASE . '/administrator/components/com_attachments'); - } - - - /** - * Gets the data set to be loaded into the database during setup - * - * @return xml dataset - */ - protected function getDataSet() - { - return $this->createXMLDataSet(JPATH_TESTS . '/joomla_db.xml'); - } - - - /** - * - * - * @dataProvider provider - * - */ - public function testImportAttachmentsFromCSVFile($test_filename, $expected_result, $update, $dry_run) - { - $path = dirname(__FILE__) . '/' . $test_filename; - - // Open the CSV file - $result = AttachmentsImport::importAttachmentsFromCSVFile($path, $verify_parent = true, $update, $dry_run); - if (is_numeric($expected_result) && is_numeric($result)) { - $this->assertEquals((int)$expected_result, (int)$result); - } elseif (is_array($result)) { - $this->assertEquals((int)$expected_result, count($result)); - } else { - // Cut off the error number for comparison - $errmsg = substr($result, 0, strpos($result, ' (ERR')); - - $this->assertEquals($expected_result, $errmsg); - } - - // Delete the attachments - if (!$update) { - $db = Factory::getContainer()->get('DatabaseDriver'); - if (is_array($result)) { - $query = $db->getQuery(true); - $ids = implode(',', $result); - $query->delete('#__attachments')->where("id IN ( $ids )"); - $db->setQuery($query); - if (!$db->execute()) { - $this->assertTrue(false, 'ERROR deleting new test attachments' . $db->getErrorMsg()); - } - } - } - } - - - /** - * Get the test data from CSV file - */ - public function provider() - { - return new CsvFileIterator(dirname(__FILE__) . '/testImportAttachmentsData.csv'); - } -} diff --git a/test/attachments_component/admin/permissions/testActions.php b/test/attachments_component/admin/permissions/testActions.php deleted file mode 100644 index da7ae215..00000000 --- a/test/attachments_component/admin/permissions/testActions.php +++ /dev/null @@ -1,128 +0,0 @@ -createXMLDataSet(JPATH_TESTS . '/joomla_db.xml'); - } - - - /** - * Test various ACL action permissions for com_attachments for various users - * - * @dataProvider provider - * - * @param string $username The name of ther user (for error outputs) - * @param int $admin correct 'core.admin' permission (0/1 interpreted as bool) - * @param int $manage correct 'core.manage' permission (0/1 interpreted as bool) - * @param int $create correct 'core.create' permission (0/1 interpreted as bool) - * @param int $delete correct 'core.delete' permission (0/1 interpreted as bool) - * @param int $edit_state correct 'core.edit.state' permission (0/1 interpreted as bool) - * @param int $edit correct 'core.edit' permission (0/1 interpreted as bool) - * @param int $edit_own correct 'core.edit.own' permission (0/1 interpreted as bool) - * @param int $delete_own correct 'attachments.delete.own' permission (0/1 interpreted as bool) - */ - public function testActions($username, $admin, $manage, $create, $delete, $edit_state, $edit, $edit_own, $delete_own) - { - $user_id = JUserHelper::getUserId($username); - - $errmsg = "ERROR: ========> USERNAME=$username does not exist!"; - $this->assertNotEquals((int)$user_id, 0, $errmsg); - - $canDo = AttachmentsPermissions::getActions((int)$user_id); - - $errmsg = "----> Failed test for $username core.admin for com_attachments, " . - " expected $admin, got " . $canDo->get('core.admin') . " for " . $username; - $this->assertEquals($canDo->get('core.admin'), (bool)$admin, $errmsg); - - $errmsg = "----> Failed test for $username core.manage for com_attachments, " . - " expected $manage, got " . $canDo->get('core.manage') . " for " . $username; - $this->assertEquals($canDo->get('core.manage'), (bool)$manage, $errmsg); - - $errmsg = "----> Failed test for $username core.create for com_attachments, " . - " expected $create, got " . $canDo->get('core.create') . " for " . $username; - $this->assertEquals($canDo->get('core.create'), (bool)$create, $errmsg); - - $errmsg = "----> Failed test for $username core.delete for com_attachments, " . - " expected $delete, got " . $canDo->get('core.delete') . " for " . $username; - $this->assertEquals($canDo->get('core.delete'), (bool)$delete, $errmsg); - - $errmsg = "----> Failed test for $username core.edit.state for com_attachments, " . - " expected $edit_state, got " . $canDo->get('core.edit.state') . " for " . $username; - $this->assertEquals($canDo->get('core.edit.state'), (bool)$edit_state, $errmsg); - - $errmsg = "----> Failed test for $username core.edit for com_attachments, " . - " expected $edit, got " . $canDo->get('core.edit') . " for " . $username; - $this->assertEquals($canDo->get('core.edit'), (bool)$edit, $errmsg); - - $errmsg = "----> Failed test for $username core.edit.own for com_attachments, " . - " expected $edit_own, got " . $canDo->get('core.edit.own') . " for " . $username; - $this->assertEquals($canDo->get('core.edit.own'), (bool)$edit_own, $errmsg); - - $errmsg = "----> Failed test for $username attachments.delete.own for com_attachments, " . - " expected $delete_own, got " . $canDo->get('attachments.delete.own') . " for " . $username; - $this->assertEquals($canDo->get('attachments.delete.own'), (bool)$delete_own, $errmsg); - } - - - /** - * Get the test data from CSV file - */ - public function provider() - { - return new CsvFileIterator(dirname(__FILE__) . '/testActionsData.csv'); - } -} diff --git a/test/attachments_component/admin/permissions/testArticleEdit.php b/test/attachments_component/admin/permissions/testArticleEdit.php deleted file mode 100644 index 0a9d10fc..00000000 --- a/test/attachments_component/admin/permissions/testArticleEdit.php +++ /dev/null @@ -1,89 +0,0 @@ -createXMLDataSet(JPATH_TESTS . '/joomla_db.xml'); - } - - - /** - * Test to see whether a user may edit a specified article - * - * @dataProvider provider - * - * @param int $user_id the id of the user to test - * @param string $username the username (for error printouts) - * @param int $art_id the id of the article to test - * @param int $may_edit the expected result of the test - */ - public function testArticleEdit($user_id, $username, $art_id, $may_edit) - { - $result = AttachmentsPermissions::userMayEditArticle((int)$art_id, (int)$user_id); - $errmsg = "----> Failed test for $username edit article $art_id, expected $may_edit, got $result"; - - $this->assertEquals($result, (bool)$may_edit, $errmsg); - } - - - /** - * Get the test data from CSV file - */ - public function provider() - { - return new CsvFileIterator(dirname(__FILE__) . '/testArticleEditData.csv'); - } -} diff --git a/test/bootstrap.php b/test/bootstrap.php deleted file mode 100644 index afd22719..00000000 --- a/test/bootstrap.php +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/AttachmentsDatabaseTestCase.php b/tests/AttachmentsDatabaseTestCase.php new file mode 100644 index 00000000..45faba63 --- /dev/null +++ b/tests/AttachmentsDatabaseTestCase.php @@ -0,0 +1,714 @@ +setUpJoomlaMocks(); + } + + /** + * Sets up common Joomla CMS mocks including the Factory class + */ + protected function setUpJoomlaMocks(): void + { + // Create mock container + $this->mockContainer = $this->getMockBuilder('Joomla\DI\Container') + ->disableOriginalConstructor() + ->getMock(); + + // Create mock user factory + $this->mockUserFactory = $this->getMockBuilder('Joomla\CMS\User\UserFactory') + ->disableOriginalConstructor() + ->getMock(); + + // Create mock user + $this->mockUser = $this->getMockBuilder('Joomla\CMS\User\User') + ->disableOriginalConstructor() + ->onlyMethods(['authorise', 'getAuthorisedViewLevels']) + ->getMock(); + + // Create mock application + $this->mockApp = $this->getMockBuilder('Joomla\CMS\Application\CMSApplication') + // ->disableOriginalConstructor() + ->onlyMethods(['getIdentity', 'getDispatcher', 'getSession', 'bootComponent', 'getInput', 'getConfig']) + ->getMockForAbstractClass(); + + // Set up basic user properties + $this->mockUser->id = 42; + $this->mockUser->name = 'Test User'; + $this->mockUser->username = 'testuser'; + $this->mockUser->email = 'test@example.com'; + + // Default authorise to false + // $this->mockUser->method('authorise') + // ->willReturn(false); + + // Set up database driver mock that actually uses the test database + $this->mockDatabaseDriver = $this->getDatabaseManager()->getConnection(); + + $this->mockCacheController = $this->getMockBuilder('Joomla\CMS\Cache\CacheController') + ->disableOriginalConstructor() + ->getMock(); + + $this->cacheControllerFactory = $this->getMockBuilder('Joomla\CMS\Cache\CacheControllerFactoryInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->cacheControllerFactory->method('createCacheController') + ->willReturn($this->mockCacheController); + + // Create a custom user factory mock that loads users from the database when needed + $customUserFactory = $this->getMockBuilder('Joomla\CMS\User\UserFactoryInterface') + ->disableOriginalConstructor() + ->getMock(); + + $customUserFactory->method('loadUserById') + ->willReturnCallback(function ($userId) { + // Create a real User object, which will load from the database + return new \Joomla\CMS\User\User($userId); + }); + + // Set up container to return user factory + $this->mockContainer->method('get') + ->willReturnMap([ + [\Joomla\CMS\User\UserFactoryInterface::class, $customUserFactory], + // [\Joomla\Database\DatabaseDriver::class, $this->mockDatabaseDriver], + [\Joomla\Database\DatabaseDriver::class, $this->getDatabaseManager()->getConnection()], + ['DatabaseDriver', $this->getDatabaseManager()->getConnection()], + ['DatabaseInterface', $this->getDatabaseManager()->getConnection()], + [\Joomla\Database\DatabaseInterface::class, $this->getDatabaseManager()->getConnection()], + [\Joomla\CMS\Cache\CacheControllerFactoryInterface::class, $this->cacheControllerFactory], + [\Joomla\CMS\Language\LanguageFactoryInterface::class, new \Joomla\CMS\Language\LanguageFactory()], + ]); + + // Default app to return our mock user + $this->mockApp->method('getIdentity') + ->willReturn($this->mockUser); + + $this->mockApp->method('getDispatcher') + ->willReturn(new \Joomla\Event\Dispatcher); + + $session = $this->getMockBuilder('Joomla\CMS\Session\Session') + ->onlyMethods(['get']) + ->disableOriginalConstructor() + ->getMock(); + + $session->method('get') + ->willReturnMap([ + ['lang', null, 'en-GB'], + ['user', null, $this->mockUser], + ]); + + $this->mockApp->method('getSession') + ->willReturn($session); + + // Register the mock container and application with the Joomla Factory so production code uses them + \Joomla\CMS\Factory::$container = $this->mockContainer; + \Joomla\CMS\Factory::$application = $this->mockApp; + \Joomla\CMS\Factory::$database = $this->getDatabaseManager()->getConnection(); + } + + /** + * Set up a user with specified permissions + * + * @param array $permissions The permissions to set + * @param array $userProps Optional user properties to override defaults + */ + protected function setUpUserWithPermissions(array $permissions, array $userProps = []): void + { + $this->permissions = $permissions; + + // Create a new mock user with the specified permissions + $this->mockUser = $this->getMockBuilder('Joomla\CMS\User\User') + ->disableOriginalConstructor() + ->onlyMethods(['authorise']) + ->getMock(); + + // Set up user properties + $defaultProps = [ + 'id' => 42, + 'name' => 'Test User', + 'username' => 'testuser', + 'email' => 'test@example.com', + 'groups' => [2] // Default to Registered Users group + ]; + + foreach ($defaultProps as $prop => $value) { + $this->mockUser->$prop = $userProps[$prop] ?? $value; + } + + // Configure authorise to return values from permissions array + $this->mockUser->method('authorise') + ->willReturnCallback(function($action) { + return $this->permissions[$action] ?? false; + }); + + // Update app and user factory to use new mock user + $this->mockApp->method('getIdentity') + ->willReturn($this->mockUser); + + $this->mockUserFactory + ->method('loadUserById') + ->willReturn($this->mockUser); + } + + /** + * Tear down the test environment + */ + protected function tearDown(): void + { + // Reset Joomla Factory application + if (class_exists('Joomla\CMS\Factory')) { + \Joomla\CMS\Factory::$application = null; + } + + $this->mockApp = null; + $this->mockLang = null; + $this->mockUser = null; + + parent::tearDown(); + } + + protected function populateViewLevels() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create the viewlevels table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__viewlevels') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY, + " . $db->quoteName('title') . " VARCHAR(255) NOT NULL, + " . $db->quoteName('ordering') . " INTEGER DEFAULT 0, + " . $db->quoteName('rules') . " TEXT + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert viewlevels one at a time + $viewlevels = [ + ['id' => 1, 'title' => 'Public', 'ordering' => 0, 'rules' => '[1]'], + ['id' => 2, 'title' => 'Registered', 'ordering' => 1, 'rules' => '[6,2,8]'], + ['id' => 3, 'title' => 'Special', 'ordering' => 2, 'rules' => '[6,3,8]'], + ]; + + $count = 0; + foreach ($viewlevels as $level) { + $query = $db->getQuery(true); + $query->insert('#__viewlevels') + ->columns(['id', 'title', 'ordering', 'rules']) + ->values($db->quote($level['id']) . ', ' . $db->quote($level['title']) . ', ' . + $db->quote($level['ordering']) . ', ' . $db->quote($level['rules'])); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateUsers() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create the viewlevels table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__users') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY NOT NULL, + " . $db->quoteName('name') . " TEXT NOT NULL, + " . $db->quoteName('username') . " TEXT NOT NULL, + " . $db->quoteName('email') . " TEXT NOT NULL, + " . $db->quoteName('password') . " TEXT NOT NULL, + " . $db->quoteName('block') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('sendEmail') . " INTEGER DEFAULT 0, + " . $db->quoteName('registerDate') . " NUMERIC NOT NULL DEFAULT NULL, + " . $db->quoteName('lastvisitDate') . " NUMERIC DEFAULT NULL, + " . $db->quoteName('activation') . " TEXT NOT NULL, + " . $db->quoteName('params') . " MEDIUMTEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('lastResetTime') . " NUMERIC DEFAULT NULL, + " . $db->quoteName('resetCount') . " INT(11) NOT NULL DEFAULT 0, + " . $db->quoteName('otpKey') . " TEXT NOT NULL, + " . $db->quoteName('otep') . " TEXT NOT NULL, + " . $db->quoteName('requireReset') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('authProvider') . " TEXT NOT NULL + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert users one at a time + $users = [ + ['id' => 42, 'name' => 'admin', 'username' => 'admin', 'email' => 'admin@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 43, 'name' => 'jmc', 'username' => 'jmc', 'email' => 'jmc@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 50, 'name' => 'joe', 'username' => 'joe', 'email' => 'joe@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 51, 'name' => 'art', 'username' => 'art', 'email' => 'art@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 52, 'name' => 'ed', 'username' => 'ed', 'email' => 'ed@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 53, 'name' => 'pub', 'username' => 'pub', 'email' => 'pub@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 54, 'name' => 'manny', 'username' => 'manny', 'email' => 'manny@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ['id' => 55, 'name' => 'adam', 'username' => 'adam', 'email' => 'adam@example.com', 'password' => 'hashed_password', 'block' => 0, + 'sendEmail' => 0, 'registerDate' => '2024-01-01 00:00:00', 'lastvisitDate' => null, + 'activation' => '', 'params' => '', 'lastResetTime' => null, 'resetCount' => 0, + 'otpKey' => '', 'otep' => '', 'requireReset' => 0, 'authProvider' => ''], + ]; + + $count = 0; + foreach ($users as $level) { + $query = $db->getQuery(true); + $query->insert('#__users') + ->columns(['id', 'name', 'username', 'email', 'password', 'block', 'sendEmail', 'registerDate', + 'lastvisitDate', 'activation', 'params', 'lastResetTime', 'resetCount', + 'otpKey', 'otep', 'requireReset', 'authProvider']) + ->values($db->quote($level['id']) . ', ' . $db->quote($level['name']) . ', ' . + $db->quote($level['username']) . ', ' . $db->quote($level['email']) . ', ' . + $db->quote($level['password']) . ', ' . $db->quote($level['block']) . ', ' . + $db->quote($level['sendEmail']) . ', ' . $db->quote($level['registerDate']) . ', ' . + $db->quote($level['lastvisitDate']) . ', ' . $db->quote($level['activation']) . ', ' . + $db->quote($level['params']) . ', ' . $db->quote($level['lastResetTime']) . ', ' . + $db->quote($level['resetCount']) . ', ' . $db->quote($level['otpKey']) . ', ' . + $db->quote($level['otep']) . ', ' . $db->quote($level['requireReset']) . ', ' . $db->quote($level['authProvider'])); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateUserGroups() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create the viewlevels table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__usergroups') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY NOT NULL, + " . $db->quoteName('parent_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('lft') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('rgt') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('title') . " TEXT NOT NULL + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert users one at a time + $users = [ + ['id' => 1, 'parent_id' => 0, 'lft' => 1, 'rgt' => 26, 'title' => 'Public'], + ['id' => 2, 'parent_id' => 1, 'lft' => 6, 'rgt' => 13, 'title' => 'Registered'], + ['id' => 3, 'parent_id' => 2, 'lft' => 7, 'rgt' => 12, 'title' => 'Author'], + ['id' => 4, 'parent_id' => 3, 'lft' => 8, 'rgt' => 11, 'title' => 'Editor'], + ['id' => 5, 'parent_id' => 4, 'lft' => 9, 'rgt' => 10, 'title' => 'Publisher'], + ['id' => 6, 'parent_id' => 1, 'lft' => 2, 'rgt' => 5, 'title' => 'Manager'], + ['id' => 7, 'parent_id' => 6, 'lft' => 3, 'rgt' => 4, 'title' => 'Administrator'], + ['id' => 8, 'parent_id' => 1, 'lft' => 24, 'rgt' => 25, 'title' => 'Super Users'], + ['id' => 9, 'parent_id' => 1, 'lft' => 14, 'rgt' => 23, 'title' => 'Special'], + ['id' => 10, 'parent_id' => 9, 'lft' => 15, 'rgt' => 20, 'title' => 'Attachments Author'], + ['id' => 11, 'parent_id' => 10, 'lft' => 16, 'rgt' => 17, 'title' => 'Attachments Editor'], + ['id' => 12, 'parent_id' => 10, 'lft' => 18, 'rgt' => 19, 'title' => 'Attachments Publisher'], + ['id' => 13, 'parent_id' => 9, 'lft' => 21, 'rgt' => 22, 'title' => 'Attachments Manager'] + ]; + + $count = 0; + foreach ($users as $level) { + $query = $db->getQuery(true); + $query->insert('#__usergroups') + ->columns(['id', 'parent_id', 'lft', 'rgt', 'title']) + ->values($db->quote($level['id']) . ', ' . $db->quote($level['parent_id']) . ', ' . + $db->quote($level['lft']) . ', ' . $db->quote($level['rgt']) . ', ' . + $db->quote($level['title'])); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateUserGroupMap() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create the viewlevels table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__user_usergroup_map') . " ( + " . $db->quoteName('user_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('group_id') . " INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (" . $db->quoteName('user_id') . "," . $db->quoteName('group_id') . ") + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert users one at a time + // Based on testActionsData.csv expectations: + // joe (ID 50) -> Public (ID 1) -> all permissions 0 + // art (ID 51) -> Attachments Author (ID 10) -> core.create=1, core.edit.own=1, attachments.delete.own=1 + // ed (ID 52) -> Attachments Editor (ID 11) -> core.create=1, core.edit=1, core.edit.own=1, attachments.delete.own=1 + // pub (ID 53) -> Attachments Publisher (ID 12) -> core.create=1, core.edit=1, core.edit.state=1, core.edit.own=1, attachments.delete.own=1 + // manny (ID 54) -> Attachments Manager (ID 13) -> core.create=1, core.delete=1, core.edit=1, core.edit.state=1, core.edit.own=1, attachments.delete.own=1 + // adam (ID 55) -> Administrator (ID 7) -> core.manage=1, core.create=1, core.delete=1, core.edit=1, core.edit.state=1, core.edit.own=1, attachments.delete.own=1 + // admin (ID 42) -> Super Users (ID 8) -> all permissions 1 + // jmc (ID 43) -> Attachments Author (ID 10) -> core.create=1, core.edit.own=1, attachments.delete.own=1 (same as art) + $users = [ + ['user_id' => 42, 'group_id' => 8], // admin -> Super Users + ['user_id' => 43, 'group_id' => 10], // jmc -> Attachments Author + ['user_id' => 50, 'group_id' => 1], // joe -> Public + ['user_id' => 51, 'group_id' => 10], // art -> Attachments Author + ['user_id' => 52, 'group_id' => 11], // ed -> Attachments Editor + ['user_id' => 53, 'group_id' => 12], // pub -> Attachments Publisher + ['user_id' => 54, 'group_id' => 13], // manny -> Attachments Manager + ['user_id' => 55, 'group_id' => 7], // adam -> Administrator + ]; + + $count = 0; + foreach ($users as $level) { + $query = $db->getQuery(true); + $query->insert('#__user_usergroup_map') + ->columns(['user_id', 'group_id']) + ->values($db->quote($level['user_id']) . ', ' . $db->quote($level['group_id'])); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateAssets() + { + $db = $this->getDatabaseManager()->getConnection(); + + // Create extensions table if it doesn't exist, as it's required for ACL system + $this->populateExtensions(); + + try { + // Create the viewlevels table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__assets') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY NOT NULL DEFAULT NULL, + " . $db->quoteName('parent_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('lft') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('rgt') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('level') . " INTEGER NOT NULL DEFAULT NULL, + " . $db->quoteName('name') . " TEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('title') . " TEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('rules') . " TEXT NOT NULL DEFAULT NULL + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert assets one at a time + $assets = [ + ['id' => 1, 'parent_id' => 0, 'lft' => 1, 'rgt' => 20, 'level' => 0, 'name' => 'root.1', 'title' => 'Root Asset', 'rules' => '{"core.login.site":{"6":1,"2":1},"core.login.admin":{"6":1},"core.login.offline":{"6":1},"core.admin":{"8":1},"core.manage":{"7":1},"core.create":{"6":1,"3":1},"core.delete":{"6":1},"core.edit":{"6":1,"4":1},"core.edit.state":{"6":1,"5":1},"core.edit.own":{"6":1,"3":1},"attachments.delete.own":{"6":1,"3":1},"attachments.edit.state.own":{"6":1,"4":1},"attachments.edit.state.ownparent":{"6":1,"4":1},"attachments.edit.ownparent":{"6":1,"3":1},"attachments.delete.ownparent":{"6":1,"3":1}}'], + ['id' => 2, 'parent_id' => 1, 'lft' => 2, 'rgt' => 3, 'level' => 1, 'name' => 'com_attachments', 'title' => 'com_attachments', 'rules' => '{"core.create":{"12":1,"13":1,"11":1,"10":1},"core.delete":{"13":1},"core.edit":{"11":1,"12":1,"13":1},"core.edit.state":{"12":1,"13":1},"core.edit.own":{"12":1,"13":1,"11":1,"10":1},"attachments.edit.state.own":{"12":1,"13":1,"11":1,"10":1},"attachments.delete.own":{"12":1,"13":1,"11":1,"10":1},"attachments.edit.ownparent":{"12":1,"13":1,"11":1,"10":1},"attachments.edit.state.ownparent":{"12":1,"13":1,"11":1,"10":1},"attachments.delete.ownparent":{"12":1,"13":1,"11":1,"10":1}}'], + ['id' => 8, 'parent_id' => 1, 'lft' => 4, 'rgt' => 19, 'level' => 1, 'name' => 'com_content', 'title' => 'com_content', 'rules' => '{"core.admin":{"7":1},"core.manage":{"6":1},"core.create":{"3":1},"core.edit":{"4":1,"11":1,"12":1,"13":1},"core.edit.state":{"5":1,"12":1,"13":1},"core.edit.own":{"12":1,"11":1,"10":1}}'], + // Category-specific assets under com_content + ['id' => 9, 'parent_id' => 8, 'lft' => 5, 'rgt' => 6, 'level' => 2, 'name' => 'com_content.category.2', 'title' => 'Category 2', 'rules' => '{"core.edit":{"11":1,"12":1,"13":1},"core.edit.own":{"12":1,"11":1,"10":1}}'], + ['id' => 10, 'parent_id' => 8, 'lft' => 7, 'rgt' => 8, 'level' => 2, 'name' => 'com_content.category.7', 'title' => 'Category 7', 'rules' => '{"core.edit":{"11":1,"12":1,"13":1},"core.edit.own":{"12":1,"11":1,"10":1}}'], + ['id' => 11, 'parent_id' => 8, 'lft' => 9, 'rgt' => 10, 'level' => 2, 'name' => 'com_content.category.8', 'title' => 'Category 8', 'rules' => '{"core.edit":{"11":1,"12":1,"13":1},"core.edit.own":{"12":1,"11":1,"10":1}}'], + // Article-specific assets under com_content + ['id' => 12, 'parent_id' => 8, 'lft' => 11, 'rgt' => 12, 'level' => 2, 'name' => 'com_content.article.1', 'title' => 'Article 1', 'rules' => '{"core.edit":{"11":1,"12":1,"13":1},"core.edit.own":{"12":1,"11":1,"10":1}}'], + ['id' => 13, 'parent_id' => 8, 'lft' => 13, 'rgt' => 14, 'level' => 2, 'name' => 'com_content.article.2', 'title' => 'Article 2', 'rules' => '{"core.edit":{"11":1,"12":1,"13":1},"core.edit.own":{"12":1,"11":1,"10":1}}'] + ]; + + $count = 0; + foreach ($assets as $asset) { + $query = $db->getQuery(true); + $query->insert('#__assets') + ->columns(['id', 'parent_id', 'lft', 'rgt', 'level', 'name', 'title', 'rules']) + ->values($db->quote($asset['id']) . ', ' . $db->quote($asset['parent_id']) . ', ' . + $db->quote($asset['lft']) . ', ' . $db->quote($asset['rgt']) . ', ' . + $db->quote($asset['level']) . ', ' . $db->quote($asset['name']) . ', ' . + $db->quote($asset['title']) . ', ' . $db->quote($asset['rules'])); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateExtensions() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create extensions table if it doesn't exist + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__extensions') . " ( + " . $db->quoteName('extension_id') . " INTEGER PRIMARY KEY NOT NULL, + " . $db->quoteName('name') . " TEXT NOT NULL, + " . $db->quoteName('type') . " TEXT NOT NULL, + " . $db->quoteName('element') . " TEXT NOT NULL, + " . $db->quoteName('folder') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('client_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('enabled') . " INTEGER NOT NULL DEFAULT 1, + " . $db->quoteName('access') . " INTEGER NOT NULL DEFAULT 1, + " . $db->quoteName('protected') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('manifest_cache') . " TEXT NOT NULL, + " . $db->quoteName('params') . " TEXT NOT NULL, + " . $db->quoteName('custom_data') . " TEXT NOT NULL, + " . $db->quoteName('system_data') . " TEXT NOT NULL, + " . $db->quoteName('checked_out') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('checked_out_time') . " TEXT NOT NULL, + " . $db->quoteName('ordering') . " INTEGER DEFAULT 0, + " . $db->quoteName('state') . " INTEGER DEFAULT 0 + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert required extensions + $extensions = [ + ['extension_id' => 7, 'name' => 'files_joomla', 'type' => 'file', 'element' => 'joomla', 'folder' => '', 'client_id' => 0, 'enabled' => 1, 'access' => 1, 'protected' => 1, 'manifest_cache' => '', 'params' => '', 'custom_data' => '', 'system_data' => '', 'checked_out' => 0, 'checked_out_time' => '0000-00-00 00:00:00', 'ordering' => 0, 'state' => 0], + ['extension_id' => 19, 'name' => 'com_content', 'type' => 'component', 'element' => 'com_content', 'folder' => '', 'client_id' => 1, 'enabled' => 1, 'access' => 1, 'protected' => 1, 'manifest_cache' => '', 'params' => '', 'custom_data' => '', 'system_data' => '', 'checked_out' => 0, 'checked_out_time' => '0000-00-00 00:00:00', 'ordering' => 0, 'state' => 0], + ['extension_id' => 21, 'name' => 'com_attachments', 'type' => 'component', 'element' => 'com_attachments', 'folder' => '', 'client_id' => 1, 'enabled' => 1, 'access' => 1, 'protected' => 0, 'manifest_cache' => '', 'params' => '', 'custom_data' => '', 'system_data' => '', 'checked_out' => 0, 'checked_out_time' => '0000-00-00 00:00:00', 'ordering' => 0, 'state' => 0], + ['extension_id' => 23, 'name' => 'com_admin', 'type' => 'component', 'element' => 'com_admin', 'folder' => '', 'client_id' => 1, 'enabled' => 1, 'access' => 1, 'protected' => 1, 'manifest_cache' => '', 'params' => '', 'custom_data' => '', 'system_data' => '', 'checked_out' => 0, 'checked_out_time' => '0000-00-00 00:00:00', 'ordering' => 0, 'state' => 0] + ]; + + $count = 0; + foreach ($extensions as $ext) { + $query = $db->getQuery(true); + $columns = array_keys($ext); + $values = array_values($ext); + $query->insert('#__extensions') + ->columns($db->quoteName($columns)) + ->values(implode(',', array_map([$db, 'quote'], $values))); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateCategories() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create categories table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__categories') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY NOT NULL, + " . $db->quoteName('asset_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('parent_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('lft') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('rgt') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('level') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('path') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('extension') . " TEXT NOT NULL, + " . $db->quoteName('title') . " TEXT NOT NULL, + " . $db->quoteName('alias') . " TEXT NOT NULL, + " . $db->quoteName('note') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('description') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('published') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('checked_out') . " INTEGER DEFAULT NULL, + " . $db->quoteName('checked_out_time') . " TEXT DEFAULT NULL, + " . $db->quoteName('access') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('params') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('metadesc') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('metakey') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('metadata') . " TEXT NOT NULL DEFAULT '', + " . $db->quoteName('created_user_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('created_time') . " TEXT NOT NULL, + " . $db->quoteName('modified_user_id') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('modified_time') . " TEXT NOT NULL, + " . $db->quoteName('hits') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('language') . " CHAR(7) NOT NULL, + " . $db->quoteName('version') . " INTEGER NOT NULL DEFAULT 1 + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert categories one at a time + $categories = [ + ['id' => 2, 'asset_id' => 0, 'title' => 'Category 2', 'alias' => 'category-2', 'created_user_id' => 52, 'created_time' => '2024-01-01 00:00:00', 'modified_user_id' => 0, 'modified_time' => '2024-01-01 00:00:00', 'language' => '*', 'parent_id' => 1, 'level' => 1, 'lft' => 1, 'rgt' => 2, 'extension' => 'com_content', 'published' => 1], + ['id' => 7, 'asset_id' => 0, 'title' => 'Category 7', 'alias' => 'category-7', 'created_user_id' => 53, 'created_time' => '2024-01-01 00:00:00', 'modified_user_id' => 0, 'modified_time' => '2024-01-01 00:00:00', 'language' => '*', 'parent_id' => 1, 'level' => 1, 'lft' => 3, 'rgt' => 4, 'extension' => 'com_content', 'published' => 1], + ['id' => 8, 'asset_id' => 0, 'title' => 'Category 8', 'alias' => 'category-8', 'created_user_id' => 54, 'created_time' => '2024-01-01 00:00:00', 'modified_user_id' => 0, 'modified_time' => '2024-01-01 00:00:00', 'language' => '*', 'parent_id' => 1, 'level' => 1, 'lft' => 5, 'rgt' => 6, 'extension' => 'com_content', 'published' => 1] + ]; + + $count = 0; + foreach ($categories as $category) { + $query = $db->getQuery(true); + $query->insert('#__categories') + ->columns(array_keys($category)) + ->values(implode(',', array_map([$db, 'quote'], $category))); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function populateContent() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create content table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__content') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY NOT NULL, + " . $db->quoteName('title') . " TEXT NOT NULL, + " . $db->quoteName('alias') . " TEXT NOT NULL, + " . $db->quoteName('introtext') . " TEXT NOT NULL, + " . $db->quoteName('fulltext') . " TEXT NOT NULL, + " . $db->quoteName('state') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('catid') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('created') . " TEXT NOT NULL, + " . $db->quoteName('created_by') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('modified') . " TEXT NOT NULL, + " . $db->quoteName('modified_by') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('checked_out') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('checked_out_time') . " TEXT NOT NULL, + " . $db->quoteName('publish_up') . " TEXT NOT NULL, + " . $db->quoteName('publish_down') . " TEXT NOT NULL, + " . $db->quoteName('version') . " INTEGER NOT NULL DEFAULT 1, + " . $db->quoteName('ordering') . " INTEGER NOT NULL DEFAULT 0 + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + // Insert content (articles) one at a time + $articles = [ + ['id' => 1, 'title' => 'Article 1', 'alias' => 'article-1', 'introtext' => '', 'fulltext' => '', 'state' => 1, 'catid' => 2, 'created' => '2024-01-01 00:00:00', 'created_by' => 52, 'modified' => '2024-01-01 00:00:00', 'modified_by' => 0, 'checked_out' => 0, 'checked_out_time' => '0000-00-00 00:00:00', 'publish_up' => '2024-01-01 00:00:00', 'publish_down' => '0000-00-00 00:00:00', 'version' => 1, 'ordering' => 0], + ['id' => 2, 'title' => 'Article 2', 'alias' => 'article-2', 'introtext' => '', 'fulltext' => '', 'state' => 1, 'catid' => 7, 'created' => '2024-01-01 00:00:00', 'created_by' => 43, 'modified' => '2024-01-01 00:00:00', 'modified_by' => 0, 'checked_out' => 0, 'checked_out_time' => '0000-00-00 00:00:00', 'publish_up' => '2024-01-01 00:00:00', 'publish_down' => '0000-00-00 00:00:00', 'version' => 1, 'ordering' => 0] + ]; + + $count = 0; + foreach ($articles as $article) { + $query = $db->getQuery(true); + $query->insert('#__content') + ->columns(array_keys($article)) + ->values(implode(',', array_map([$db, 'quote'], $article))); + + $db->setQuery($query); + if ($db->execute()) { + $count++; + } + } + return $count; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } +} \ No newline at end of file diff --git a/tests/AttachmentsTestCase.php b/tests/AttachmentsTestCase.php new file mode 100644 index 00000000..d3a92ea5 --- /dev/null +++ b/tests/AttachmentsTestCase.php @@ -0,0 +1,195 @@ +setUpJoomlaMocks(); + } + + /** + * Sets up common Joomla CMS mocks including the Factory class + */ + protected function setUpJoomlaMocks(): void + { + // Create mock container + $this->mockContainer = $this->getMockBuilder('Joomla\DI\Container') + ->disableOriginalConstructor() + ->getMock(); + + // Create mock user factory + $this->mockUserFactory = $this->getMockBuilder('Joomla\CMS\User\UserFactory') + ->disableOriginalConstructor() + ->getMock(); + + // Create mock user + $this->mockUser = $this->getMockBuilder('Joomla\CMS\User\User') + ->disableOriginalConstructor() + ->onlyMethods(['authorise']) + ->getMock(); + + // Create mock application + $this->mockApp = $this->getMockBuilder('Joomla\CMS\Application\CMSApplication') + ->disableOriginalConstructor() + ->onlyMethods(['getIdentity']) + ->getMockForAbstractClass(); + + // Set up basic user properties + $this->mockUser->id = 42; + $this->mockUser->name = 'Test User'; + $this->mockUser->username = 'testuser'; + $this->mockUser->email = 'test@example.com'; + + // Default authorise to false + $this->mockUser->method('authorise') + ->willReturn(false); + + // Set up database driver mock + $this->mockDatabaseDriver = $this->getMockBuilder('Joomla\Database\DatabaseDriver') + ->disableOriginalConstructor() + ->getMock(); + + $this->mockDatabaseDriver->method('getQuery') + ->willReturnCallback(function() { + return new \Joomla\Database\Sqlite\SqliteQuery(); + }); + + $this->mockDatabaseDriver->method('loadObject') + ->willReturnCallback(function() { + return null; + }); + + // Set up container to return user factory + $this->mockContainer->method('get') + ->willReturnMap([ + ['Joomla\CMS\User\UserFactoryInterface', $this->mockUserFactory], + ['DatabaseDriver', $this->mockDatabaseDriver], + ]); + + // Default app to return our mock user + $this->mockApp->method('getIdentity') + ->willReturn($this->mockUser); + + // Register the mock container and application with the Joomla Factory so production code uses them + \Joomla\CMS\Factory::$container = $this->mockContainer; + \Joomla\CMS\Factory::$application = $this->mockApp; + } + + /** + * Set up a user with specified permissions + * + * @param array $permissions The permissions to set + * @param array $userProps Optional user properties to override defaults + */ + protected function setUpUserWithPermissions(array $permissions, array $userProps = []): void + { + $this->permissions = $permissions; + + // Create a new mock user with the specified permissions + $this->mockUser = $this->getMockBuilder('Joomla\CMS\User\User') + ->disableOriginalConstructor() + ->onlyMethods(['authorise']) + ->getMock(); + + // Set up user properties + $defaultProps = [ + 'id' => 42, + 'name' => 'Test User', + 'username' => 'testuser', + 'email' => 'test@example.com', + 'groups' => [2] // Default to Registered Users group + ]; + + foreach ($defaultProps as $prop => $value) { + $this->mockUser->$prop = $userProps[$prop] ?? $value; + } + + // Configure authorise to return values from permissions array + $this->mockUser->method('authorise') + ->willReturnCallback(function($action) { + return $this->permissions[$action] ?? false; + }); + + // Update app and user factory to use new mock user + $this->mockApp->method('getIdentity') + ->willReturn($this->mockUser); + + $this->mockUserFactory + ->method('loadUserById') + ->willReturn($this->mockUser); + } + + /** + * Tear down the test environment + */ + protected function tearDown(): void + { + // Reset Joomla Factory application + if (class_exists('Joomla\CMS\Factory')) { + \Joomla\CMS\Factory::$application = null; + } + + $this->mockApp = null; + $this->mockLang = null; + $this->mockUser = null; + + parent::tearDown(); + } +} \ No newline at end of file diff --git a/tests/Integration/Component/Admin/Import/AttachmentsImport2.php b/tests/Integration/Component/Admin/Import/AttachmentsImport2.php new file mode 100644 index 00000000..17176df4 --- /dev/null +++ b/tests/Integration/Component/Admin/Import/AttachmentsImport2.php @@ -0,0 +1,24 @@ + index) or error message + */ + public static function parseFieldNames($file) + { + return parent::parseFieldNames($file); + } +} \ No newline at end of file diff --git a/tests/Integration/Component/Admin/Import/ImportAttachmentsTest.php b/tests/Integration/Component/Admin/Import/ImportAttachmentsTest.php new file mode 100644 index 00000000..8b2374af --- /dev/null +++ b/tests/Integration/Component/Admin/Import/ImportAttachmentsTest.php @@ -0,0 +1,255 @@ +populateViewLevels(); + + // Populate users table + $this->populateUsers(); + + // Create attachments table + $this->createAttachmentsTable(); + // var_dump($this->populateViewLevels()); + // $db = $this->getDatabaseManager()->getConnection(); + // $query = $db->getQuery(true); + // $query->select('*')->from('#__viewlevels'); + // $db->setQuery($query); + // $db->execute(); + // var_dump($db->loadObjectList()); + + $this->mockApp->method('getConfig') + ->willReturn(new Registry()); + } + // Force loading the component language + /** @var \Joomla\CMS\Application\WebApplication $app */ + $app = Factory::getApplication(); + $app->loadLanguage(); + $lang = Factory::getApplication()->getLanguage(); + $lang->load('com_attachments', JPATH_BASE . '/attachments_component/admin', 'en-GB', true); + + // It is only used in the plugin framework to load the language files + // and it looks at the wrong path anyway as it assumes being in site context + // so we define the constant here just to avoid errors + if (!defined('JPATH_PLUGINS')) { + define('JPATH_PLUGINS', JPATH_ROOT . '/plugins'); + } + + if (!defined('JPATH_COMPONENT')) { + define('JPATH_COMPONENT', JPATH_BASE . '/attachments_component/admin/src'); + } + + Table::addIncludePath(JPATH_BASE . '/attachments_component/admin/src/Table'); + + // Set up mock functions to avoid further db queries and dependencies + $this->mockUser->method('getAuthorisedViewLevels') + ->willReturn([1, 2, 3]); + + $namespace = "JMCameron\\Component\\Attachments"; + $mvcFactory = new MVCFactory($namespace); + $dispatcher = new ComponentDispatcherFactory($namespace, $mvcFactory); + $mvcComponent = new \Joomla\CMS\Extension\MVCComponent($dispatcher); + $mvcComponent->setMVCFactory($mvcFactory); + + $this->mockApp->method('bootComponent') + ->willReturnMap([ + ['com_attachments', $mvcComponent], + ]); + $this->mockApp->method('getInput') + ->willReturn(Factory::getApplication()->input); + + // Add com_content as a known parent type + $apm = AttachmentsPluginManager::getAttachmentsPluginManager(); + $apm->addParentType('com_content'); + + // Inject a lightweight stub plugin to avoid installPlugin() trying to load real plugin classes + $ref = new \ReflectionClass($apm); + $prop = $ref->getProperty('plugin'); + $prop->setAccessible(true); + $plugins = $prop->getValue($apm) ?: []; + + $plugins['com_content'] = new class { + // implement the minimal methods AttachmentsImport/getInstalledEntityInfo expect + public function getEntities(): array + { + return ['article']; // adjust to match entities used in your CSV/testdata + } + public function getCanonicalEntityId(string $entity): string + { + return strtolower($entity); + } + + public function parentExists($parent_id, $parent_entity): bool + { + if ($parent_id === 1) { + return true; + } + + return false; + } + + public function getTitle($parent_id, $parent_entity): string + { + if ($parent_id === 1) { + return "Welcome"; + } + + return "Test Article Title"; + } + }; + + $prop->setValue($apm, $plugins); + } + + /** + * + * + * @dataProvider provider + * + */ + public function testImportAttachmentsFromCSVFile($test_filename, $expected_result, $update, $dry_run) + { + $path = dirname(__FILE__) . '/' . $test_filename; + + // Open the CSV file + $result = AttachmentsImport::importAttachmentsFromCSVFile($path, true, $update, $dry_run); + if (is_numeric($expected_result) && is_numeric($result)) { + $this->assertEquals((int)$expected_result, (int)$result); + } elseif (is_array($result)) { + $this->assertEquals((int)$expected_result, count($result)); + } else { + // Cut off the error number for comparison + $errmsg = substr($result, 0, strpos($result, ' (ERR')); + + // Replace " [LINE: x] " with empty string for comparison + $errmsg = preg_replace('/ \[LINE: \d+\] /', '', $errmsg); + + // Replace %base_path% with actual path in the expected result + $expected_result = str_replace("%base_path%", dirname(__FILE__), $expected_result); + + $this->assertEquals($expected_result, $errmsg); + } + + // Delete the attachments + if (!$update) { + $db = Factory::getContainer()->get('DatabaseDriver'); + if (is_array($result)) { + $query = $db->getQuery(true); + $ids = implode(',', $result); + $query->delete('#__attachments')->where("id IN ( $ids )"); + $db->setQuery($query); + if (!$db->execute()) { + $this->assertTrue(false, 'ERROR deleting new test attachments' . $db->getErrorMsg()); + } + } + } + } + + + /** + * Get the test data from CSV file + */ + public static function provider() + { + return new CsvFileIterator(dirname(__FILE__) . '/testImportAttachmentsData.csv'); + } + + protected function createAttachmentsTable() + { + $db = $this->getDatabaseManager()->getConnection(); + + try { + // Create the attachments table if it doesn't exist using raw SQL + $createTableSQL = "CREATE TABLE IF NOT EXISTS " . $db->quoteName('#__attachments') . " ( + " . $db->quoteName('id') . " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + " . $db->quoteName('filename') . " TEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('filename_sys') . " TEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('file_type') . " TEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('file_size') . " INTEGER NOT NULL DEFAULT NULL, + " . $db->quoteName('url') . " TEXT NOT NULL, + " . $db->quoteName('url_valid') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('url_relative') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('url_verify') . " INTEGER NOT NULL DEFAULT 1, + " . $db->quoteName('display_name') . " TEXT NOT NULL, + " . $db->quoteName('description') . " TEXT NOT NULL, + " . $db->quoteName('icon_filename') . " TEXT NOT NULL DEFAULT NULL, + " . $db->quoteName('access') . " INTEGER NOT NULL DEFAULT 1, + " . $db->quoteName('state') . " INTEGER NOT NULL DEFAULT 0, + " . $db->quoteName('user_field_1') . " TEXT NOT NULL, + " . $db->quoteName('user_field_2') . " TEXT NOT NULL, + " . $db->quoteName('user_field_3') . " TEXT NOT NULL, + " . $db->quoteName('parent_type') . " TEXT NOT NULL DEFAULT 'com_content', + " . $db->quoteName('parent_entity') . " TEXT NOT NULL DEFAULT 'article', + " . $db->quoteName('parent_id') . " INTEGER DEFAULT NULL, + " . $db->quoteName('created') . " NUMERIC DEFAULT NULL, + " . $db->quoteName('created_by') . " INTEGER NOT NULL DEFAULT NULL, + " . $db->quoteName('modified') . " NUMERIC DEFAULT NULL, + " . $db->quoteName('modified_by') . " INTEGER NOT NULL DEFAULT NULL, + " . $db->quoteName('download_count') . " INTEGER DEFAULT 0 + )"; + + $db->setQuery($createTableSQL); + $db->execute(); + + return true; + } catch (\Exception $e) { + return 'Error: ' . $e->getMessage(); + } + } + + protected function tearDown(): void + { + // Reset the AttachmentsPluginManager singleton + $ref = new \ReflectionClass(AttachmentsPluginManager::class); + if ($ref->hasProperty('instance')) { + $prop = $ref->getProperty('instance'); + $prop->setAccessible(true); + $prop->setValue(null, null); + } + parent::tearDown(); + } +} diff --git a/test/attachments_component/admin/import/testParseFieldnames.php b/tests/Integration/Component/Admin/Import/ParseFieldnamesTest.php similarity index 51% rename from test/attachments_component/admin/import/testParseFieldnames.php rename to tests/Integration/Component/Admin/Import/ParseFieldnamesTest.php index fdea3ab7..f13e58c6 100644 --- a/test/attachments_component/admin/import/testParseFieldnames.php +++ b/tests/Integration/Component/Admin/Import/ParseFieldnamesTest.php @@ -1,5 +1,7 @@ index) or error message - */ - public static function parseFieldNames($file) - { - return AttachmentsImport::_parseFieldNames($file); - } -} - +use JMCameron\Component\Attachments\Administrator\Helper\AttachmentsImport; +use Joomla\CMS\Factory; +use Tests\AttachmentsDatabaseTestCase; +use Tests\Utils\CsvFileIterator; /** * Tests for ACL action permissions for various users @@ -56,33 +25,24 @@ public static function parseFieldNames($file) * @package Attachments_test * @subpackage Attachments_permissions */ -class ImportParseFieldnamesTest extends JoomlaDatabaseTestCase +class ParseFieldnamesTest extends AttachmentsDatabaseTestCase { /** * Sets up the fixture */ - protected function setUp() + protected function setUp(): void { parent::setUp(); parent::setUpBeforeClass(); // Force loading the component language - $lang = JFactory::getLanguage(); - $lang->load('com_attachments', JPATH_BASE . '/administrator/components/com_attachments'); + /** @var \Joomla\CMS\Application\WebApplication $app */ + $app = Factory::getApplication(); + $app->loadLanguage(); + $lang = Factory::getApplication()->getLanguage(); + $lang->load('com_attachments', JPATH_BASE . '/attachments_component/admin', 'en-GB', true); } - - /** - * Gets the data set to be loaded into the database during setup - * - * @return xml dataset - */ - protected function getDataSet() - { - return $this->createXMLDataSet(JPATH_TESTS . '/joomla_db.xml'); - } - - /** * * @@ -109,11 +69,10 @@ public function testParseFieldnames($test_filename, $result) } } - /** * Get the test data from CSV file */ - public function provider() + public static function provider() { return new CsvFileIterator(dirname(__FILE__) . '/testParseFieldnamesData.csv'); } diff --git a/test/attachments_component/admin/import/testImportAttachmentsData.csv b/tests/Integration/Component/Admin/Import/testImportAttachmentsData.csv similarity index 90% rename from test/attachments_component/admin/import/testImportAttachmentsData.csv rename to tests/Integration/Component/Admin/Import/testImportAttachmentsData.csv index 6f997b68..c97829b3 100644 --- a/test/attachments_component/admin/import/testImportAttachmentsData.csv +++ b/tests/Integration/Component/Admin/Import/testImportAttachmentsData.csv @@ -1,6 +1,6 @@ "testfiles/import_good.csv", 2, 0, 1 "testfiles/import_badid.csv", "Error importing attachments CSV file; attachment ID is not an integer! (bad id)", 0, 1 -"testfiles/import_nofile.csv", "ERROR: Unable to open attachments CSV file '/home/jmcameron/src/attachments/work-j1.6/test/attachments_component/admin/import/testfiles/import_nofile.csv'!", 0, 1 +"testfiles/import_nofile.csv", "ERROR: Unable to open attachments CSV file '%base_path%/testfiles/import_nofile.csv'!", 0, 1 "testfiles/import_badparentid.csv", "Error importing attachments CSV file; unknown article/parent ID=-23!", 0, 1 "testfiles/import_badtitle.csv", "Error importing attachments CSV file; title for article/parent ID=1 is 'Welcome' not '-BAD TITLE-' as specified in the CSV file!", 0, 1 "testfiles/import_badcreatorid.csv", "Error importing attachments CSV file; unable to find creator with ID=-10 (username='admin')!", 0, 1 diff --git a/test/attachments_component/admin/import/testParseFieldnamesData.csv b/tests/Integration/Component/Admin/Import/testParseFieldnamesData.csv similarity index 100% rename from test/attachments_component/admin/import/testParseFieldnamesData.csv rename to tests/Integration/Component/Admin/Import/testParseFieldnamesData.csv diff --git a/test/attachments_component/admin/import/testfiles/fieldnames_badfieldname.csv b/tests/Integration/Component/Admin/Import/testfiles/fieldnames_badfieldname.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/fieldnames_badfieldname.csv rename to tests/Integration/Component/Admin/Import/testfiles/fieldnames_badfieldname.csv diff --git a/test/attachments_component/admin/import/testfiles/fieldnames_good.csv b/tests/Integration/Component/Admin/Import/testfiles/fieldnames_good.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/fieldnames_good.csv rename to tests/Integration/Component/Admin/Import/testfiles/fieldnames_good.csv diff --git a/test/attachments_component/admin/import/testfiles/fieldnames_missing.csv b/tests/Integration/Component/Admin/Import/testfiles/fieldnames_missing.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/fieldnames_missing.csv rename to tests/Integration/Component/Admin/Import/testfiles/fieldnames_missing.csv diff --git a/test/attachments_component/admin/import/testfiles/fieldnames_noid.csv b/tests/Integration/Component/Admin/Import/testfiles/fieldnames_noid.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/fieldnames_noid.csv rename to tests/Integration/Component/Admin/Import/testfiles/fieldnames_noid.csv diff --git a/test/attachments_component/admin/import/testfiles/fieldnames_reordered.csv b/tests/Integration/Component/Admin/Import/testfiles/fieldnames_reordered.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/fieldnames_reordered.csv rename to tests/Integration/Component/Admin/Import/testfiles/fieldnames_reordered.csv diff --git a/test/attachments_component/admin/import/testfiles/import_add1.csv b/tests/Integration/Component/Admin/Import/testfiles/import_add1.csv similarity index 90% rename from test/attachments_component/admin/import/testfiles/import_add1.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_add1.csv index a8e17d1d..5cf1ac8e 100644 --- a/test/attachments_component/admin/import/testfiles/import_add1.csv +++ b/tests/Integration/Component/Admin/Import/testfiles/import_add1.csv @@ -1,2 +1,2 @@ "id", "filename", "filename_sys", "file_type", "file_size", "url", "uri_type", "url_valid", "url_relative", "display_name", "description", "icon_filename", "access", "state", "user_field_1", "user_field_2", "user_field_3", "parent_type", "parent_entity", "parent_id", "created", "created_by", "modified", "modified_by", "download_count", "parent_title", "created_by_username", "modified_by_username" -0, "file.gif", "/var/www/test/joomla16/attachments/article/1/file.gif", "image/gif", 2811, "attachments/article/1/file.gif", "file", 0, 0, "", "", "image.gif", 2, 1, "", "", "", "com_content", "article", 1, "2011-06-09 04:14:07", 42, "2011-06-09 05:12:41", 42, 1, "Welcome", "admin", "admin" +1, "file.gif", "/var/www/test/joomla16/attachments/article/1/file.gif", "image/gif", 2811, "attachments/article/1/file.gif", "file", 0, 0, "", "", "image.gif", 2, 1, "", "", "", "com_content", "article", 1, "2011-06-09 04:14:07", 42, "2011-06-09 05:12:41", 42, 1, "Welcome", "admin", "admin" diff --git a/test/attachments_component/admin/import/testfiles/import_badcreatorid.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badcreatorid.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badcreatorid.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badcreatorid.csv diff --git a/test/attachments_component/admin/import/testfiles/import_badcreatorname.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badcreatorname.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badcreatorname.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badcreatorname.csv diff --git a/test/attachments_component/admin/import/testfiles/import_badid.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badid.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badid.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badid.csv diff --git a/test/attachments_component/admin/import/testfiles/import_badmodifierid.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badmodifierid.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badmodifierid.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badmodifierid.csv diff --git a/test/attachments_component/admin/import/testfiles/import_badmodifiername.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badmodifiername.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badmodifiername.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badmodifiername.csv diff --git a/test/attachments_component/admin/import/testfiles/import_badparentid.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badparentid.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badparentid.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badparentid.csv diff --git a/test/attachments_component/admin/import/testfiles/import_badtitle.csv b/tests/Integration/Component/Admin/Import/testfiles/import_badtitle.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_badtitle.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_badtitle.csv diff --git a/test/attachments_component/admin/import/testfiles/import_good.csv b/tests/Integration/Component/Admin/Import/testfiles/import_good.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_good.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_good.csv diff --git a/test/attachments_component/admin/import/testfiles/import_windows.csv b/tests/Integration/Component/Admin/Import/testfiles/import_windows.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_windows.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_windows.csv diff --git a/test/attachments_component/admin/import/testfiles/import_zeroid.csv b/tests/Integration/Component/Admin/Import/testfiles/import_zeroid.csv similarity index 100% rename from test/attachments_component/admin/import/testfiles/import_zeroid.csv rename to tests/Integration/Component/Admin/Import/testfiles/import_zeroid.csv diff --git a/tests/Integration/Component/Admin/Permissions/ActionsTest.php b/tests/Integration/Component/Admin/Permissions/ActionsTest.php new file mode 100644 index 00000000..e863cd3f --- /dev/null +++ b/tests/Integration/Component/Admin/Permissions/ActionsTest.php @@ -0,0 +1,111 @@ +populateUsers(); + $this->populateUserGroups(); + $this->populateUserGroupMap(); + $this->populateAssets(); + } + + /** + * Test various ACL action permissions for com_attachments for various users + * + * @dataProvider provider + * + * @param string $username The name of the user (for error outputs) + * @param int $admin correct 'core.admin' permission (0/1 interpreted as bool) + * @param int $manage correct 'core.manage' permission (0/1 interpreted as bool) + * @param int $create correct 'core.create' permission (0/1 interpreted as bool) + * @param int $delete correct 'core.delete' permission (0/1 interpreted as bool) + * @param int $edit_state correct 'core.edit.state' permission (0/1 interpreted as bool) + * @param int $edit correct 'core.edit' permission (0/1 interpreted as bool) + * @param int $edit_own correct 'core.edit.own' permission (0/1 interpreted as bool) + * @param int $delete_own correct 'attachments.delete.own' permission (0/1 interpreted as bool) + */ + public function testActions($username, $admin, $manage, $create, $delete, $edit_state, $edit, $edit_own, $delete_own) + { + $user_id = UserHelper::getUserId($username); + $errmsg = "ERROR: ========> USERNAME=$username does not exist!"; + $this->assertNotEquals((int)$user_id, 0, $errmsg); + + $result = AttachmentsPermissions::getActions((int)$user_id); + $this->assertInstanceOf(Registry::class, $result); + + $errmsg = "----> Failed test for $username core.admin for com_attachments, " . + "expected ".var_export((bool)(int)$admin,true).", got " . var_export($result->get('core.admin'),true) . " for " . $username; + $this->assertEquals($result->get('core.admin'), (bool)(int)$admin, $errmsg); + + $errmsg = "----> Failed test for $username core.manage for com_attachments, " . + "expected ".var_export((bool)(int)$manage, true).", got " . var_export($result->get('core.manage'),true) . " for " . $username; + $this->assertEquals($result->get('core.manage'), (bool)(int)$manage, $errmsg); + + $errmsg = "----> Failed test for $username core.create for com_attachments, " . + "expected ".var_export((bool)(int)$create,true).", got " . var_export($result->get('core.create'),true) . " for " . $username; + $this->assertEquals($result->get('core.create'), (bool)(int)$create, $errmsg); + + $errmsg = "----> Failed test for $username core.delete for com_attachments, " . + "expected ".var_export((bool)(int)$delete,true).", got " . var_export($result->get('core.delete'),true) . " for " . $username; + $this->assertEquals($result->get('core.delete'), (bool)(int)$delete, $errmsg); + + $errmsg = "----> Failed test for $username core.edit.state for com_attachments, " . + "expected ".var_export((bool)(int)$edit_state,true).", got " . var_export($result->get('core.edit.state'),true) . " for " . $username; + $this->assertEquals($result->get('core.edit.state'), (bool)(int)$edit_state, $errmsg); + + $errmsg = "----> Failed test for $username core.edit for com_attachments, " . + "expected ".var_export((bool)(int)$edit,true).", got " . var_export($result->get('core.edit'),true) . " for " . $username; + $this->assertEquals($result->get('core.edit'), (bool)(int)$edit, $errmsg); + + $errmsg = "----> Failed test for $username core.edit.own for com_attachments, " . + "expected ".var_export((bool)(int)$edit_own,true).", got " . var_export($result->get('core.edit.own'),true) . " for " . $username; + $this->assertEquals($result->get('core.edit.own'), (bool)(int)$edit_own, $errmsg); + + $errmsg = "----> Failed test for $username attachments.delete.own for com_attachments, " . + "expected ".var_export((bool)(int)$delete_own,true).", got " . var_export($result->get('attachments.delete.own'),true) . " for " . $username; + $this->assertEquals($result->get('attachments.delete.own'), (bool)(int)$delete_own, $errmsg); + } + + /** + * Get the test data from CSV file + */ + public static function provider(): CsvFileIterator + { + $csvFile = dirname(__FILE__) .'/testActionsData.csv'; + return new CsvFileIterator($csvFile); + } +} \ No newline at end of file diff --git a/tests/Integration/Component/Admin/Permissions/ArticleEditTest.php b/tests/Integration/Component/Admin/Permissions/ArticleEditTest.php new file mode 100644 index 00000000..1e8b663a --- /dev/null +++ b/tests/Integration/Component/Admin/Permissions/ArticleEditTest.php @@ -0,0 +1,73 @@ +populateUsers(); + $this->populateUserGroups(); + $this->populateUserGroupMap(); + $this->populateAssets(); + $this->populateContent(); + $this->populateCategories(); + } + + /** + * Test to see whether a user may edit a specified article + * + * @dataProvider provider + * + * @param int $user_id the id of the user to test + * @param string $username the username (for error printouts) + * @param int $art_id the id of the article to test + * @param int $may_edit the expected result of the test + */ + public function testArticleEdit($user_id, $username, $art_id, $may_edit) + { + $result = AttachmentsPermissions::userMayEditArticle((int)$art_id, (int)$user_id); + $errmsg = "----> Failed test for $username edit article $art_id, expected $may_edit, got $result"; + + // Ensure the method returns a boolean + $this->assertIsBool($result); + $this->assertEquals($result, (bool)$may_edit, $errmsg); + } + + /** + * Get the test data from CSV file + */ + public static function provider(): CsvFileIterator + { + $csvFile = dirname(__FILE__) .'/testArticleEditData.csv'; + return new CsvFileIterator($csvFile); + } +} \ No newline at end of file diff --git a/test/attachments_component/admin/permissions/testCategoryEdit.php b/tests/Integration/Component/Admin/Permissions/CategoryEditTest.php similarity index 52% rename from test/attachments_component/admin/permissions/testCategoryEdit.php rename to tests/Integration/Component/Admin/Permissions/CategoryEditTest.php index 3a2ced09..b5f7dfd3 100644 --- a/test/attachments_component/admin/permissions/testCategoryEdit.php +++ b/tests/Integration/Component/Admin/Permissions/CategoryEditTest.php @@ -3,8 +3,8 @@ /** * Attachments component * - * @package Attachments_test - * @subpackage Attachments_permissions + * @package Attachments + * @subpackage Tests * * @copyright Copyright (C) 2007-2025 Jonathan M. Cameron, All Rights Reserved * @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL @@ -12,53 +12,36 @@ * @author Jonathan M. Cameron */ -/** Load the PHPUnit test framework */ -require_once 'PHPUnit/Framework/TestCase.php'; - -/** Load the CSV file iterator class */ -require_once JPATH_TESTS . '/utils/CsvFileIterator.php'; - -jimport('joomla.log.log'); - -jimport('joomla.plugin.plugin'); -jimport('joomla.plugin.helper'); -jimport('joomla.event.dispatcher'); -jimport('joomla.filter.filterinput'); -jimport('joomla.environment.request'); -jimport('joomla.application.component.helper'); - -require_once JPATH_BASE . '/administrator/components/com_attachments/permissions.php'; +namespace Tests\Integration\Component\Admin\Permissions; +use JMCameron\Component\Attachments\Administrator\Helper\AttachmentsPermissions; +use Tests\AttachmentsDatabaseTestCase; +use Tests\AttachmentsTestCase; +use Tests\Utils\CsvFileIterator; /** * Tests for permissions to edit categories for various users * - * @package Attachments_test - * @subpackage Attachments_permissions + * @package Attachments + * @subpackage Tests */ -class CategoryEditTest extends JoomlaDatabaseTestCase +class CategoryEditTest extends AttachmentsDatabaseTestCase { /** * Sets up the fixture */ - protected function setUp() + protected function setUp(): void { parent::setUp(); parent::setUpBeforeClass(); - } - - /** - * Gets the data set to be loaded into the database during setup - * - * @return xml dataset - */ - protected function getDataSet() - { - return $this->createXMLDataSet(JPATH_TESTS . '/joomla_db.xml'); + $this->populateUsers(); + $this->populateUserGroups(); + $this->populateUserGroupMap(); + $this->populateAssets(); + $this->populateCategories(); } - /** * Test to see whether a user may edit a specified category * @@ -74,14 +57,17 @@ public function testCategoryEdit($user_id, $username, $cat_id, $may_edit) $result = AttachmentsPermissions::userMayEditCategory((int)$cat_id, (int)$user_id); $errmsg = "----> Failed test for $username edit category $cat_id, expected $may_edit, got $result"; + // Ensure the method returns a boolean + $this->assertIsBool($result); $this->assertEquals($result, (bool)$may_edit, $errmsg); } /** * Get the test data from CSV file */ - public function provider() + public static function provider(): CsvFileIterator { - return new CsvFileIterator(dirname(__FILE__) . '/testCategoryEditData.csv'); + $csvFile = dirname(__FILE__) .'/testCategoryEditData.csv'; + return new CsvFileIterator($csvFile); } -} +} \ No newline at end of file diff --git a/test/attachments_component/admin/permissions/testActionsData.csv b/tests/Integration/Component/Admin/Permissions/testActionsData.csv similarity index 100% rename from test/attachments_component/admin/permissions/testActionsData.csv rename to tests/Integration/Component/Admin/Permissions/testActionsData.csv diff --git a/test/attachments_component/admin/permissions/testArticleEditData.csv b/tests/Integration/Component/Admin/Permissions/testArticleEditData.csv similarity index 100% rename from test/attachments_component/admin/permissions/testArticleEditData.csv rename to tests/Integration/Component/Admin/Permissions/testArticleEditData.csv diff --git a/test/attachments_component/admin/permissions/testCategoryEditData.csv b/tests/Integration/Component/Admin/Permissions/testCategoryEditData.csv similarity index 100% rename from test/attachments_component/admin/permissions/testCategoryEditData.csv rename to tests/Integration/Component/Admin/Permissions/testCategoryEditData.csv diff --git a/test/attachments_component/site/file_types/testFileTypesConversions.php b/tests/Integration/Component/Site/FileTypes/FileTypeConversionsTest.php similarity index 66% rename from test/attachments_component/site/file_types/testFileTypesConversions.php rename to tests/Integration/Component/Site/FileTypes/FileTypeConversionsTest.php index f319b3a7..c196d9dc 100644 --- a/test/attachments_component/site/file_types/testFileTypesConversions.php +++ b/tests/Integration/Component/Site/FileTypes/FileTypeConversionsTest.php @@ -3,8 +3,8 @@ /** * Attachments component * - * @package Attachments_test - * @subpackage Attachments_file_types + * @package Attachments + * @subpackage Tests * * @copyright Copyright (C) 2007-2025 Jonathan M. Cameron, All Rights Reserved * @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL @@ -12,21 +12,19 @@ * @author Jonathan M. Cameron */ -use JMCameron\Component\Attachments\Site\Helper\AttachmentsFileTypes; - -/** Load the PHPUnit test framework */ -require_once 'PHPUnit/Framework/TestCase.php'; +namespace Tests\Integration\Component\Site\FileTypes; -/** Load the CSV file iterator class */ -require_once JPATH_TESTS . '/utils/CsvFileIterator.php'; +use JMCameron\Component\Attachments\Site\Helper\AttachmentsFileTypes; +use Tests\AttachmentsTestCase; +use Tests\Utils\CsvFileIterator; /** * Tests for file_type conversion functions * - * @package Attachments_test - * @subpackage Attachments_file_types + * @package Attachments + * @subpackage Tests */ -class FileTypeConversionsTest extends PHPUnit_Framework_TestCase +class FileTypeConversionsTest extends AttachmentsTestCase { /** * Test various file type and mime type conversions to icon filenames @@ -42,15 +40,15 @@ public function testConversions($filename, $iconFilename, $mime_type) $this->assertEquals($iconFilename, AttachmentsFileTypes::iconFilename($filename, $mime_type)); if ($filename) { - $this->assertEquals($mime_type, AttachmentsFileTypes::mime_type($filename)); + $this->assertEquals($mime_type, AttachmentsFileTypes::mimeType($filename)); } } /** * Get the test data from CSV file */ - public function provider() + public static function provider(): CsvFileIterator { - return new CsvFileIterator(dirname(__FILE__) . '/testFileTypesConversionsData.csv'); + return new CsvFileIterator(__DIR__ . '/testFileTypesConversionsData.csv'); } -} +} \ No newline at end of file diff --git a/test/attachments_component/site/file_types/testFileTypesRoundTrip.php b/tests/Integration/Component/Site/FileTypes/FileTypesRoundTripTest.php similarity index 81% rename from test/attachments_component/site/file_types/testFileTypesRoundTrip.php rename to tests/Integration/Component/Site/FileTypes/FileTypesRoundTripTest.php index 889d65b3..3f16bbe2 100644 --- a/test/attachments_component/site/file_types/testFileTypesRoundTrip.php +++ b/tests/Integration/Component/Site/FileTypes/FileTypesRoundTripTest.php @@ -1,5 +1,7 @@ assertEquals($icon, AttachmentsFileTypes::iconFilename('', $mime_type)); + } + } + } +} \ No newline at end of file diff --git a/test/attachments_component/site/helper/testHelperFilenameTruncation.php b/tests/Integration/Component/Site/Helper/HelperFilenameTruncationTest.php similarity index 53% rename from test/attachments_component/site/helper/testHelperFilenameTruncation.php rename to tests/Integration/Component/Site/Helper/HelperFilenameTruncationTest.php index 826a8c9e..5f3f0d81 100644 --- a/test/attachments_component/site/helper/testHelperFilenameTruncation.php +++ b/tests/Integration/Component/Site/Helper/HelperFilenameTruncationTest.php @@ -3,8 +3,8 @@ /** * Attachments component * - * @package Attachments_test - * @subpackage Attachments_helper + * @package Attachments + * @subpackage Tests * * @copyright Copyright (C) 2007-2025 Jonathan M. Cameron, All Rights Reserved * @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL @@ -12,19 +12,17 @@ * @author Jonathan M. Cameron */ -use JMCameron\Component\Attachments\Site\Helper\AttachmentsHelper; - -/** Load the PHPUnit test framework */ -require_once 'PHPUnit/Framework/TestCase.php'; +namespace Tests\Integration\Component\Site\Helper; -/** Load the CSV file iterator class */ -require_once JPATH_TESTS . '/utils/CsvFileIterator.php'; +use JMCameron\Component\Attachments\Site\Helper\AttachmentsHelper; +use Tests\AttachmentsTestCase; +use Tests\Utils\CsvFileIterator; /** * Work-around class to expose protected method for testing * - * @package Attachments_test - * @subpackage Attachments_helper + * @package Attachments + * @subpackage Tests */ class AttachmentsHelper2 extends AttachmentsHelper { @@ -37,29 +35,28 @@ class AttachmentsHelper2 extends AttachmentsHelper * * @return the truncated filename */ - public static function truncate_filename($raw_filename, $maxlen) + public static function truncateFilename($raw_filename, $maxlen) { - return parent::truncate_filename($raw_filename, $maxlen); + return parent::truncateFilename($raw_filename, $maxlen); } } - /** - * Tests filename trunction + * Tests filename truncation * - * @package Attachments_test - * @subpackage Attachments_helper + * @package Attachments + * @subpackage Tests */ -class HelperFilenameTruncationTest extends PHPUnit_Framework_TestCase +class HelperFilenameTruncationTest extends AttachmentsTestCase { /** - * Test filename trunction + * Test filename truncation * * @dataProvider provider * - * @param string @truncated_filename the expected result of the truncation - * @param strint @full_filename the filename before truncating - * @param int the maximum length for the filename + * @param string $truncated_filename the expected result of the truncation + * @param string $full_filename the filename before truncating + * @param int $maxlen the maximum length for the filename */ public function testFilenameTruncation($truncated_filename, $full_filename, $maxlen) { @@ -67,15 +64,15 @@ public function testFilenameTruncation($truncated_filename, $full_filename, $max $this->assertEquals( $truncated_filename, - AttachmentsHelper2::truncate_filename($full_filename, $maxlen) + AttachmentsHelper2::truncateFilename($full_filename, $maxlen) ); } /** * Get the test data from CSV file */ - public function provider() + public static function provider(): CsvFileIterator { - return new CsvFileIterator(dirname(__FILE__) . '/testHelperFilenameTruncationData.csv'); + return new CsvFileIterator(__DIR__ . '/testHelperFilenameTruncationData.csv'); } -} +} \ No newline at end of file diff --git a/test/attachments_component/site/helper/testHelperURLTruncation.php b/tests/Integration/Component/Site/Helper/HelperURLTruncationTest.php similarity index 66% rename from test/attachments_component/site/helper/testHelperURLTruncation.php rename to tests/Integration/Component/Site/Helper/HelperURLTruncationTest.php index bbdddbfd..a4e7f185 100644 --- a/test/attachments_component/site/helper/testHelperURLTruncation.php +++ b/tests/Integration/Component/Site/Helper/HelperURLTruncationTest.php @@ -3,8 +3,8 @@ /** * Attachments component * - * @package Attachments_test - * @subpackage Attachments_helper + * @package Attachments + * @subpackage Tests * * @copyright Copyright (C) 2007-2025 Jonathan M. Cameron, All Rights Reserved * @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL @@ -12,19 +12,17 @@ * @author Jonathan M. Cameron */ -use JMCameron\Component\Attachments\Site\Helper\AttachmentsHelper; - -/** Load the PHPUnit test framework */ -require_once 'PHPUnit/Framework/TestCase.php'; +namespace Tests\Integration\Component\Site\Helper; -/** Load the CSV file iterator class */ -require_once JPATH_TESTS . '/utils/CsvFileIterator.php'; +use JMCameron\Component\Attachments\Site\Helper\AttachmentsHelper; +use Tests\AttachmentsTestCase; +use Tests\Utils\CsvFileIterator; /** * Work-around class to expose protected method for testing * - * @package Attachments_test - * @subpackage Attachments_helper + * @package Attachments + * @subpackage Tests */ class AttachmentsHelper3 extends AttachmentsHelper { @@ -41,20 +39,19 @@ class AttachmentsHelper3 extends AttachmentsHelper * * @return the truncated URL */ - public static function truncate_url($raw_url, $maxlen) + public static function truncateUrl($raw_url, $maxlen) { - return parent::truncate_url($raw_url, $maxlen); + return parent::truncateUrl($raw_url, $maxlen); } } - /** - * Tests URL trunction + * Tests URL truncation * - * @package Attachments_test - * @subpackage Attachments_helper + * @package Attachments + * @subpackage Tests */ -class HelperURLTruncationTest extends PHPUnit_Framework_TestCase +class HelperURLTruncationTest extends AttachmentsTestCase { /** * Test truncating a url @@ -71,7 +68,7 @@ public function testURLTruncation($truncated_url, $full_url, $maxlen) $this->assertEquals( $truncated_url, - AttachmentsHelper3::truncate_url($full_url, $maxlen) + AttachmentsHelper3::truncateUrl($full_url, $maxlen) ); } @@ -79,8 +76,8 @@ public function testURLTruncation($truncated_url, $full_url, $maxlen) /** * Get the test data from CSV file */ - public function provider() + public static function provider(): CsvFileIterator { - return new CsvFileIterator(dirname(__FILE__) . '/testHelperURLTruncationData.csv'); + return new CsvFileIterator(__DIR__ . '/testHelperURLTruncationData.csv'); } -} +} \ No newline at end of file diff --git a/test/attachments_component/site/helper/testHelperFilenameTruncationData.csv b/tests/Integration/Component/Site/Helper/testHelperFilenameTruncationData.csv similarity index 100% rename from test/attachments_component/site/helper/testHelperFilenameTruncationData.csv rename to tests/Integration/Component/Site/Helper/testHelperFilenameTruncationData.csv diff --git a/test/attachments_component/site/helper/testHelperURLTruncationData.csv b/tests/Integration/Component/Site/Helper/testHelperURLTruncationData.csv similarity index 100% rename from test/attachments_component/site/helper/testHelperURLTruncationData.csv rename to tests/Integration/Component/Site/Helper/testHelperURLTruncationData.csv diff --git a/tests/Stubs/empty.xml b/tests/Stubs/empty.xml new file mode 100644 index 00000000..2024cb9e --- /dev/null +++ b/tests/Stubs/empty.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/Unit/AttachmentsBasicTest.php b/tests/Unit/AttachmentsBasicTest.php new file mode 100644 index 00000000..f3f756cc --- /dev/null +++ b/tests/Unit/AttachmentsBasicTest.php @@ -0,0 +1,46 @@ +assertDirectoryExists($sourceFile, 'Attachments component directory should exist'); + + // Check AttachmentsHelper class existence + $this->assertTrue(class_exists('JMCameron\Component\Attachments\Site\Helper\AttachmentsHelper'), 'AttachmentsHelper class should exist'); + } + + public function testBasicAssertions(): void + { + $this->assertTrue(true, 'Basic true assertion should pass'); + $this->assertIsString("test", 'String assertion should pass'); + $this->assertNotEmpty(array("item"), 'Array should not be empty'); + } +} diff --git a/tests/Unit/Helper/AttachmentsInstallTest.php b/tests/Unit/Helper/AttachmentsInstallTest.php new file mode 100644 index 00000000..ad553ec8 --- /dev/null +++ b/tests/Unit/Helper/AttachmentsInstallTest.php @@ -0,0 +1,39 @@ +assertTrue($reflection->isPublic()); + $this->assertTrue($reflection->isStatic()); + } +} \ No newline at end of file diff --git a/tests/Unit/Helper/AttachmentsPermissionsTest.php b/tests/Unit/Helper/AttachmentsPermissionsTest.php new file mode 100644 index 00000000..2c306d1b --- /dev/null +++ b/tests/Unit/Helper/AttachmentsPermissionsTest.php @@ -0,0 +1,129 @@ +setUpUserWithPermissions([ + 'core.admin' => true, + 'core.manage' => true, + 'core.create' => true, + 'core.delete' => true, + 'core.edit' => true, + 'core.edit.state' => true, + 'core.edit.own' => true, + 'attachments.edit.state.own' => true, + 'attachments.delete.own' => true + ], ['id' => 42, 'groups' => [8]]); // Group 8 is Super Users in Joomla + + // Test that the method returns permissions for super admin + $result = AttachmentsPermissions::getActions(42); + + $this->assertInstanceOf(Registry::class, $result); + + // Super admin should have all permissions + $this->assertTrue($result->get('core.admin'), 'Super admin should have core.admin'); + $this->assertTrue($result->get('core.manage'), 'Super admin should have core.manage'); + $this->assertTrue($result->get('core.create'), 'Super admin should have core.create'); + $this->assertTrue($result->get('core.delete'), 'Super admin should have core.delete'); + $this->assertTrue($result->get('core.edit'), 'Super admin should have core.edit'); + $this->assertTrue($result->get('core.edit.state'), 'Super admin should have core.edit.state'); + } + + /** + * Test permissions for a regular editor + */ + public function testGetActionsEditor() + { + // Set up an editor user with typical permissions + $this->setUpUserWithPermissions([ + 'core.admin' => false, + 'core.manage' => false, + 'core.create' => true, + 'core.edit.own' => true, + 'attachments.edit.state.own' => true, + 'attachments.delete.own' => true + ], ['id' => 43, 'groups' => [4]]); // Group 4 is Author in Joomla + + $result = AttachmentsPermissions::getActions(43); + + // Editor should have limited permissions + $this->assertFalse($result->get('core.admin'), 'Editor should not have core.admin'); + $this->assertFalse($result->get('core.manage'), 'Editor should not have core.manage'); + $this->assertTrue($result->get('core.create'), 'Editor should have core.create'); + $this->assertTrue($result->get('core.edit.own'), 'Editor should have core.edit.own'); + $this->assertTrue($result->get('attachments.edit.state.own'), 'Editor should have attachments.edit.state.own'); + $this->assertTrue($result->get('attachments.delete.own'), 'Editor should have attachments.delete.own'); + $this->assertFalse($result->get('core.edit'), 'Editor should not have core.edit'); + $this->assertFalse($result->get('core.edit.state'), 'Editor should not have core.edit.state'); + } + + /** + * Test userMayEditCategory method for admin user + */ + public function testUserMayEditCategoryAsAdmin() + { + // Set up an admin user with full permissions + $this->setUpUserWithPermissions([ + 'core.edit' => true, + 'core.edit.own' => true + ], ['id' => 42, 'groups' => [8]]); // Group 8 is Super Users + + $result = AttachmentsPermissions::userMayEditCategory(1, 42); + $this->assertTrue($result, 'Admin should be able to edit any category'); + } + + /** + * Test userMayEditCategory method for regular user + */ + public function testUserMayEditCategoryAsRegularUser() + { + // Set up a regular user with limited permissions + $this->setUpUserWithPermissions([ + 'core.edit' => false, + 'core.edit.own' => true + ], ['id' => 43, 'groups' => [2]]); // Group 2 is Registered Users + + $result = AttachmentsPermissions::userMayEditCategory(1, 43); + $this->assertFalse($result, 'Regular user should not be able to edit categories'); + } + + /** + * Test userMayEditArticle method for admin user + */ + public function testUserMayEditArticleAsAdmin() + { + // Set up an admin user with full permissions + $this->setUpUserWithPermissions([ + 'core.edit' => true, + 'core.edit.own' => true + ], ['id' => 42, 'groups' => [8]]); // Group 8 is Super Users + + $result = AttachmentsPermissions::userMayEditArticle(1, 42); + $this->assertTrue($result, 'Admin should be able to edit any article'); + } + + /** + * Test userMayEditArticle method for regular user + */ + public function testUserMayEditArticleAsRegularUser() + { + // Set up a regular user with limited permissions + $this->setUpUserWithPermissions([ + 'core.edit' => false, + 'core.edit.own' => true + ], ['id' => 43, 'groups' => [2]]); // Group 2 is Registered Users + + $result = AttachmentsPermissions::userMayEditArticle(1, 43); + $this->assertFalse($result, 'Regular user should not be able to edit articles without permission'); + } +} \ No newline at end of file diff --git a/tests/Unit/Helper/AttachmentsUpdateTest.php b/tests/Unit/Helper/AttachmentsUpdateTest.php new file mode 100644 index 00000000..28f4b59a --- /dev/null +++ b/tests/Unit/Helper/AttachmentsUpdateTest.php @@ -0,0 +1,43 @@ +assertTrue($method->isPublic()); + $this->assertTrue($method->isStatic()); + $this->assertEquals(1, $method->getNumberOfParameters()); + } +} \ No newline at end of file diff --git a/test/utils/CsvFileIterator.php b/tests/Utils/CsvFileIterator.php similarity index 56% rename from test/utils/CsvFileIterator.php rename to tests/Utils/CsvFileIterator.php index 6d2eb788..704d431f 100644 --- a/test/utils/CsvFileIterator.php +++ b/tests/Utils/CsvFileIterator.php @@ -3,18 +3,20 @@ /** * Copied from the PHPUnit documentation * - * @package Attachments_test - * @subpackage Attachments_utils + * @package Attachments + * @subpackage Tests */ +namespace Tests\Utils; + /** * Class to iterate through a Comma-Separated-Value file * - * @package Attachments_test - * @subpackage Attachments_utils + * @package Attachments + * @subpackage Tests */ -class CsvFileIterator implements Iterator +class CsvFileIterator implements \Iterator { /** the filename */ protected $filename; @@ -42,39 +44,45 @@ public function __construct($filename) /** Destructor */ public function __destruct() { - fclose($this->file); + if ($this->file) { + fclose($this->file); + } } /** Rewind to the beginning of the file */ - public function rewind() + public function rewind(): void { - rewind($this->file); - $this->current = fgetcsv($this->file); - $this->key = 0; + if ($this->file) { + rewind($this->file); + $this->current = fgetcsv($this->file, 0, ',', '"', '\\'); + $this->key = 0; + } } /** @return if the file is valid (not at the end) */ - public function valid() + public function valid(): bool { - return !feof($this->file); + return $this->file && !feof($this->file); } /** @return the key */ - public function key() + public function key(): mixed { return $this->key; } /** Get the current line of data */ - public function current() + public function current(): mixed { return $this->current; } /** Read the next line of data */ - public function next() + public function next(): void { - $this->current = fgetcsv($this->file); - $this->key++; + if ($this->file) { + $this->current = fgetcsv($this->file, 0, ',', '"', '\\'); + $this->key++; + } } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..b5c74ce5 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,49 @@ +