diff --git a/.gitignore b/.gitignore index a66bdc8f7..4bd4fee69 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ public/wp-content/themes/twentytwentyone/package-lock.json **/.DS_Store public/wp-content/plugins/ccs-custom/library/user-activity-log.txt + +public/wp-content/plugins/ccs-mdm/vendor diff --git a/public/wp-content/plugins/ccs-mdm/ccs-mdm-import.php b/public/wp-content/plugins/ccs-mdm/ccs-mdm-import.php new file mode 100644 index 000000000..55f02019b --- /dev/null +++ b/public/wp-content/plugins/ccs-mdm/ccs-mdm-import.php @@ -0,0 +1,22 @@ +=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + }, + "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/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-08-27T14:37:49+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.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": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.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": "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/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.46", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", + "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.2", + "phpunit/php-code-coverage": "^11.0.11", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.2", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.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/11.5.46" + }, + "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-12-06T08:01:15+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.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": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + }, + "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": "2025-08-10T08:07:46+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "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" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.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/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "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": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "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/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "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/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "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" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "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": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "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/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/public/wp-content/plugins/ccs-mdm/includes/ImportTest.php b/public/wp-content/plugins/ccs-mdm/includes/ImportTest.php new file mode 100644 index 000000000..c87ab65a5 --- /dev/null +++ b/public/wp-content/plugins/ccs-mdm/includes/ImportTest.php @@ -0,0 +1,447 @@ +mdmApiMock = Mockery::mock('App\Services\MDM\MdmApi'); + $this->frameworkRepoMock = Mockery::mock('App\Repository\FrameworkRepository'); + $this->lotRepoMock = Mockery::mock('App\Repository\LotRepository'); + $this->lotSupplierRepoMock = Mockery::mock('App\Repository\LotSupplierRepository'); + $this->supplierRepoMock = Mockery::mock('App\Repository\SupplierRepository'); + $this->dbManagerMock = Mockery::mock('CCS\MDMImport\dbManager'); + $this->syncTextMock = Mockery::mock('CCS\MDMImport\SyncText'); + $this->loggerMock = Mockery::mock('App\Services\Logger\ImportLogger'); + $this->frameworkSearchClientMock = Mockery::mock('App\Search\FrameworkSearchClient'); + $this->supplierSearchClientMock = Mockery::mock('App\Search\SupplierSearchClient'); + $this->lockFactoryMock = Mockery::mock('Symfony\Component\Lock\LockFactory'); + $this->lockMock = Mockery::mock('Symfony\Component\Lock\LockInterface'); + + // 2. Instantiate without constructor to avoid the "FlockStore" error + $reflection = new ReflectionClass(Import::class); + $this->import = $reflection->newInstanceWithoutConstructor(); + + // 3. Inject ALL properties (only once) + $this->setProtectedProperty('mdmApi', $this->mdmApiMock); + $this->setProtectedProperty('frameworkRepository', $this->frameworkRepoMock); + $this->setProtectedProperty('lotRepository', $this->lotRepoMock); + $this->setProtectedProperty('lotSupplierRepository', $this->lotSupplierRepoMock); + $this->setProtectedProperty('supplierRepository', $this->supplierRepoMock); + $this->setProtectedProperty('dbManager', $this->dbManagerMock); + $this->setProtectedProperty('syncText', $this->syncTextMock); + $this->setProtectedProperty('logger', $this->loggerMock); + $this->setProtectedProperty('frameworkSearchClient', $this->frameworkSearchClientMock); + $this->setProtectedProperty('supplierSearchClient', $this->supplierSearchClientMock); + $this->setProtectedProperty('lockFactory', $this->lockFactoryMock); + + $this->setProtectedProperty('importCount', ['frameworks' => 0, 'lots' => 0, 'suppliers' => 0]); + $this->setProtectedProperty('errorCount', ['frameworks' => 0, 'lots' => 0, 'suppliers' => 0]); + + // 4. Set up default Lock behavior + $this->lockFactoryMock->shouldReceive('createLock')->andReturn($this->lockMock); + $this->lockMock->shouldReceive('acquire')->andReturn(true); + $this->lockMock->shouldReceive('release')->byDefault(); + } + + protected function tearDown(): void + { + Mockery::close(); + parent::tearDown(); + } + + protected function setProtectedProperty($property, $value) + { + $reflection = new ReflectionClass($this->import); + $reflection_property = $reflection->getProperty($property); + $reflection_property->setAccessible(true); + $reflection_property->setValue($this->import, $value); + } + + public function testImportSingleSuccess() +{ + $rmNumber = 'RM1001'; + $sfId = 'sf-framework-1'; + $lotSfId = 'sf-lot-1'; + + // 1. SyncText Mocking + $this->syncTextMock->shouldReceive('getFrameworksFromWordPress')->once()->andReturn([]); + $this->syncTextMock->shouldReceive('getLotsFromWordPress')->once()->andReturn([]); + + // 2. Framework Mocking + $framework = Mockery::mock(Framework::class); + $framework->shouldReceive('getSalesforceId')->andReturn($sfId); + $framework->shouldReceive('getTitle')->andReturn('Test Framework'); + $framework->shouldReceive('getWordpressId')->andReturn(null); + $framework->shouldReceive('setWordpressId')->with('12345'); + + $this->mdmApiMock->shouldReceive('getAgreement')->with($rmNumber)->once()->andReturn($framework); + + $this->frameworkRepoMock->shouldReceive('createOrUpdateExcludingWordpressFields') + ->with('salesforce_id', $sfId, $framework)->once(); + + $this->frameworkRepoMock->shouldReceive('findById') + ->with($sfId, 'salesforce_id')->once()->andReturn($framework); + + $this->frameworkRepoMock->shouldReceive('update') + ->with('salesforce_id', $sfId, $framework, true)->once(); + + // 3. Lot Mocking + $lot = Mockery::mock(Lot::class); + $lot->shouldReceive('getSalesforceId')->andReturn($lotSfId); + $lot->shouldReceive('getTitle')->andReturn('Test Lot'); + $lot->shouldReceive('getWordpressId')->andReturn(null); + + // Expect two calls - first one with null from DB, second with 12345 from WP creation + $lot->shouldReceive('setWordpressId')->with(null)->once(); + $lot->shouldReceive('setWordpressId')->with(12345)->once(); + + $lot->shouldReceive('isHideSuppliers')->andReturn(false); + + $lots = [$lot]; + $this->mdmApiMock->shouldReceive('getAgreementLots')->with($sfId)->once()->andReturn($lots); + + $this->dbManagerMock->shouldReceive('getLotSalesforceIdByFrameworkId')->with($sfId)->andReturn([]); + $this->dbManagerMock->shouldReceive('getLotWordpressIdBySalesforceId')->with($lotSfId)->andReturn(null); + + $this->lotRepoMock->shouldReceive('createOrUpdateExcludingWordpressFields') + ->with('salesforce_id', $lotSfId, $lot)->once(); + + $this->lotRepoMock->shouldReceive('findById') + ->with($lotSfId, 'salesforce_id')->andReturn($lot); + + $this->lotRepoMock->shouldReceive('update') + ->with('salesforce_id', $lotSfId, $lot, true)->once(); + + // 4. Other dependencies + $this->mdmApiMock->shouldReceive('getLotSuppliers')->with($lotSfId)->andReturn([]); + $this->dbManagerMock->shouldReceive('getLotSuppliersSalesforceIdByLotId')->with($lotSfId)->andReturn([]); + $this->supplierRepoMock->shouldReceive('findAll')->andReturn([]); + $this->dbManagerMock->shouldReceive('updateFrameworkTitleInWordpress')->once(); + $this->dbManagerMock->shouldReceive('updateLotTitleInWordpress')->once(); + + $this->frameworkRepoMock->shouldReceive('printImportCount'); + $this->lotRepoMock->shouldReceive('printImportCount'); + $this->lotSupplierRepoMock->shouldReceive('printImportCount'); + + // FIX: Using try/finally ensures the output buffer is cleaned up even if assertions fail + ob_start(); + try { + $this->import->importSingle([$rmNumber]); + } finally { + ob_end_clean(); + } + + $this->assertTrue(true); +} + + public function testCheckAndDeleteLots(): void + { + // 1. Define local variables to fix the "Undefined variable" warning + $rmNumber = 'RM1001'; + $frameworkId = 'sf-f-1'; + $lotIdToDelete = 'lot-2'; + + // 2. Setup the Framework Mock + $framework = Mockery::mock(Framework::class); + $framework->shouldReceive('getSalesforceId')->andReturn($frameworkId); + $framework->shouldReceive('getRmNumber')->andReturn($rmNumber); + + // 3. Setup Logger Expectation (Matches the new PHP 8 string format) + $this->loggerMock->shouldReceive('info') + ->with("Deleting lot: $lotIdToDelete from $rmNumber") + ->once(); + + // 4. Setup DB Manager Mock + $this->dbManagerMock->shouldReceive('getLotSalesforceIdByFrameworkId') + ->with($frameworkId) + ->andReturn([$lotIdToDelete]); + + $this->dbManagerMock->shouldReceive('getLotWordpressIdBySalesforceId') + ->with($lotIdToDelete) + ->andReturn(99); + + $this->dbManagerMock->shouldReceive('deleteLotPostInWordpress') + ->with(99) + ->once(); + + // 5. Setup Lot Repository Mock + // CHECK THIS NAME: Ensure it matches the property in your setUp() method + $this->lotRepoMock->shouldReceive('delete') + ->with($lotIdToDelete) + ->once(); + + // 6. Execute the method under test + $this->import->checkAndDeleteLots([], $framework); + + $this->assertTrue(true); + } + + public function testCheckAndDeleteSuppliers() + { + // Access private method via Reflection + $method = new \ReflectionMethod(Import::class, 'checkAndDeleteSuppliers'); + $method->setAccessible(true); + + $lotId = 'sf-lot-1'; + + // Mock Suppliers from API (only supp-1 exists remotely) + $supplier1 = Mockery::mock(\App\Model\Supplier::class); + $supplier1->shouldReceive('getSalesforceId')->andReturn('supp-1'); + $suppliersFromApi = [$supplier1]; + + // Mock DB returning supp-1 and supp-2 (supp-2 needs deletion) + $this->dbManagerMock->shouldReceive('getLotSuppliersSalesforceIdByLotId') + ->with($lotId) + ->once() + ->andReturn(['supp-1', 'supp-2']); + + // Expectations for deletion of supp-2 + $this->lotSupplierRepoMock->shouldReceive('deleteByLotIdAndSupplierId') + ->with($lotId, 'supp-2') + ->once(); + + // Execute + $method->invoke($this->import, $suppliersFromApi, $lotId); + + $this->assertTrue(true); + } + + public function testImportSingleWithExistingWordPressIds() + { + $rmNumber = 'RM999'; + $sfId = 'sf-existing-1'; + $wpId = 555; + + // Framework Setup + $framework = Mockery::mock(Framework::class); + $framework->shouldReceive('getSalesforceId')->andReturn($sfId); + $framework->shouldReceive('getWordpressId')->andReturn($wpId); + $framework->shouldReceive('getTitle')->andReturn('Existing Framework'); + + $this->mdmApiMock->shouldReceive('getAgreement')->with($rmNumber)->andReturn($framework); + $this->syncTextMock->shouldReceive('getFrameworksFromWordPress')->andReturn([]); + $this->syncTextMock->shouldReceive('getLotsFromWordPress')->andReturn([]); + + $this->frameworkRepoMock->shouldReceive('createOrUpdateExcludingWordpressFields')->once(); + $this->frameworkRepoMock->shouldReceive('findById')->andReturn($framework); + + // API logic + $this->mdmApiMock->shouldReceive('getAgreementLots')->andReturn([]); + $this->dbManagerMock->shouldReceive('getLotSalesforceIdByFrameworkId')->andReturn([]); + $this->supplierRepoMock->shouldReceive('findAll')->andReturn([]); + + // WordPress Updates + $this->dbManagerMock->shouldReceive('updateFrameworkTitleInWordpress')->once(); + $this->dbManagerMock->shouldReceive('updateLotTitleInWordpress')->once(); + + // MISSING EXPECTATIONS: printSummary() calls these + $this->frameworkRepoMock->shouldReceive('printImportCount')->once(); + $this->lotRepoMock->shouldReceive('printImportCount')->once(); + $this->lotSupplierRepoMock->shouldReceive('printImportCount')->once(); + + // Use try/finally to close the buffer + ob_start(); + try { + $this->import->importSingle([$rmNumber]); + } finally { + ob_end_clean(); + } + + $this->assertTrue(true); + } + + public function testImportLotWithHiddenSuppliers() + { + $rmNumber = 'RM1234'; + $sfId = 'sf-framework-1'; + $lotSfId = 'lot-hidden-1'; + + // 1. Basic Setup for Framework + $this->syncTextMock->shouldReceive('getFrameworksFromWordPress')->andReturn([]); + $this->syncTextMock->shouldReceive('getLotsFromWordPress')->andReturn([]); + + $framework = Mockery::mock(Framework::class); + $framework->shouldReceive('getSalesforceId')->andReturn($sfId); + $framework->shouldReceive('getWordpressId')->andReturn('100'); + $framework->shouldReceive('getTitle')->andReturn('Test Framework'); + + $this->mdmApiMock->shouldReceive('getAgreement')->with($rmNumber)->andReturn($framework); + $this->frameworkRepoMock->shouldReceive('createOrUpdateExcludingWordpressFields')->once(); + $this->frameworkRepoMock->shouldReceive('findById')->andReturn($framework); + + // 2. Setup the Hidden Lot + $lot = Mockery::mock(Lot::class); + $lot->shouldReceive('getSalesforceId')->andReturn($lotSfId); + $lot->shouldReceive('getWordpressId')->andReturn(200); + $lot->shouldReceive('getTitle')->andReturn('Hidden Lot'); + $lot->shouldReceive('isHideSuppliers')->andReturn(true); // Trigger the deletion logic + // Add these to satisfy the update/find calls in the loop + $lot->shouldReceive('setWordpressId'); + + // Crucial: The API must return this lot for the loop to run + $this->mdmApiMock->shouldReceive('getAgreementLots')->with($sfId)->andReturn([$lot]); + + // 3. Repository and DB Expectations + $this->dbManagerMock->shouldReceive('getLotSalesforceIdByFrameworkId')->andReturn([]); + $this->dbManagerMock->shouldReceive('getLotWordpressIdBySalesforceId')->andReturn('200'); + $this->lotRepoMock->shouldReceive('createOrUpdateExcludingWordpressFields')->once(); + $this->lotRepoMock->shouldReceive('findById')->andReturn($lot); + + // This is the call that was previously failing (called 0 times) + $this->lotSupplierRepoMock->shouldReceive('deleteById') + ->with($lotSfId, 'lot_id') + ->once(); + + // 4. Finalizing expectations (Summary and Cleanup) + $this->supplierRepoMock->shouldReceive('findAll')->andReturn([]); + $this->dbManagerMock->shouldReceive('updateFrameworkTitleInWordpress')->once(); + $this->dbManagerMock->shouldReceive('updateLotTitleInWordpress')->once(); + $this->frameworkRepoMock->shouldReceive('printImportCount')->once(); + $this->lotRepoMock->shouldReceive('printImportCount')->once(); + $this->lotSupplierRepoMock->shouldReceive('printImportCount')->once(); + + ob_start(); + try { + $this->import->importSingle([$rmNumber]); + } finally { + ob_end_clean(); + } + + $this->assertTrue(true); + } + + public function testImportSingleHandlesApiFailure() + { + $rmNumber = 'RM-FAIL'; + + $this->syncTextMock->shouldReceive('getFrameworksFromWordPress')->andReturn([]); + $this->syncTextMock->shouldReceive('getLotsFromWordPress')->andReturn([]); + + // Simulate API throwing an exception + $this->mdmApiMock->shouldReceive('getAgreement') + ->with($rmNumber) + ->andThrow(new \Exception("API Timeout")); + + // Expect the logger to receive the error message + $this->loggerMock->shouldReceive('error') + ->with(Mockery::pattern('/Something went wrong while importing RM-FAIL/')) + ->once(); + + //These are called during the post-import cleanup tasks + $this->supplierRepoMock->shouldReceive('findAll')->andReturn([]); + $this->dbManagerMock->shouldReceive('updateFrameworkTitleInWordpress'); + $this->dbManagerMock->shouldReceive('updateLotTitleInWordpress'); + $this->frameworkRepoMock->shouldReceive('printImportCount'); + $this->lotRepoMock->shouldReceive('printImportCount'); + $this->lotSupplierRepoMock->shouldReceive('printImportCount'); + + ob_start(); + try { + $this->import->importSingle([$rmNumber]); + } finally { + // This ensures the buffer is closed even if Mockery throws an exception + ob_end_clean(); + } + + $this->assertTrue(true); + } + + public function testImportSingleFailsOnInvalidFrameworkData() + { + $rmNumber = 'RM_INVALID'; + + // Mock API returning null + $this->mdmApiMock->shouldReceive('getAgreement')->with($rmNumber)->andReturn(null); + + // Mock logger to expect the error message + $this->loggerMock->shouldReceive('error') + ->with(Mockery::pattern('/Framework data for RM_INVALID is invalid/')) + ->once(); + + $this->syncTextMock->shouldReceive('getFrameworksFromWordPress')->andReturn([]); + $this->syncTextMock->shouldReceive('getLotsFromWordPress')->andReturn([]); + + //Expectations for cleanup tasks + $this->supplierRepoMock->shouldReceive('findAll')->andReturn([]); + $this->dbManagerMock->shouldReceive('updateFrameworkTitleInWordpress'); + $this->dbManagerMock->shouldReceive('updateLotTitleInWordpress'); + $this->frameworkRepoMock->shouldReceive('printImportCount'); + $this->lotRepoMock->shouldReceive('printImportCount'); + $this->lotSupplierRepoMock->shouldReceive('printImportCount'); + + ob_start(); + try { + $this->import->importSingle([$rmNumber]); + } finally { + ob_end_clean(); + } + + $this->assertTrue(true); + } +} diff --git a/public/wp-content/plugins/ccs-mdm/includes/SyncText.php b/public/wp-content/plugins/ccs-mdm/includes/SyncText.php new file mode 100644 index 000000000..bc14db12b --- /dev/null +++ b/public/wp-content/plugins/ccs-mdm/includes/SyncText.php @@ -0,0 +1,218 @@ + Custom tables field name + * + * @var array + */ + protected $fieldsToSync = [ + 'frameworks' => [ + 'framework_type' => 'type', + 'framework_summary' => 'summary', + 'framework_updates' => 'updates', + 'framework_description' => 'description', + 'framework_benefits' => 'benefits', + 'framework_how_to_buy' => 'how_to_buy', + 'framework_documents_updates' => 'document_updates', + 'framework_keywords' => 'keywords', + 'framework_upcoming_deal_details' => 'upcoming_deal_details', + 'framework_upcoming_deal_summary' => 'upcoming_deal_summary', + 'framework_availability' => 'availability', + 'framework_cannot_use' => 'cannot_use', + ], + 'lots' => [ + 'lot_description' => 'description', + ] + ]; + + /** + * Sync data from WordPress to custom table + * + * @param string $type frameworks or lots + * @param array $wordpressData + * @param array $customTableData + * @return int $count Number of items synced from WordPress to custom table + * @throws \Exception + */ + public function syncTextContent(string $type, array $wordpressData, array $customTableData): int + { + $count = 0; + + $valid = array_keys($this->fieldsToSync); + if (!in_array($type, $valid)) { + throw new \Exception(sprintf('Invalid type: %s', $type)); + } + + $ids = array_keys($wordpressData); + foreach ($ids as $id) { + $update = []; + + foreach ($this->fieldsToSync[$type] as $wpField => $customField) { + + if (!isset($wordpressData[$id][$wpField])) { + continue; + } + + $wpData = $wordpressData[$id][$wpField]; + + if (empty($wpData)) { + continue; + } + + if (!isset($customTableData[$id])) { + continue; + } + + $customData = $customTableData[$id][$wpField]; + if ($wpData != $customData) { + $update[$customField] = $wpData; + } + } + + // Save data + if (!empty($update)) { + switch ($type) { + case 'frameworks': + $repository = new FrameworkRepository(); + $repository->updateFields($update, 'wordpress_id', $id); + break; + + case 'lots': + $repository = new LotRepository(); + $repository->updateFields($update, 'wordpress_id', $id); + break; + } + $count++; + } + } + + return $count; + } + + + /** + * Return array of all content for frameworks from WordPress + * + * @return array + */ + public function getFrameworksFromWordPress(): array + { + $data = []; + $args = [ + 'post_type' => 'framework', + 'post_status' => 'any', + 'posts_per_page' => -1 + ]; + $loop = new \WP_Query( $args ); + while ($loop->have_posts()) { + $loop->the_post(); + $id = get_the_ID(); + $title = the_title('', '', false); + $itemData = []; + + foreach ($this->fieldsToSync['frameworks'] as $wpField => $customField) { + + if($wpField === 'framework_type') { + foreach (wp_get_post_terms($id, $wpField) as $term){ + $itemData[$wpField] = $term->name; + } + } else { + $itemData[$wpField] = get_field($wpField); + } + } + $data[$id] = $itemData; + $data[$id]['framework_title'] = $title; + } + + return $data; + } + + /** + * Return array of all content for frameworks from custom tables + * + * @return array + */ + public function getFrameworksFromCustomTables(): array + { + $data = []; + $repository = new FrameworkRepository(); + $results = $repository->findAll(); + + foreach ($results as $item) { + $item = $item->toArray(); + $itemData = []; + foreach ($this->fieldsToSync['frameworks'] as $wpField => $customField) { + $itemData[$wpField] = $item[$customField]; + } + $id = $item['wordpress_id']; + $data[$id] = $itemData; + } + return $data; + } + + + /** + * @todo Return array of all content for lots from WordPress + * + * @return array + */ + public function getLotsFromWordPress(): array + { + $data = []; + $args = [ + 'post_type' => 'lot', + 'post_status' => 'any', + 'posts_per_page' => -1 + ]; + $loop = new \WP_Query( $args ); + while ($loop->have_posts()) { + $loop->the_post(); + $id = get_the_ID(); + $title = the_title('', '', false); + $itemData = []; + + foreach ($this->fieldsToSync['lots'] as $wpField => $customField) { + $itemData[$wpField] = get_post_field('post_content', $id); + } + $data[$id] = $itemData; + $data[$id]['lot_title'] = $title; + } + + return $data; + } + + /** + * Return array of all content for lots from custom tables + * + * @return array + */ + public function getLotsFromCustomTables(): array + { + $data = []; + $repository = new LotRepository(); + $results = $repository->findAll(); + + foreach ($results as $item) { + $item = $item->toArray(); + $itemData = []; + foreach ($this->fieldsToSync['lots'] as $wpField => $customField) { + $itemData[$wpField] = $item[$customField]; + } + $id = $item['wordpress_id']; + $data[$id] = $itemData; + } + return $data; + } + + +} diff --git a/public/wp-content/plugins/ccs-mdm/includes/cli-commands.php b/public/wp-content/plugins/ccs-mdm/includes/cli-commands.php new file mode 100644 index 000000000..21a889457 --- /dev/null +++ b/public/wp-content/plugins/ccs-mdm/includes/cli-commands.php @@ -0,0 +1,497 @@ + 0, 'lots' => 0, 'suppliers' => 0]; + private array $errorCount = ['frameworks' => 0, 'lots' => 0, 'suppliers' => 0]; + + private MdmApi $mdmApi; + private dbManager $dbManager; + private SyncText $syncText; + private FrameworkRepository $frameworkRepository; + private LotRepository $lotRepository; + private SupplierRepository $supplierRepository; + private LotSupplierRepository $lotSupplierRepository; + private FrameworkSearchClient $frameworkSearchClient; + private SupplierSearchClient $supplierSearchClient; + private array $wordpressFrameworks = []; + private array $wordpressLots = []; + + public function __construct() + { + $this->initializeResources(); + } + + /** + * Logic separated so it can be called explicitly if __construct is bypassed + */ + private function initializeResources(): void + { + if (isset($this->lockFactory)) { + return; + } + + // Initialise lock + $store = new FlockStore(sys_get_temp_dir()); + $this->lockFactory = new LockFactory($store); + + // Initialise logger + $this->logger = new ImportLogger(); + + // Initialise resources + $this->mdmApi = new MdmApi(); + $this->syncText = new SyncText(); + $this->frameworkRepository = new FrameworkRepository(); + $this->dbManager = new dbManager(new DatabaseConnection()); + $this->lotRepository = new LotRepository(); + $this->supplierRepository = new SupplierRepository(); + $this->lotSupplierRepository = new LotSupplierRepository(); + $this->frameworkSearchClient = new FrameworkSearchClient(); + $this->supplierSearchClient = new SupplierSearchClient(); + } + + public function importAll(): void { + + $this->initializeResources(); // Safety check for tests + $start_time = microtime(true); + + $lock = $this->lockFactory->createLock('ccs-mdm-import-all'); + + if (!$lock->acquire()) { + $this->addErrorAndExit('Lock file is currently in use by another process, quitting script'); + } + + try { + $this->wordpressFrameworks = $this->syncText->getFrameworksFromWordPress(); + $this->wordpressLots = $this->syncText->getLotsFromWordPress(); + } catch (\Exception $e) { + $this->addErrorAndExit("Process cannot complete without WordPress data. Error: {$e->getMessage()}"); + } + + WP_CLI::line("Starting import all frameworks"); + + try { + $frameworkRmNumbers = $this->mdmApi->getAgreementsRmNumbers(); + } catch (\Exception $e) { + $this->addErrorAndExit("Failed to retrieve frameworks from MDM: " . $e->getMessage()); + } + + $this->addSuccess(count($frameworkRmNumbers) . " frameworks returned from MDM.", null, true); + + $importCounter = 0; + + foreach ($frameworkRmNumbers as $rmNumber) { + $this->single($rmNumber); + $importCounter++; + + if ($importCounter % 25 === 0) { + WP_CLI::line("{$importCounter} frameworks imported so far"); + } + } + + $this->addSuccess(sprintf('Import took %s seconds to run', round(microtime(true) - $start_time, 2)), null, true); + + $this->printSummary(); + + WP_CLI::line("Starting post-import tasks..."); + + $this->postImportTask(); + + $this->addSuccess(sprintf('Import + post-import tasks took %s seconds to run', round(microtime(true) - $start_time, 2)), null, true); + + $this->addSuccess("Whole import process completed.", null, true); + + } + + public function importSingle(array $args, array $assoc_args = []): void { + $this->initializeResources(); // Safety check for tests + $start_time = microtime(true); + + $lock = $this->lockFactory->createLock('ccs-mdm-import-single'); + + if (!$lock->acquire()) { + $this->addErrorAndExit('Lock file is currently in use by another process, quitting script'); + } + + $rmNumber = $args[0] ?? null; + if (empty($rmNumber)) { + WP_CLI::error('RM number is required'); + return; + } + + WP_CLI::line("Starting single import for RM number: $rmNumber"); + + try { + $this->wordpressFrameworks = $this->syncText->getFrameworksFromWordPress(); + $this->wordpressLots = $this->syncText->getLotsFromWordPress(); + } catch (\Exception $e) { + $this->addErrorAndExit("Process cannot complete without WordPress data. Error: {$e->getMessage()}"); + } + + $this->single($rmNumber); + + WP_CLI::success(sprintf('Import took %s seconds to run', round(microtime(true) - $start_time, 2))); + + $this->printSummary(); + + WP_CLI::line("Starting post-import tasks..."); + + $this->postImportTask(isset($assoc_args['skip'])); + + $lock->release(); + + WP_CLI::success("Import completed for $rmNumber."); + } + + + private function single(string $rmNumber): void + { + try { + $framework = $this->mdmApi->getAgreement($rmNumber); + + if (!$framework?->getSalesforceId()) { + $this->addError("Framework data for $rmNumber is invalid or missing Salesforce ID."); + return; + } + + $this->frameworkRepository->createOrUpdateExcludingWordpressFields('salesforce_id', $framework->getSalesforceId(), $framework); + $framework = $this->frameworkRepository->findById($framework->getSalesforceId(), 'salesforce_id'); + $this->ensureWordPressPostExists($framework, 'framework'); + + $lots = $this->mdmApi->getAgreementLots($framework->getSalesforceId()); + $this->checkAndDeleteLots($lots, $framework); + + foreach ($lots as $lot) { + //we are skipping non-live frameworks and non-live lots + if ($framework->getStatus() != "Live" || $lot->getStatus() != "Live") { + return; + } + + $lot->setWordpressId($this->dbManager->getLotWordpressIdBySalesforceId($lot->getSalesforceId())); + $this->lotRepository->createOrUpdateExcludingWordpressFields('salesforce_id', $lot->getSalesforceId(), $lot); + $lot = $this->lotRepository->findById($lot->getSalesforceId(), 'salesforce_id'); + $this->ensureWordPressPostExists($lot, 'lot'); + + if ($lot->isHideSuppliers()) { + $this->lotSupplierRepository->deleteById($lot->getSalesforceId(), 'lot_id'); + continue; + } + + $suppliers = $this->mdmApi->getLotSuppliers($lot->getSalesforceId()); + $this->checkAndDeleteSuppliers($suppliers, $lot->getSalesforceId()); + + foreach ($suppliers as $supplier) { + if (empty($supplier->getSalesforceId())) { + $this->addError("Missing Salesforce ID for supplier on lot {$lot->getSalesforceId()}", 'suppliers'); + continue; + } + + $this->supplierRepository->createOrUpdateExcludingLiveFrameworkField('salesforce_id', $supplier->getSalesforceId(), $supplier); + + $lotSupplier = $this->lotSupplierRepository->findByLotIdAndSupplierId($lot->getSalesforceId(), $supplier->getSalesforceId()); + + if (!$lotSupplier) { + $this->lotSupplierRepository->create(new LotSupplier([ + 'lot_id' => $lot->getSalesforceId(), + 'supplier_id' => $supplier->getSalesforceId(), + 'contact_name' => $supplier->getContactName(), + 'contact_email' => $supplier->getContactEmail(), + 'trading_name' => $supplier->getTradingName(), + ])); + } else { + $lotSupplier->setContactName($supplier->getContactName()) + ->setContactEmail($supplier->getContactEmail()) + ->setTradingName($supplier->getTradingName()); + + $this->lotSupplierRepository->update('id', $lotSupplier->getId(), $lotSupplier); + } + } + } + } catch (\Exception $e) { + $this->addError("Something went wrong while importing $rmNumber: " . $e->getMessage()); + return; + } + } + + /** + * Helper to handle WordPress post creation for both Frameworks and Lots. + */ + private function ensureWordPressPostExists(object $entity, string $type): void + { + if ($entity->getWordpressId()) { + return; + } + + $metaKey = $type === 'framework' ? 'framework_id' : 'lot_id'; + + $wordpressId = wp_insert_post([ + 'post_title' => $entity->getTitle() ?: "Untitled $type - " . $entity->getSalesforceId(), + 'post_type' => $type, + 'post_status' => 'draft', + ]); + + if (is_wp_error($wordpressId) || $wordpressId === 0) { + $this->addError("Failed to create WordPress post for $type: " . $entity->getSalesforceId()); + return; + } + + update_field($metaKey, $entity->getSalesforceId(), $wordpressId); + $entity->setWordpressId((string) $wordpressId); + + $repository = $type === 'framework' ? $this->frameworkRepository : $this->lotRepository; + $repository->update('salesforce_id', $entity->getSalesforceId(), $entity, true); + + WP_CLI::success("Created $type in WordPress (ID: $wordpressId)."); + } + + public function checkAndDeleteLots(array $lots, Framework $framework): void + { + $lotIdsFromAPI = array_map(fn($lot) => $lot->getSalesforceId(), $lots); + $localLotIds = (array) $this->dbManager->getLotSalesforceIdByFrameworkId($framework->getSalesforceId()); + + foreach (array_diff($localLotIds, $lotIdsFromAPI) as $lotToDelete) { + $this->logger->info("Deleting lot: $lotToDelete from {$framework->getRmNumber()}"); + $this->lotRepository->delete($lotToDelete); + $this->dbManager->deleteLotPostInWordpress($this->dbManager->getLotWordpressIdBySalesforceId($lotToDelete)); + $this->addSuccess("Lot $lotToDelete deleted."); + } + } + + protected function checkAndDeleteSuppliers(array $suppliers, string $lotId): void + { + $supplierIdsFromAPI = array_map(fn($s) => $s->getSalesforceId(), $suppliers); + $localSuppliers = (array) $this->dbManager->getLotSuppliersSalesforceIdByLotId($lotId); + + foreach (array_diff($localSuppliers, $supplierIdsFromAPI) as $supplierToDelete) { + $this->lotSupplierRepository->deleteByLotIdAndSupplierId($lotId, $supplierToDelete); + $this->addSuccess("Lot supplier $supplierToDelete deleted."); + } + } + + protected function checkAllSuppliersIfOnLiveFrameworks(): void + { + foreach ($this->supplierRepository->findAll() as $supplier) { + $count = $this->frameworkRepository->countAllSupplierLiveFrameworks($supplier->getSalesforceId()); + $this->supplierRepository->updateOnLiveField('salesforce_id', $supplier->getSalesforceId(), $count > 0); + } + } + + public function checkEventCron() { + $cron_jobs = get_option('cron'); + + $check_event_dates = false; + foreach ((array) $cron_jobs as $cron_job) { + if (array_key_exists("check_event_dates", (array) $cron_job)){ + $check_event_dates = true; + $this->addSuccess('"check_event_dates" cron job exist'); + + break; + } + } + + if ($check_event_dates == false && getenv('CCS_FRONTEND_APP_ENV') == 'prod'){ + $OpGenieLogger = new OpGenieLogger(); + + $OpGenieLogger->sendToOPGenie([ + 'priority' => 'P2', + 'message' => 'Website - Event Cron job disappear', + 'description' => 'The check_event_dates cron job has disappear, please start the "S24 Event Unpublisher" plugin on Wordpress.', + 'impactedServices' => [getenv('websiteProjectIDOnOPGenie')], + 'tags' => [strtoupper(getenv('CCS_FRONTEND_APP_ENV'))] + ]); + } + } + + public function updateFrameworkSearchIndex() + { + WP_CLI::success('Beginning Search index update on Frameworks.'); + + $frameworks = $this->frameworkRepository->findAll(); + + WP_CLI::success(count($frameworks) . ' Frameworks found'); + + $indexStatus = array('Live', 'Expired - Data Still Received', 'Future (Pipeline)', 'Planned (Pipeline)', 'Underway (Pipeline)', 'Awarded (Pipeline)'); + + foreach ($frameworks as $framework) + { + if (in_array($framework->getStatus(), $indexStatus)) { + $this->updateFrameworkSearchIndexWithSingleFramework($framework); + }else{ + $this->frameworkSearchClient->removeDocument($framework); + } + } + + WP_CLI::success('updateFrameworkSearchIndex operation completed successfully.'); + + return; + } + + protected function updateFrameworkSearchIndexWithSingleFramework(Framework $framework) { + WP_CLI::success('Updating Framework index for: ' . $framework->getRmNumber()); + + $lots = $this->lotRepository->findAllById($framework->getSalesforceId(), 'framework_id'); + + if (!$lots) { + $lots = []; + } + + $this->frameworkSearchClient->createOrUpdateDocument($framework, $lots); + } + + public function updateSupplierSearchIndex() + { + WP_CLI::success('Beginning Search index update on Suppliers.'); + + $suppliers = $this->supplierRepository->findAll(); + + WP_CLI::success(count($suppliers) . ' Suppliers found'); + + $count = 0; + + foreach ($suppliers as $supplier) { + + $liveFrameworks = $this->frameworkRepository->findSupplierLiveFrameworks($supplier->getSalesforceId()); + $dpsFrameworkCount = 0; + $totalFrameworkCount = 0; + + if (!empty($liveFrameworks)) + { + $totalFrameworkCount = count($liveFrameworks); + + foreach ($liveFrameworks as $liveFramework) + { + $lots = $this->lotRepository->findAllByFrameworkIdSupplierId($liveFramework->getSalesforceId(), $supplier->getSalesforceId()); + $liveFramework->setLots($lots); + + if ($liveFramework->getTerms() == 'DPS' || $liveFramework->getType() == 'Dynamic purchasing system' ){ + $dpsFrameworkCount++; + } + } + } + + $alternativeTradingNames = []; + $lotSuppliers = $this->lotSupplierRepository->findAllById($supplier->getSalesforceId(), 'supplier_id'); + + if (!empty($lotSuppliers)) + { + + foreach ($lotSuppliers as $lotSupplier) + { + if (!empty($lotSupplier->getTradingName())) { + $alternativeTradingNames[$lotSupplier->getTradingName()] = $lotSupplier->getTradingName(); + } + } + + if (!empty($supplier->getTradingName())) { + $alternativeTradingNames[$supplier->getTradingName()] = $supplier->getTradingName(); + } + + $supplier->setAlternativeTradingNames(array_values($alternativeTradingNames)); + } + + + if (!$liveFrameworks || $dpsFrameworkCount == $totalFrameworkCount ) { + $this->supplierSearchClient->removeDocument($supplier); + } else { + $this->supplierSearchClient->createOrUpdateDocument($supplier, $liveFrameworks); + } + + $count++; + + if ($count % 50 == 0) { + WP_CLI::success($count . ' Suppliers imported...'); + } + + } + + WP_CLI::success('updateSupplierSearchIndex operation completed successfully.'); + + return; + } + private function postImportTask(bool $skipOpenSearchIndex = false): void + { + // Check the event cron job exists + $this->checkEventCron(); + + //Mark whether a supplier has any live frameworks + $this->checkAllSuppliersIfOnLiveFrameworks(); + + //Update framework titles in WordPress to include the RM number + $this->dbManager->updateFrameworkTitleInWordpress(); + + //Update lot titles in WordPress to include the RM number and the lot number + $this->dbManager->updateLotTitleInWordpress(); + + if (!$skipOpenSearchIndex) { + // Update elasticsearch indexes + $this->updateFrameworkSearchIndex(); + $this->updateSupplierSearchIndex(); + } + } + + private function addError(string $message, ?string $type = null): void + { + $this->logger->error($message); + WP_CLI::line("Error: $message"); + if ($type) { + $this->errorCount[$type]++; + } + } + + private function addErrorAndExit(string $message): void + { + $this->logger->error($message); + WP_CLI::error($message, true); + } + + private function addSuccess(string $message, ?string $type = null, bool $log = false): void + { + if ($log) { + $this->logger->info($message); + WP_CLI::line($message); + } + if ($type) { + $this->importCount[$type]++; + } + } + + private function printSummary(): void + { + WP_CLI::line("===== Summary ====="); + $this->frameworkRepository->printImportCount(); + $this->lotRepository->printImportCount(); + $this->lotSupplierRepository->printImportCount(); + WP_CLI::line("==================="); + } +} + +// Usage: +// wp mdm-import importSingle RM526 +// wp mdm-import importSingle RM526 --skip +// wp mdm-import importAll \ No newline at end of file diff --git a/public/wp-content/plugins/ccs-mdm/includes/dbManager.php b/public/wp-content/plugins/ccs-mdm/includes/dbManager.php new file mode 100644 index 000000000..393754d52 --- /dev/null +++ b/public/wp-content/plugins/ccs-mdm/includes/dbManager.php @@ -0,0 +1,101 @@ +dbConnection = $dbConnection; + } + + public function getLotWordpressIdBySalesforceId(?string $salesforceId) { + $lotWordpressId = null; + + $sql = "SELECT wordpress_id FROM ccs_lots WHERE salesforce_id = '" . $salesforceId . "';"; + + $query = $this->dbConnection->connection->prepare($sql); + $query->execute(); + + $sqlData = $query->fetch(\PDO::FETCH_ASSOC); + + if(!empty($sqlData['wordpress_id'])) { + $lotWordpressId = (string) $sqlData['wordpress_id']; + } + + return $lotWordpressId; + } + + public function deleteLotPostInWordpress($wordpressId) { + $sql = " DELETE FROM ccs_15423_posts WHERE ID = '" . $wordpressId . "';"; + + $query = $this->dbConnection->connection->prepare($sql); + $query->execute(); + } + + public function updateFrameworkTitleInWordpress() { + $sql = <<dbConnection->connection->prepare($sql); + $response = $query->execute(); + + if (!$response) { + print_r($query->errorInfo()); + throw new \Exception('Framework title could not be updated in the database.'); + } + + } + + public function updateLotTitleInWordpress() { + $sql = <<dbConnection->connection->prepare($sql); + $response = $query->execute(); + + if (!$response) { + print_r($query->errorInfo()); + throw new \Exception('Lot title could not be updated in the database.'); + } + } + + public function getLotSalesforceIdByFrameworkId(?string $frameworkId){ + $sql = "SELECT salesforce_id FROM ccs_lots WHERE framework_id = '" . $frameworkId . "';"; + + $query = $this->dbConnection->connection->prepare($sql); + $query->execute(); + + $sqlData = $query->fetchAll(\PDO::PARAM_STR); + + return count($sqlData) == 0 ? null : array_column($sqlData, 'salesforce_id'); + } + + public function getLotSuppliersSalesforceIdByLotId(?string $lotId) { + $sql = "SELECT supplier_id FROM ccs_lot_supplier WHERE lot_id = '" . $lotId . "';"; + + $query = $this->dbConnection->connection->prepare($sql); + $query->execute(); + + $sqlData = $query->fetchAll(\PDO::PARAM_STR); + + return count($sqlData) == 0 ? null : array_column($sqlData, 'supplier_id'); + } +} \ No newline at end of file diff --git a/public/wp-content/plugins/ccs-salesforce/includes/cli-commands.php b/public/wp-content/plugins/ccs-salesforce/includes/cli-commands.php index bcb8861ca..402d3fa36 100644 --- a/public/wp-content/plugins/ccs-salesforce/includes/cli-commands.php +++ b/public/wp-content/plugins/ccs-salesforce/includes/cli-commands.php @@ -551,18 +551,18 @@ protected function importSingleFramework($framework) $lotSupplier->setTradingName($tradingName); } - if ($guarantorId = $this->salesforceApi->getLotSuppliersGuarantor($lotSalesforceId,$supplier->getSalesforceId())){ - $this->addSuccess('Framework supplier Guarantor found.'); - $lotSupplier->setGuarantorId($guarantorId); + // if ($guarantorId = $this->salesforceApi->getLotSuppliersGuarantor($lotSalesforceId,$supplier->getSalesforceId())){ + // $this->addSuccess('Framework supplier Guarantor found.'); + // $lotSupplier->setGuarantorId($guarantorId); - $guarantorSupplier = $this->salesforceApi->getSupplier($lotSupplier->getGuarantorId()); + // $guarantorSupplier = $this->salesforceApi->getSupplier($lotSupplier->getGuarantorId()); - if (!$this->supplierRepository->createOrUpdateExcludingLiveFrameworkField('salesforce_id', $guarantorSupplier->getSalesforceId(), $guarantorSupplier)) { - $this->addError('Guarantor Supplier ' . $guarantorSupplier->getSalesforceId() . ' not imported. An error occurred running the createOrUpdateExcludingLiveFrameworkField method', 'suppliers'); - }else{ - $this->addSuccess('Guarantor Supplier imported.'); - } - } + // if (!$this->supplierRepository->createOrUpdateExcludingLiveFrameworkField('salesforce_id', $guarantorSupplier->getSalesforceId(), $guarantorSupplier)) { + // $this->addError('Guarantor Supplier ' . $guarantorSupplier->getSalesforceId() . ' not imported. An error occurred running the createOrUpdateExcludingLiveFrameworkField method', 'suppliers'); + // }else{ + // $this->addSuccess('Guarantor Supplier imported.'); + // } + // } $this->addSuccess('Searching for contact details for Lot: ' . $lotSupplier->getLotId() . ' and Supplier: ' . $lotSupplier->getSupplierId()); @@ -762,7 +762,7 @@ protected function createFrameworkInWordpress($framework, array $wordpressFramew WP_CLI::success('Created Framework in Wordpress.'); //Update the Framework model with the new Wordpress ID - $framework->setWordpressId($wordpressId); + $framework->setWordpressId((string) $wordpressId); // Save the Framework back into the custom database. $this->frameworkRepository->update('salesforce_id', $framework->getSalesforceId(), $framework, true); @@ -785,7 +785,7 @@ protected function createLotInWordpress($lot, array $wordpressLots) WP_CLI::success('Created Lot in Wordpress.'); //Update the Lot model with the new Wordpress ID - $lot->setWordpressId($wordpressId); + $lot->setWordpressId((string) $wordpressId); // Save the Lot back into the custom database. $this->lotRepository->update('salesforce_id', $lot->getSalesforceId(), $lot, true); @@ -1089,7 +1089,7 @@ protected function getLotWordpressIdBySalesforceId(?string $salesforceId) $sqlData = $query->fetch(\PDO::FETCH_ASSOC); if(!empty($sqlData['wordpress_id'])) { - $lotWordpressId = $sqlData['wordpress_id']; + $lotWordpressId = (string) $sqlData['wordpress_id']; } return $lotWordpressId; diff --git a/public/wp-content/plugins/ccs-salesforce/includes/wp-rest-api/CustomLotApi.php b/public/wp-content/plugins/ccs-salesforce/includes/wp-rest-api/CustomLotApi.php index 8e8a48600..7bc349545 100644 --- a/public/wp-content/plugins/ccs-salesforce/includes/wp-rest-api/CustomLotApi.php +++ b/public/wp-content/plugins/ccs-salesforce/includes/wp-rest-api/CustomLotApi.php @@ -96,6 +96,7 @@ public function get_lot_suppliers(WP_REST_Request $request) 'supplier_website' => $supplier->getWebsite(), 'supplier_crp_url' => $supplier->getCrpUrl(), 'supplier_contact_name' => $supplier->getContactName(), + 'supplier_trading_name' => $supplier->getTradingName(), 'supplier_contact_email' => $supplier->getContactEmail(), 'supplier_have_guarantor' => $supplier->getHaveGuarantor(), 'live_frameworks' => $liveFrameworks diff --git a/src/App/Config/Mappings/MDM_agreement.yaml b/src/App/Config/Mappings/MDM_agreement.yaml new file mode 100644 index 000000000..4e6aa3537 --- /dev/null +++ b/src/App/Config/Mappings/MDM_agreement.yaml @@ -0,0 +1,20 @@ + + +rmNumber: FrameworkNumber +salesforceId: SalesforceID +title: FrameworkName +terms: FrameworkTerms +pillar: CategoryGroup +category: Category +status: Status +regulation: RegulationUsed +regulationType: AgreementType +startDate: FrameworkStartDate +endDate: FrameworkEndDate +tendersOpenDate: TendersOpenDate +tendersCloseDate: TenderClosingDate +expectedLiveDate: FrameworkLiveTargetDate +expectedAwardDate: AwardTargetDate +publishOnWebsite: DontPublishFrameworkOnWebsite +createDraftPage: CreateDraftWebPage +policyCompliance: tempPolicyCompliance diff --git a/src/App/Config/Mappings/MDM_lot.yaml b/src/App/Config/Mappings/MDM_lot.yaml new file mode 100644 index 000000000..a3dcf9952 --- /dev/null +++ b/src/App/Config/Mappings/MDM_lot.yaml @@ -0,0 +1,8 @@ +salesforceId: SalesforceID +frameworkId: FrameworkSalesforceID +lotNumber: LotNumber +title: Title +status: Status +expiryDate: ExpiryDate +hideSuppliers: HideLotSuppliersFromWebsite +hideLot: HideThisLotFromWebsite \ No newline at end of file diff --git a/src/App/Config/Mappings/MDM_supplier.yaml b/src/App/Config/Mappings/MDM_supplier.yaml new file mode 100644 index 000000000..ea62f17a3 --- /dev/null +++ b/src/App/Config/Mappings/MDM_supplier.yaml @@ -0,0 +1,15 @@ +salesforceId: SupplierSalesforceID +dunsNumber: DUNS +name: SupplierName +phoneNumber: ContactPhone +street: Street +city: City +postcode: PostCode +country: Country +website: Website +tradingName: TradingName +crpUrl: crpURL +crpStatus: crpStatus +contactName: ContactName +contactEmail: ContactEmail +websiteContact: WebsiteContact diff --git a/src/App/Config/Mappings/Supplier.yaml b/src/App/Config/Mappings/Supplier.yaml index ad2906ad2..1c43cad44 100644 --- a/src/App/Config/Mappings/Supplier.yaml +++ b/src/App/Config/Mappings/Supplier.yaml @@ -11,5 +11,4 @@ properties: website: Website tradingName: Trading_Name__c crpUrl: Supplier_Carbon_Reduction_Plan__c - crpStatus: CRP_Assesment_Status__c - lastModifiedDate: LastModifiedDate \ No newline at end of file + crpStatus: CRP_Assesment_Status__c \ No newline at end of file diff --git a/src/App/Model/Framework.php b/src/App/Model/Framework.php index 30775afeb..c83440a5b 100644 --- a/src/App/Model/Framework.php +++ b/src/App/Model/Framework.php @@ -3,6 +3,8 @@ namespace App\Model; use App\Traits\SalesforceMappingTrait; +use App\Utils\YamlLoader; +use Datetime; class Framework extends AbstractModel { @@ -535,7 +537,8 @@ public function getStartDate(): ?\DateTime if (!$this->startDate) { return null; } - return $this->startDate; + + return is_string($this->startDate) ? new DateTime($this->startDate) : $this->startDate; } /** @@ -568,7 +571,8 @@ public function getEndDate(): ?\DateTime if (!$this->endDate) { return null; } - return $this->endDate; + + return is_string($this->endDate) ? new DateTime($this->endDate) : $this->endDate; } /** @@ -601,7 +605,8 @@ public function getTendersOpenDate(): ?\DateTime if (!$this->tendersOpenDate) { return null; } - return $this->tendersOpenDate; + + return is_string($this->tendersOpenDate) ? new DateTime($this->tendersOpenDate) : $this->tendersOpenDate; } /** @@ -634,7 +639,8 @@ public function getTendersCloseDate(): ?\DateTime if (!$this->tendersCloseDate) { return null; } - return $this->tendersCloseDate; + + return is_string($this->tendersCloseDate) ? new DateTime($this->tendersCloseDate) : $this->tendersCloseDate; } /** @@ -667,7 +673,8 @@ public function getExpectedLiveDate(): ?\DateTime if (!$this->expectedLiveDate) { return null; } - return $this->expectedLiveDate; + + return is_string($this->expectedLiveDate) ? new DateTime($this->expectedLiveDate) : $this->expectedLiveDate; } /** @@ -699,7 +706,8 @@ public function getExpectedAwardDate(): ?\DateTime if (!$this->expectedAwardDate) { return null; } - return $this->expectedAwardDate; + + return is_string($this->expectedAwardDate) ? new DateTime($this->expectedAwardDate) : $this->expectedAwardDate; } /** @@ -938,4 +946,15 @@ public function toArray() 'upcoming_deal_summary' => $this->getUpcomingDealSummary(), ]; } + + public function setData($data) + { + $mappings = YamlLoader::loadMappings('MDM_agreement'); + + foreach ($mappings as $property => $apiField) { + if (array_key_exists($apiField, $data) && property_exists($this, $property)) { + $this->$property = $data[$apiField]; + } + } + } } diff --git a/src/App/Model/Lot.php b/src/App/Model/Lot.php index 87178db96..649ca1620 100644 --- a/src/App/Model/Lot.php +++ b/src/App/Model/Lot.php @@ -2,6 +2,8 @@ namespace App\Model; +use App\Utils\YamlLoader; +use Datetime; use App\Traits\SalesforceMappingTrait; class Lot extends AbstractModel @@ -49,6 +51,8 @@ class Lot extends AbstractModel */ protected $hideSuppliers = false; + protected $hideLot = false; + /** * @return string */ @@ -201,7 +205,8 @@ public function getExpiryDate(): ?\DateTime if (!$this->expiryDate) { return null; } - return $this->expiryDate; + + return is_string($this->expiryDate) ? new DateTime($this->expiryDate) : $this->expiryDate; } /** @@ -238,6 +243,22 @@ public function setHideSuppliers(bool $hideSuppliers): void $this->hideSuppliers = $hideSuppliers; } + /** + * @return bool + */ + public function isHideLot(): bool + { + return $this->hideLot; + } + + /** + * @param bool $hideLot + */ + public function setHideLot(bool $hideLot): void + { + $this->hideLot = $hideLot; + } + /** * Returns a simple text array representing the object * @@ -258,4 +279,15 @@ public function toArray() 'suppliers' => null, ]; } + + public function setData($data) + { + $mappings = YamlLoader::loadMappings('MDM_lot'); + + foreach ($mappings as $property => $apiField) { + if (array_key_exists($apiField, $data) && property_exists($this, $property)) { + $this->$property = $data[$apiField]; + } + } + } } diff --git a/src/App/Model/Supplier.php b/src/App/Model/Supplier.php index 3414da59f..c1a37982f 100644 --- a/src/App/Model/Supplier.php +++ b/src/App/Model/Supplier.php @@ -3,6 +3,7 @@ namespace App\Model; use App\Traits\SalesforceMappingTrait; +use App\Utils\YamlLoader; class Supplier extends AbstractModel { @@ -81,10 +82,6 @@ class Supplier extends AbstractModel */ protected $haveGuarantor = false; - - protected $lastModifiedDate; - - /** * @return string */ @@ -408,17 +405,6 @@ public function setAlternativeTradingNames(array $alternativeTradingNames): Supp return $this; } - - public function getLastModifiedDate() - { - return $this->lastModifiedDate; - } - - public function setLastModifiedDate($lastModifiedDate) - { - $this->lastModifiedDate = $lastModifiedDate; - } - /** * Returns a simple text array representing the object * @@ -439,7 +425,17 @@ public function toArray() 'website' => $this->getWebsite(), 'crp_url' => $this->getCrpUrl(), 'trading_name' => $this->getTradingName(), - ]; } + + public function setData($data) + { + $mappings = YamlLoader::loadMappings('MDM_supplier'); + + foreach ($mappings as $property => $apiField) { + if (array_key_exists($apiField, $data) && property_exists($this, $property)) { + $this->$property = $data[$apiField]; + } + } + } } diff --git a/src/App/Repository/AbstractRepository.php b/src/App/Repository/AbstractRepository.php index 8401c3310..8c498fbbd 100644 --- a/src/App/Repository/AbstractRepository.php +++ b/src/App/Repository/AbstractRepository.php @@ -176,7 +176,7 @@ public function findSingleRow($sql = null) * * @param string $fieldName * @param $id - * @return bool + * @return \App\Model\Framework|\App\Model\Lot|\App\Model\Supplier|bool */ public function findById($id, $fieldName = 'id') { @@ -203,7 +203,7 @@ public function findById($id, $fieldName = 'id') * * @param string $fieldName * @param $id - * @return bool + * @return mixed */ public function findAllById($id, $fieldName = 'id') { diff --git a/src/App/Repository/LotSupplierRepository.php b/src/App/Repository/LotSupplierRepository.php index 1da264575..226e208f6 100644 --- a/src/App/Repository/LotSupplierRepository.php +++ b/src/App/Repository/LotSupplierRepository.php @@ -172,7 +172,7 @@ protected function bindValues($databaseBindings, $query, LotSupplier $lotSupplie * * @param $lotId * @param $supplierId - * @return bool + * @return bool|LotSupplier */ public function findByLotIdAndSupplierId($lotId, $supplierId) { diff --git a/src/App/Repository/SupplierRepository.php b/src/App/Repository/SupplierRepository.php index 67cc3d160..f195e4ea9 100644 --- a/src/App/Repository/SupplierRepository.php +++ b/src/App/Repository/SupplierRepository.php @@ -115,6 +115,18 @@ public function update($searchField, $searchValue, Supplier $supplier) return $query->execute(); } + public function updateOnLiveField($searchField, $searchValue, bool $onLive) + { + + $sql = 'UPDATE ' . $this->tableName . ' SET `on_live_frameworks` = :on_live_frameworks WHERE ' . $searchField . ' = :searchValue'; + + $query = $this->connection->prepare($sql); + $query->bindParam(':searchValue', $searchValue, \PDO::PARAM_STR); + $query->bindParam(':on_live_frameworks', $onLive, \PDO::PARAM_BOOL); + + return $query->execute(); + } + /** * Bind PDO Values @@ -178,7 +190,7 @@ protected function bindValues($databaseBindings, $query, Supplier $supplier) if (isset($databaseBindings['on_live_frameworks'])) { $onLiveFrameworks = $supplier->isOnLiveFrameworks(); - $query->bindParam(':on_live_frameworks', $onLiveFrameworks, \PDO::PARAM_STR); + $query->bindParam(':on_live_frameworks', $onLiveFrameworks, \PDO::PARAM_BOOL); } if (isset($databaseBindings['crp_url'])) { diff --git a/src/App/Services/MDM/MdmApi.php b/src/App/Services/MDM/MdmApi.php new file mode 100644 index 000000000..773993dc1 --- /dev/null +++ b/src/App/Services/MDM/MdmApi.php @@ -0,0 +1,120 @@ +apiKey = getenv('MDM_API_Key'); + $this->baseURL = getenv('MDM_API_URL'); + $this->client = new \GuzzleHttp\Client([]); + } + + public function getAgreementsRmNumbers() + { + $filter = "CreateDraftWebPage eq '1' and ( + status eq 'Live' or + status eq 'Expired - Data Still Received' or + status eq 'Future (Pipeline)' or + status eq 'Planned (Pipeline)' or + status eq 'Underway (Pipeline)' or + status eq 'Awarded (Pipeline)' + )"; + $response = $this->requestResource('[vw_Framework]', ['filter' => $filter]); + + $rmNumbers = []; + foreach ($response as $apiRecord) { + $rmNumbers[] = $apiRecord["FrameworkNumber"]; + } + + return $rmNumbers; + } + + public function getAgreement(string $agreementNumber) + { + + $filter = "FrameworkNumber eq '$agreementNumber'"; + $response = $this->requestResource('[vw_Framework]', ['filter' => $filter]); + + $framework = new Framework(); + $framework->setData($response[0]); + + return $framework; + } + + public function getAgreementLots(string $salesforceAgreementId) + { + $filter = "FrameworkSalesforceID eq '$salesforceAgreementId'"; + $response = $this->requestResource('[vw_FrameworkLots]', ['filter' => $filter]); + + $lots = []; + + foreach ($response as $apiRecord) { + $lot = new Lot(); + $lot->setData($apiRecord); + $lots[] = $lot; + } + + return $lots; + } + + public function getLotSuppliers(string $salesforceLotId) + { + $filter = "FrameworkLotSalesforceID eq '$salesforceLotId'"; + $response = $this->requestResource('[vw_FrameworkLotSupplierContacts]', ['filter' => $filter]); + + $suppliers = []; + + foreach ($response as $apiRecord) { + $supplier = new Supplier(); + $supplier->setData($apiRecord); + $suppliers[] = $supplier; + } + + return $suppliers; + } + + private function requestResource(string $resourcePath, array $extraQuery = []) + { + $defaultQuery = [ + 'api-version' => '2016-10-01', + 'sp' => '/triggers/manual/run', + 'sv' => '1.0', + 'sig' => $this->apiKey, + ]; + + $resourcePath = trim($resourcePath, '/') . '/'; + $query = array_merge($defaultQuery, $extraQuery); + $url = rtrim($this->baseURL, '/') . $resourcePath . '?' . http_build_query($query); + + try { + $response = $this->client->request('GET', $url); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $resp = $e->getResponse(); + if ($resp && $resp->getStatusCode() === 400) { + return []; + } + throw $e; + } + + + if ($response->getStatusCode() != 200) { + throw new \Exception('Response has no content. Response status code: ' . $response->getStatusCode() . ' Response Error Message: ' . $response->getReasonPhrase()); + } + + $contents = $response->getBody()->getContents(); + + return json_decode($contents, true); + } +}