diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b46b49c..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Jamf Plugin CI -on: - push: - branches: - - "main" - tags: - - 'v*' - pull_request: - schedule: - - cron: "0 0 * * *" - workflow_dispatch: -jobs: - ci: - name: "GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}" - strategy: - fail-fast: false - matrix: - include: - - { glpi-version: "10.0.x", php-version: "7.4", db-image: "mariadb:10.5" } - - { glpi-version: "10.0.x", php-version: "8.1", db-image: "mariadb:10.5" } - - { glpi-version: "10.0.x", php-version: "8.2", db-image: "mariadb:11.0" } - uses: "glpi-project/plugin-ci-workflows/.github/workflows/continuous-integration.yml@v1" - with: - plugin-key: "jamf" - glpi-version: "${{ matrix.glpi-version }}" - php-version: "${{ matrix.php-version }}" - db-image: "${{ matrix.db-image }}" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 2798b56..a5d02c9 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -20,7 +20,7 @@ jobs: name: "Generate CI matrix" uses: "glpi-project/plugin-ci-workflows/.github/workflows/generate-ci-matrix.yml@v1" with: - glpi-version: "10.0.x" + glpi-version: "11.0.x" ci: name: "GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}" needs: "generate-ci-matrix" diff --git a/.gitignore b/.gitignore index 58be7c7..4993913 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vendor .phpunit.result.cache +var/ diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 7a81c5b..ddf4eba 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -11,11 +11,11 @@ $config = new Config(); $rules = [ - '@PER-CS2.0' => true, - 'trailing_comma_in_multiline' => ['elements' => ['arguments', 'array_destructuring', 'arrays']], // For PHP 7.4 compatibility + '@PER-CS' => true, // Latest PER rules. ]; return $config ->setRules($rules) ->setFinder($finder) - ->setUsingCache(false); + ->setCacheFile(__DIR__ . '/var/php-cs-fixer/.php-cs-fixer.cache') +; diff --git a/.twig_cs.dist.php b/.twig_cs.dist.php index 352fe0a..6041bcf 100644 --- a/.twig_cs.dist.php +++ b/.twig_cs.dist.php @@ -2,14 +2,16 @@ declare(strict_types=1); -use FriendsOfTwig\Twigcs; +use FriendsOfTwig\Twigcs\Finder\TemplateFinder; +use FriendsOfTwig\Twigcs\Config\Config; +use Glpi\Tools\GlpiTwigRuleset; -$finder = Twigcs\Finder\TemplateFinder::create() +$finder = TemplateFinder::create() ->in(__DIR__ . '/templates') ->name('*.html.twig') ->ignoreVCSIgnored(true); -return Twigcs\Config\Config::create() +return Config::create() ->setFinder($finder) - ->setRuleSet(\Glpi\Tools\GlpiTwigRuleset::class) + ->setRuleSet(GlpiTwigRuleset::class) ; diff --git a/CHANGELOG.md b/CHANGELOG.md index 714326e..bb93161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [UNRELEASED] +- GLPI 11 compatibility + ### Fixes - SQL error when merging the Jamf device linked to a GLPI asset diff --git a/RoboFile.php b/RoboFile.php deleted file mode 100644 index 09964f4..0000000 --- a/RoboFile.php +++ /dev/null @@ -1,5 +0,0 @@ -. * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; + +use function Safe\file_get_contents; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Html::header_nocache(); Session::checkLoginUser(); +/** @var DBmysql $DB */ global $DB; // Get AJAX input and load it into $_REQUEST @@ -55,4 +58,5 @@ if (!in_array($_REQUEST['crontask'], $accepted_tasks)) { throw new RuntimeException('Unacceptable cron task!'); } + CronTask::launch(-CronTask::MODE_EXTERNAL, 1, $_REQUEST['crontask']); diff --git a/ajax/getMDMCommandForm.php b/ajax/getMDMCommandForm.php index 86504ab..9ab2c99 100644 --- a/ajax/getMDMCommandForm.php +++ b/ajax/getMDMCommandForm.php @@ -22,18 +22,18 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Session::checkLoginUser(); @@ -45,6 +45,10 @@ if (isset($_GET['itemtype'], $_GET['items_id'])) { $className = 'PluginJamf' . $_GET['itemtype']; + if (is_a($className, 'CommonDBTM', true) === false) { + throw new RuntimeException('Invalid itemtype!'); + } + $device = new $className(); if (!$device->getFromDB($_GET['items_id'])) { throw new RuntimeException('Invalid itemtype/items_id!'); @@ -52,5 +56,6 @@ } else { $device = null; } + $form = PluginJamfMDMCommand::getFormForCommand($_GET['command'], $device); echo $form; diff --git a/ajax/import.php b/ajax/import.php index 8173f0e..5dce5ad 100644 --- a/ajax/import.php +++ b/ajax/import.php @@ -22,24 +22,27 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; + +use function Safe\file_get_contents; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Html::header_nocache(); Session::checkLoginUser(); +/** @var DBmysql $DB */ global $DB; // Get AJAX input and load it into $_REQUEST diff --git a/ajax/merge.php b/ajax/merge.php index a465874..dcb8c16 100644 --- a/ajax/merge.php +++ b/ajax/merge.php @@ -22,24 +22,27 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; + +use function Safe\file_get_contents; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Html::header_nocache(); Session::checkLoginUser(); +/** @var DBmysql $DB */ global $DB; // Get AJAX input and load it into $_REQUEST @@ -50,6 +53,7 @@ if (!isset($_REQUEST['action'])) { throw new RuntimeException('Required argument missing!'); } + if ($_REQUEST['action'] === 'merge') { // Trigger extension attribute definition sync PluginJamfMobileSync::syncExtensionAttributeDefinitions(); @@ -62,6 +66,7 @@ if (!isset($data['jamf_id'], $data['itemtype'])) { continue; } + $jamf_id = $data['jamf_id']; $itemtype = $data['itemtype']; @@ -69,10 +74,11 @@ // Invalid itemtype for a mobile device throw new RuntimeException('Invalid itemtype!'); } + $item = new $itemtype(); - /** @var PluginJamfAbstractDevice $plugin_itemtype */ + /** @var class-string $plugin_itemtype */ $plugin_itemtype = 'PluginJamf' . $data['jamf_type']; - /** @var PluginJamfDeviceSync $plugin_sync_itemtype */ + /** @var class-string $plugin_sync_itemtype */ $plugin_sync_itemtype = 'PluginJamf' . $data['jamf_type'] . 'Sync'; if ($data['jamf_type'] === 'MobileDevice') { $plugin_sync_itemtype = 'PluginJamfMobileSync'; @@ -102,7 +108,7 @@ 'supervised' => $jamf_item['supervised'] ?? $os_details['supervised'], ]; $ruleinput = $rules->processAllRules($ruleinput, $ruleinput, ['recursive' => true]); - $import = isset($ruleinput['_import']) ? $ruleinput['_import'] : 'NS'; + $import = $ruleinput['_import'] ?? 'NS'; if (isset($ruleinput['_import']) && !$ruleinput['_import']) { // Dropped by rules @@ -120,9 +126,15 @@ 'jamf_items_id' => $data['jamf_id'], ]); if ($r === false) { - throw new \RuntimeException('Failed to import the device data!'); + throw new RuntimeException('Failed to import the device data!'); } + // Link + + if (is_a($plugin_itemtype, 'CommonDBTM', true) === false) { + throw new RuntimeException('Invalid plugin itemtype!'); + } + $plugin_item = new $plugin_itemtype(); $plugin_items_id = $plugin_item->add([ 'glpi_plugin_jamf_devices_id' => $DB->insertId(), @@ -155,8 +167,9 @@ $DB->rollBack(); } } - if ($failures) { - Session::addMessageAfterRedirect(sprintf(__('An error occurred while merging %d devices!', 'jamf'), $failures), false, ERROR); + + if ($failures !== 0) { + Session::addMessageAfterRedirect(sprintf(__s('An error occurred while merging %d devices!', 'jamf'), $failures), false, ERROR); } } else { throw new RuntimeException('Required argument missing!'); diff --git a/ajax/sendMDMCommand.php b/ajax/sendMDMCommand.php index 7a2952d..81b9d82 100644 --- a/ajax/sendMDMCommand.php +++ b/ajax/sendMDMCommand.php @@ -22,18 +22,19 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\BadRequestHttpException; +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Html::header_nocache(); @@ -61,19 +62,18 @@ $valid_types = ['MobileDevice']; if (!in_array($_POST['itemtype'], $valid_types, true)) { - die('Invalid itemtype. Cannot send Jamf MDM command.'); + throw new BadRequestHttpException(); } $items = []; -if ($_POST['itemtype'] === 'MobileDevice') { - foreach ($_POST['items_id'] as $items_id) { - /** @var PluginJamfMobileDevice $item */ - $item = new PluginJamfMobileDevice(); - $item->getFromDB((int) $items_id); - $items[] = $item; - } +foreach ($_POST['items_id'] as $items_id) { + /** @var PluginJamfMobileDevice $item */ + $item = new PluginJamfMobileDevice(); + $item->getFromDB((int) $items_id); + $items[] = $item; } + foreach ($items as $k => $item) { $commands = PluginJamfItem_MDMCommand::getApplicableCommands($item); $command_names = array_keys($commands); @@ -83,9 +83,9 @@ } } -if (!count($items)) { +if ($items === []) { // No applicable items or no right to send command - exit(); + return; } // The API endpoint for sending MDM commands only accepts XML, so we need to build the XML payload @@ -101,4 +101,5 @@ $m = $mobile_devices->addChild('mobile_device'); $m->addChild('id', $jamf_id); } + echo PluginJamfAPI::sendMDMCommand($payload->asXML(), true); diff --git a/ajax/sync.php b/ajax/sync.php index bcaae43..bb087c3 100644 --- a/ajax/sync.php +++ b/ajax/sync.php @@ -22,24 +22,27 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; + +use function Safe\file_get_contents; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Html::header_nocache(); Session::checkLoginUser(); +/** @var DBmysql $DB */ global $DB; // Get AJAX input and load it into $_REQUEST @@ -56,9 +59,9 @@ if ($jamf_class !== null) { $sync_class = PluginJamfSync::getDeviceSyncEngines()[$jamf_class] ?? null; if ($sync_class !== null) { - $sync_class::sync($_REQUEST['itemtype'], $_REQUEST['items_id']); + $sync_class::sync($_REQUEST['itemtype'], (int) $_REQUEST['items_id']); } } -} catch (Exception $e) { - trigger_error($e->getMessage(), E_USER_ERROR); +} catch (Exception $exception) { + trigger_error($exception->getMessage(), E_USER_ERROR); } diff --git a/composer.json b/composer.json index d045a16..1d4373a 100644 --- a/composer.json +++ b/composer.json @@ -1,28 +1,22 @@ { "require": { - "php": "~7.4.0|~8.0.0|~8.1.0|~8.2.0" + "php": ">=8.2" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.68", - "friendsoftwig/twigcs": "^6.1", - "glpi-project/tools": "^0.7.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "~9.6" + "glpi-project/tools": "^0.8" + }, "config": { "optimize-autoloader": true, "platform": { - "php": "7.4" + "php": "8.2.99" }, - "sort-packages": true, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": false, - "phpstan/extension-installer": true - } + "sort-packages": true }, - "scripts": { - "test": "phpunit -c phpunit.xml", - "phpstan": "phpstan analyse -c phpstan.neon" + "autoload": { + "psr-4": { + "GlpiPlugin\\Jamf\\Tests\\": "tests/" + } }, "autoload-dev": { "psr-4": { diff --git a/composer.lock b/composer.lock index 07689dd..5a090aa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,182 +4,91 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9253c137613e9ecd0ffd391042748ccb", + "content-hash": "0bb7734a8837cdd118f20d259d3613e7", "packages": [], "packages-dev": [ { - "name": "clue/ndjson-react", - "version": "v1.3.0", + "name": "glpi-project/tools", + "version": "0.8.3", "source": { "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + "url": "https://github.com/glpi-project/tools.git", + "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "url": "https://api.github.com/repos/glpi-project/tools/zipball/8ea2a7d4702a858f4b0360ba7d4f1841a5e77026", + "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026", "shasum": "" }, "require": { - "php": ">=5.3", - "react/stream": "^1.2" + "symfony/console": "^5.4 || ^6.0", + "twig/twig": "^3.3" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\React\\NDJson\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", - "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" - ], - "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + "nikic/php-parser": "^4.13", + "phpstan/phpstan-src": "^1.10" }, - "funding": [ - { - "url": "https://clue.engineering/support", - "type": "custom" - }, - { - "url": "https://github.com/clue", - "type": "github" - } + "bin": [ + "bin/extract-locales", + "bin/licence-headers-check", + "tools/plugin-release" ], - "time": "2022-12-23T10:58:28+00:00" - }, - { - "name": "composer/pcre", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Composer\\Pcre\\": "src" + "GlpiProject\\Tools\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "GPL-3.0-or-later" ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Teclib'", + "email": "glpi@teclib.com", + "homepage": "http://teclib-group.com" } ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "description": "Various tools for GLPI and its plugins", "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" + "glpi", + "plugins", + "tools" ], "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "issues": "https://github.com/glpi-project/tools/issues", + "source": "https://github.com/glpi-project/tools" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2025-10-14T10:26:06+00:00" }, { - "name": "composer/semver", - "version": "3.4.3", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Composer\\Semver\\": "src" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -188,210 +97,77 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "composer/xdebug-handler", - "version": "3.0.5", + "name": "symfony/console", + "version": "v6.4.27", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + "url": "https://github.com/symfony/console.git", + "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "url": "https://api.github.com/repos/symfony/console/zipball/13d3176cf8ad8ced24202844e9f95af11e2959fc", + "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc", "shasum": "" }, "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, - "require": { - "php": "^7.1 || ^8.0" + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "Symfony\\Component\\Console\\": "" }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:15:36+00:00" - }, - { - "name": "evenement/evenement", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Evenement\\": "src/" - } + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -399,3319 +175,24 @@ ], "authors": [ { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], - "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" - }, - "time": "2023-08-08T05:53:35+00:00" - }, - { - "name": "fidry/cpu-core-counter", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Tiny utility to get the number of CPU cores.", + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2024-08-06T10:04:20+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.75.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", - "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", - "shasum": "" - }, - "require": { - "clue/ndjson-react": "^1.0", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", - "ext-filter": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", - "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.5", - "react/event-loop": "^1.0", - "react/promise": "^2.0 || ^3.0", - "react/socket": "^1.0", - "react/stream": "^1.0", - "sebastian/diff": "^4.0 || ^5.1 || ^6.0 || ^7.0", - "symfony/console": "^5.4 || ^6.4 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", - "symfony/finder": "^5.4 || ^6.4 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", - "symfony/polyfill-mbstring": "^1.31", - "symfony/polyfill-php80": "^1.31", - "symfony/polyfill-php81": "^1.31", - "symfony/process": "^5.4 || ^6.4 || ^7.2", - "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.6", - "infection/infection": "^0.29.14", - "justinrainbow/json-schema": "^5.3 || ^6.2", - "keradus/cli-executor": "^2.1", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.7", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12", - "symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3", - "symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/Fixer/Internal/*" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2025-03-31T18:40:42+00:00" - }, - { - "name": "friendsoftwig/twigcs", - "version": "v6.1.0", - "source": { - "type": "git", - "url": "https://github.com/friendsoftwig/twigcs.git", - "reference": "3c36d606c4f19db0dd2a01b735ec7a8151b7f182" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/friendsoftwig/twigcs/zipball/3c36d606c4f19db0dd2a01b735ec7a8151b7f182", - "reference": "3c36d606c4f19db0dd2a01b735ec7a8151b7f182", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0", - "symfony/console": "^4.4 || ^5.3 || ^6.0", - "symfony/filesystem": "^4.4 || ^5.3 || ^6.0", - "symfony/finder": "^4.4 || ^5.3 || ^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5.20", - "symfony/phpunit-bridge": "^6.2.3" - }, - "bin": [ - "bin/twigcs" - ], - "type": "library", - "autoload": { - "psr-4": { - "FriendsOfTwig\\Twigcs\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tristan Maindron", - "email": "tmaindron@gmail.com" - } - ], - "description": "Checkstyle automation for Twig", - "support": { - "issues": "https://github.com/friendsoftwig/twigcs/issues", - "source": "https://github.com/friendsoftwig/twigcs/tree/v6.1.0" - }, - "time": "2023-01-04T16:01:24+00:00" - }, - { - "name": "glpi-project/tools", - "version": "0.7.4", - "source": { - "type": "git", - "url": "https://github.com/glpi-project/tools.git", - "reference": "65a09a93350da6fa67d423dd94e4cb4023a17e20" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/glpi-project/tools/zipball/65a09a93350da6fa67d423dd94e4cb4023a17e20", - "reference": "65a09a93350da6fa67d423dd94e4cb4023a17e20", - "shasum": "" - }, - "require": { - "symfony/console": "^5.4 || ^6.0", - "twig/twig": "^3.3" - }, - "require-dev": { - "nikic/php-parser": "^4.13", - "phpstan/phpstan-src": "^1.10" - }, - "bin": [ - "bin/extract-locales", - "bin/licence-headers-check", - "tools/plugin-release" - ], - "type": "library", - "autoload": { - "psr-4": { - "GlpiProject\\Tools\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-or-later" - ], - "authors": [ - { - "name": "Teclib'", - "email": "glpi@teclib.com", - "homepage": "http://teclib-group.com" - } - ], - "description": "Various tools for GLPI and its plugins", - "keywords": [ - "glpi", - "plugins", - "tools" - ], - "support": { - "issues": "https://github.com/glpi-project/tools/issues", - "source": "https://github.com/glpi-project/tools" - }, - "time": "2024-09-18T06:58:02+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.13.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", - "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.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-02-12T12:17:51+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.4.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", - "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.0-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.4.0" - }, - "time": "2024-12-30T11:07:19+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": "phpstan/phpstan", - "version": "1.12.24", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "338b92068f58d9f8035b76aed6cf2b9e5624c025" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/338b92068f58d9f8035b76aed6cf2b9e5624c025", - "reference": "338b92068f58d9f8035b76aed6cf2b9e5624c025", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2025-04-16T13:01:53+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.32", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.6" - }, - "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": "9.2.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/9.2.32" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:23:01+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.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": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "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": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.6.22", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.5.0 || ^2", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", - "sebastian/version": "^3.0.2" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.6-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/9.6.22" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2024-12-05T13:48:26+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "react/cache", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/promise": "^3.0 || ^2.0 || ^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, Promise-based cache interface for ReactPHP", - "keywords": [ - "cache", - "caching", - "promise", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2022-11-30T15:59:55+00:00" - }, - { - "name": "react/child-process", - "version": "v0.6.6", - "source": { - "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/event-loop": "^1.2", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/socket": "^1.16", - "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\ChildProcess\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven library for executing child processes with ReactPHP.", - "keywords": [ - "event-driven", - "process", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-01-01T16:37:48+00:00" - }, - { - "name": "react/dns", - "version": "v1.13.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-13T14:18:03+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "suggest": { - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2023-11-13T13:48:05+00:00" - }, - { - "name": "react/promise", - "version": "v3.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-05-24T10:39:05+00:00" - }, - { - "name": "react/socket", - "version": "v1.16.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.13", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.6 || ^1.2.1", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3.3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-07-26T10:38:09+00:00" - }, - { - "name": "react/stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "support": { - "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-11T12:45:25+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.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", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:27:43+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.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", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:41:17+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "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", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:19:30+00:00" - }, - { - "name": "sebastian/diff", - "version": "4.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:30:58+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:03:51+00:00" - }, - { - "name": "sebastian/exporter", - "version": "4.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "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", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:33:00+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:35:11+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.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", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:20:34+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "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", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.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" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-14T16:00:52+00:00" - }, - { - "name": "sebastian/type", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:13:03+00:00" - }, - { - "name": "sebastian/version", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "symfony/console", - "version": "v5.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v5.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-06T11:30:55+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", - "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "2.5-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", - "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/dependency-injection": "<4.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", - "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/event-dispatcher": "^1" - }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "2.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", - "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "symfony/process": "^5.4|^6.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-22T13:05:35+00:00" - }, - { - "name": "symfony/finder", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "63741784cd7b9967975eec610b256eed3ede022b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", - "reference": "63741784cd7b9967975eec610b256eed3ede022b", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-28T13:32:08+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", - "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" + "cli", + "command-line", + "console", + "terminal" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/console/tree/v6.4.27" }, "funding": [ { @@ -3722,49 +203,47 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-10-06T10:25:16+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=8.1" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3781,18 +260,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -3808,30 +279,30 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { "php": ">=7.2" }, "provide": { - "ext-mbstring": "*" + "ext-ctype": "*" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-ctype": "For best performance" }, "type": "library", "extra": { @@ -3845,7 +316,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3854,25 +325,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "ctype", "polyfill", - "portable", - "shim" + "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -3883,6 +353,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -3891,22 +365,25 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.31.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { "php": ">=7.2" }, + "suggest": { + "ext-intl": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -3919,11 +396,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3939,16 +413,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "grapheme", + "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -3959,30 +435,37 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { "php": ">=7.2" }, + "suggest": { + "ext-intl": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -3995,7 +478,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "classmap": [ "Resources/stubs" @@ -4006,10 +489,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -4019,16 +498,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -4039,6 +520,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4047,22 +532,29 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -4075,11 +567,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4095,16 +584,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -4116,65 +606,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/process", - "version": "v5.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", - "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v5.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -4182,33 +614,30 @@ "type": "tidelift" } ], - "time": "2024-11-06T11:36:42+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.4", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -4216,13 +645,16 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4249,7 +681,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -4261,65 +693,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -4327,38 +701,39 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v5.4.47", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4397,7 +772,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.47" + "source": "https://github.com/symfony/string/tree/v7.4.0" }, "funding": [ { @@ -4408,86 +783,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-10T20:33:58+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "twig/twig", - "version": "v3.11.3", + "version": "v3.22.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3b06600ff3abefaf8ff55d5c336cd1c4253f8c7e" + "reference": "4509984193026de413baf4ba80f68590a7f2c51d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3b06600ff3abefaf8ff55d5c336cd1c4253f8c7e", - "reference": "3b06600ff3abefaf8ff55d5c336cd1c4253f8c7e", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d", + "reference": "4509984193026de413baf4ba80f68590a7f2c51d", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22", - "symfony/polyfill-php81": "^1.29" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { + "phpstan/phpstan": "^2.0", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, @@ -4531,7 +859,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.11.3" + "source": "https://github.com/twigphp/Twig/tree/v3.22.0" }, "funding": [ { @@ -4543,20 +871,20 @@ "type": "tidelift" } ], - "time": "2024-11-07T12:34:41+00:00" + "time": "2025-10-29T15:56:47+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "~7.4.0|~8.0.0|~8.1.0|~8.2.0" + "php": ">=8.2" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { - "php": "7.4" + "php": "8.2.99" }, "plugin-api-version": "2.6.0" } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..f9e88c8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,13 @@ +import glpiEslintConfig from '../../eslint.config.mjs'; + +const filteredConfig = glpiEslintConfig.filter(config => !config.ignores); + +export default [ + ...filteredConfig, + { + ignores: [ + 'node_modules/*', + 'vendor/*', + ], + } +]; diff --git a/front/extfield.php b/front/extfield.php index 4c86e46..8944798 100644 --- a/front/extfield.php +++ b/front/extfield.php @@ -22,14 +22,16 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use Glpi\Exception\Http\NotFoundHttpException; + $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } diff --git a/front/import.php b/front/import.php index c0db4cd..4e5a5c5 100644 --- a/front/import.php +++ b/front/import.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -30,12 +30,11 @@ */ use Glpi\Application\View\TemplateRenderer; - -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Session::checkRight('plugin_jamf_mobiledevice', CREATE); diff --git a/front/menu.php b/front/menu.php index def095d..603f5df 100644 --- a/front/menu.php +++ b/front/menu.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -30,19 +30,19 @@ */ use Glpi\Application\View\TemplateRenderer; - -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Html::header('Jamf Plugin', '', 'tools', 'PluginJamfMenu', 'import'); +/** @var array $CFG_GLPI */ global $CFG_GLPI; -$plugin_dir = Plugin::getWebDir('jamf'); +$plugin_dir = $CFG_GLPI['root_doc'] . '/plugins/jamf'; $links = []; if (Session::haveRight('plugin_jamf_mobiledevice', CREATE)) { $links[] = [ @@ -51,13 +51,14 @@ ]; $links[] = [ 'name' => _x('menu', 'Merge existing devices', 'jamf'), - 'url' => "{$plugin_dir}/front/merge.php", + 'url' => $plugin_dir . '/front/merge.php', ]; } + if (Session::haveRight('config', UPDATE)) { $links[] = [ 'name' => _x('menu', 'Configuration', 'jamf'), - 'url' => Config::getFormURL() . '?forcetab=PluginJamfConfig', + 'url' => Config::getFormURL() . '?forcetab=PluginJamfConfig$1', ]; } diff --git a/front/merge.php b/front/merge.php index 1b3ed93..619f70b 100644 --- a/front/merge.php +++ b/front/merge.php @@ -22,21 +22,21 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use Glpi\Exception\Http\BadRequestHttpException; +use Glpi\DBAL\QueryExpression; use Glpi\Application\View\TemplateRenderer; -use Glpi\Toolbox\Sanitizer; - -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Session::checkRight('plugin_jamf_mobiledevice', CREATE); @@ -66,7 +66,10 @@ foreach ($pending as &$data) { $itemtype = $data['type']; - /** @var CommonDBTM $item */ + if (is_a($itemtype, 'CommonDBTM', true) === false) { + throw new BadRequestHttpException(); + } + $item = new $itemtype(); $jamftype = ('PluginJamf' . $data['jamf_type']); $guesses = $DB->request([ @@ -75,7 +78,7 @@ 'WHERE' => [ 'OR' => [ 'uuid' => $data['udid'], - 'name' => Sanitizer::sanitize($data['name']), + 'name' => $data['name'], ], 'is_deleted' => 0, 'is_template' => 0, @@ -83,11 +86,7 @@ 'ORDER' => new QueryExpression("CASE WHEN uuid='" . $data['udid'] . "' THEN 0 ELSE 1 END"), 'LIMIT' => 1, ]); - if (count($guesses)) { - $data['guessed_item'] = $guesses->current()['id']; - } else { - $data['guessed_item'] = 0; - } + $data['guessed_item'] = count($guesses) > 0 ? $guesses->current()['id'] : 0; } TemplateRenderer::getInstance()->display('@jamf/merge.html.twig', [ diff --git a/front/rule.test.php b/front/rule.test.php index 67af392..4d0e78a 100644 --- a/front/rule.test.php +++ b/front/rule.test.php @@ -22,18 +22,18 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include '../../../inc/includes.php'; +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } -require '../../../front/rule.test.php'; +require __DIR__ . '/../../../front/rule.test.php'; diff --git a/front/ruleimport.form.php b/front/ruleimport.form.php index 2512d3e..5997e71 100644 --- a/front/ruleimport.form.php +++ b/front/ruleimport.form.php @@ -22,18 +22,18 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } $rulecollection = new PluginJamfRuleImportCollection(); diff --git a/front/ruleimport.php b/front/ruleimport.php index cbadddf..4cb2e66 100644 --- a/front/ruleimport.php +++ b/front/ruleimport.php @@ -22,18 +22,18 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } $rulecollection = new PluginJamfRuleImportCollection(); diff --git a/front/user_jssaccount.form.php b/front/user_jssaccount.form.php index d5053e0..cf07845 100644 --- a/front/user_jssaccount.form.php +++ b/front/user_jssaccount.form.php @@ -22,24 +22,25 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use Glpi\Exception\Http\BadRequestHttpException; use Glpi\Event; - -include('../../../inc/includes.php'); +use Glpi\Exception\Http\NotFoundHttpException; $plugin = new Plugin(); if (!$plugin->isActivated('jamf')) { - Html::displayNotFoundError(); + throw new NotFoundHttpException(); } Session::checkRight(PluginJamfUser_JSSAccount::$rightname, UPDATE); +/** @var DBmysql $DB */ global $DB; if ($_POST['jssaccounts_id'] == 0) { $DB->delete(PluginJamfUser_JSSAccount::getTable(), ['users_id' => $_POST['users_id']]); @@ -70,4 +71,4 @@ } } -Html::displayErrorAndDie('lost'); +throw new BadRequestHttpException(); diff --git a/hook.php b/hook.php index 54afdf7..8aceb30 100644 --- a/hook.php +++ b/hook.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -74,163 +74,161 @@ function plugin_jamf_getAddSearchOptions($itemtype) { $opt = []; $plugin = new Plugin(); - if ($plugin->isActivated('jamf')) { - if ($itemtype === 'Computer' || $itemtype === 'Phone') { - $opt = [ - '22002' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'last_inventory', - 'name' => 'Jamf - ' . _x('field', 'Last inventory', 'jamf'), - 'datatype' => 'datetime', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - '22003' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'entry_date', - 'name' => 'Jamf - ' . _x('field', 'Import date', 'jamf'), - 'datatype' => 'datetime', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - '22004' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'enroll_date', - 'name' => 'Jamf - ' . _x('field', 'Enrollment date', 'jamf'), - 'datatype' => 'datetime', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - '22005' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'managed', - 'name' => 'Jamf - ' . _x('field', 'Managed', 'jamf'), - 'datatype' => 'bool', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - '22006' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'supervised', - 'name' => 'Jamf - ' . _x('field', 'Supervised', 'jamf'), - 'datatype' => 'datetime', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - // '22007' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'cloud_backup_enabled', - // 'name' => 'Jamf - ' ._x('field', 'Cloud backup enabled', 'jamf'), - // 'datatype' => 'bool', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - '22008' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'activation_lock_enabled', - 'name' => 'Jamf - ' . _x('field', 'Activation lock enabled', 'jamf'), - 'datatype' => 'bool', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - // '22009' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_mode_enabled', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode enabled', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22010' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_mode_enforced', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode enforced', 'jamf'), - // 'datatype' => 'bool', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22011' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_mode_enable_date', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode enable date', 'jamf'), - // 'datatype' => 'datetime', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22012' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_mode_message', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode message', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22013' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_mode_phone', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode phone', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22014' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_location_latitude', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode latitude', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22015' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_location_longitude', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode longitude', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22016' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_location_altitude', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode altitude', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22017' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_location_speed', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode speed', 'jamf'), - // 'datatype' => 'text', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - // '22018' => [ - // 'table' => 'glpi_plugin_jamf_mobiledevices', - // 'field' => 'lost_location_date', - // 'name' => 'Jamf - ' ._x('field', 'Lost mode location date', 'jamf'), - // 'datatype' => 'datetime', - // 'massiveaction' => false, - // 'joinparams' => ['jointype' => 'itemtype_item'] - // ], - '22019' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'import_date', - 'name' => 'Jamf - ' . _x('field', 'Import date', 'jamf'), - 'datatype' => 'datetime', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - '22020' => [ - 'table' => 'glpi_plugin_jamf_devices', - 'field' => 'sync_date', - 'name' => 'Jamf - ' . _x('field', 'Sync date', 'jamf'), - 'datatype' => 'datetime', - 'massiveaction' => false, - 'joinparams' => ['jointype' => 'itemtype_item'], - ], - ]; - } + if ($plugin->isActivated('jamf') && ($itemtype === 'Computer' || $itemtype === 'Phone')) { + $opt = [ + '22002' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'last_inventory', + 'name' => 'Jamf - ' . _x('field', 'Last inventory', 'jamf'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + '22003' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'entry_date', + 'name' => 'Jamf - ' . _x('field', 'Import date', 'jamf'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + '22004' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'enroll_date', + 'name' => 'Jamf - ' . _x('field', 'Enrollment date', 'jamf'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + '22005' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'managed', + 'name' => 'Jamf - ' . _x('field', 'Managed', 'jamf'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + '22006' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'supervised', + 'name' => 'Jamf - ' . _x('field', 'Supervised', 'jamf'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + // '22007' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'cloud_backup_enabled', + // 'name' => 'Jamf - ' ._x('field', 'Cloud backup enabled', 'jamf'), + // 'datatype' => 'bool', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + '22008' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'activation_lock_enabled', + 'name' => 'Jamf - ' . _x('field', 'Activation lock enabled', 'jamf'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + // '22009' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_mode_enabled', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode enabled', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22010' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_mode_enforced', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode enforced', 'jamf'), + // 'datatype' => 'bool', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22011' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_mode_enable_date', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode enable date', 'jamf'), + // 'datatype' => 'datetime', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22012' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_mode_message', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode message', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22013' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_mode_phone', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode phone', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22014' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_location_latitude', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode latitude', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22015' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_location_longitude', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode longitude', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22016' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_location_altitude', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode altitude', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22017' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_location_speed', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode speed', 'jamf'), + // 'datatype' => 'text', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + // '22018' => [ + // 'table' => 'glpi_plugin_jamf_mobiledevices', + // 'field' => 'lost_location_date', + // 'name' => 'Jamf - ' ._x('field', 'Lost mode location date', 'jamf'), + // 'datatype' => 'datetime', + // 'massiveaction' => false, + // 'joinparams' => ['jointype' => 'itemtype_item'] + // ], + '22019' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'import_date', + 'name' => 'Jamf - ' . _x('field', 'Import date', 'jamf'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + '22020' => [ + 'table' => 'glpi_plugin_jamf_devices', + 'field' => 'sync_date', + 'name' => 'Jamf - ' . _x('field', 'Sync date', 'jamf'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => ['jointype' => 'itemtype_item'], + ], + ]; } return $opt; @@ -241,20 +239,21 @@ function plugin_jamf_dashboardCards($cards = []) if (is_null($cards)) { $cards = []; } + $cards = array_merge($cards, PluginJamfExtensionAttribute::dashboardCards()); - $cards = array_merge($cards, PluginJamfMobileDevice::dashboardCards()); - return $cards; + return array_merge($cards, PluginJamfMobileDevice::dashboardCards()); } function plugin_jamf_showJamfInfoForItem(array $params) { $item = $params['item']; - /** @var PluginJamfAbstractDevice $jamf_class */ $jamf_class = PluginJamfAbstractDevice::getJamfItemClassForGLPIItem($item::getType(), $item->getID()); if ($jamf_class !== null) { return $jamf_class::showForItem($params); } + + return null; } function plugin_jamf_status() diff --git a/inc/abstractdevice.class.php b/inc/abstractdevice.class.php index c724ec4..4d0e147 100644 --- a/inc/abstractdevice.class.php +++ b/inc/abstractdevice.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -36,13 +36,15 @@ abstract class PluginJamfAbstractDevice extends CommonDBChild { public static $itemtype = 'itemtype'; + public static $items_id = 'items_id'; - public static $jamftype_name = null; + + public static $jamftype_name; + public static $mustBeAttached = false; /** * Display the extra information for Jamf devices on the main Computer or Phone tab. - * @param array $params * @return void|bool Displays HTML only if a supported item is in the params parameter. If there is any issue, false is returned. * @since 1.0.0 * @since 2.0.0 Renamed from showForComputerOrPhoneMain to showForItem @@ -58,21 +60,23 @@ abstract public static function getJamfDeviceUrl(int $jamf_id): string; /** * Cleanup relations when an item is purged. - * @param CommonDBTM $item * @global DBmysql $DB */ private static function purgeItemCommon(CommonDBTM $item) { + /** @var DBmysql $DB */ global $DB; $jamf_class = static::getJamfItemClassForGLPIItem($item::getType(), $item->getID()); if (!is_string($jamf_class)) { return; } + $jamf_item = $jamf_class::getJamfItemForGLPIItem($item); if ($jamf_item === null) { return; } + $device = $jamf_item->getJamfDeviceData(); if (!empty($device)) { $DB->delete('glpi_plugin_jamf_devices', [ @@ -86,24 +90,23 @@ private static function purgeItemCommon(CommonDBTM $item) /** * Cleanup relations when a Computer is purged. - * @param Computer $item * @global DBmysql $DB */ public static function plugin_jamf_purgeComputer(Computer $item) { - static::purgeItemCommon($item); + self::purgeItemCommon($item); } /** * Cleanup relations when a Phone is purged. - * @param Phone $item * @global DBmysql $DB */ public static function plugin_jamf_purgePhone(Phone $item) { + /** @var DBmysql $DB */ global $DB; - static::purgeItemCommon($item); + self::purgeItemCommon($item); $DB->delete(Item_OperatingSystem::getTable(), [ 'itemtype' => $item::getType(), 'items_id' => $item->getID(), @@ -119,6 +122,7 @@ public static function preUpdatePhone($item) public static function getJamfItemClassForGLPIItem(string $itemtype, $items_id): ?string { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ @@ -131,7 +135,7 @@ public static function getJamfItemClassForGLPIItem(string $itemtype, $items_id): 'items_id' => $items_id, ], ]); - if (count($iterator)) { + if (count($iterator) > 0) { $jamf_type = $iterator->current()['jamf_type']; if ($jamf_type === 'Computer') { return PluginJamfComputer::class; @@ -147,6 +151,7 @@ public static function getJamfItemClassForGLPIItem(string $itemtype, $items_id): public static function getJamfItemForGLPIItem(CommonDBTM $item, $limit_to_type = false): ?PluginJamfAbstractDevice { + /** @var DBmysql $DB */ global $DB; $found_type = static::class; @@ -175,7 +180,7 @@ public static function getJamfItemForGLPIItem(CommonDBTM $item, $limit_to_type = 'items_id' => $item->getID(), ], ]); - if (count($iterator)) { + if (count($iterator) > 0) { $jamf_data = $iterator->current(); if ($jamf_data['jamf_type'] === 'Computer') { $found_type = PluginJamfComputer::class; @@ -200,6 +205,11 @@ public static function getJamfItemForGLPIItem(CommonDBTM $item, $limit_to_type = public function getGLPIItem() { $itemtype = $this->fields['itemtype']; + + if (!is_a($itemtype, CommonDBTM::class, true)) { + throw new RuntimeException('Invalid itemtype stored in PluginJamfAbstractDevice: ' . $itemtype); + } + $item = new $itemtype(); $item->getFromDB($this->fields['items_id']); @@ -208,13 +218,14 @@ public function getGLPIItem() public function getJamfDeviceData() { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ 'FROM' => 'glpi_plugin_jamf_devices', 'WHERE' => ['id' => $this->fields['glpi_plugin_jamf_devices_id']], ]); - if (count($iterator)) { + if (count($iterator) > 0) { return $iterator->current(); } @@ -225,6 +236,7 @@ abstract public function getMDMCommands(); public function getExtensionAttributes() { + /** @var DBmysql $DB */ global $DB; $ext_table = PluginJamfExtensionAttribute::getTable(); diff --git a/inc/api.class.php b/inc/api.class.php index 4d73b51..49b62a2 100644 --- a/inc/api.class.php +++ b/inc/api.class.php @@ -22,16 +22,20 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\RequestOptions; +use function Safe\json_decode; +use function Safe\simplexml_load_string; + /** * Unified connector for Jamf's Classic and Pro APIs */ @@ -56,11 +60,16 @@ class PluginJamfAPI */ protected static function getClassic(string $endpoint, $raw = false, $response_type = 'application/json') { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); + } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); } - $url = static::$connection->getAPIUrl($endpoint); - $client = static::$connection->getClient(); + + $url = self::$connection->getAPIUrl($endpoint); + $client = self::$connection->getClient(); try { $response = $client->get($url, [ @@ -71,7 +80,7 @@ protected static function getClassic(string $endpoint, $raw = false, $response_t ]); $httpcode = $response->getStatusCode(); $response = $response->getBody()->getContents(); - } catch (GuzzleHttp\Exception\ClientException $e) { + } catch (ClientException) { return null; } @@ -84,6 +93,7 @@ protected static function getClassic(string $endpoint, $raw = false, $response_t throw new PluginJamfRateLimitException($fault['faultstring']); } } + throw new RuntimeException(_x('error', 'Unknown JSS API Error', 'jamf')); } @@ -99,11 +109,16 @@ protected static function getClassic(string $endpoint, $raw = false, $response_t */ protected static function addClassic(string $endpoint, string $payload) { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); + } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); } - $url = (static::$connection)->getAPIUrl($endpoint); - $client = static::$connection->getClient(); + + $url = (self::$connection)->getAPIUrl($endpoint); + $client = self::$connection->getClient(); try { $response = $client->post($url, [ @@ -114,8 +129,8 @@ protected static function addClassic(string $endpoint, string $payload) RequestOptions::BODY => $payload, ]); $httpcode = $response->getStatusCode(); - } catch (GuzzleHttp\Exception\ClientException $e) { - return null; + } catch (ClientException) { + return false; } return ($httpcode === 201) ? true : $httpcode; @@ -130,11 +145,16 @@ protected static function addClassic(string $endpoint, string $payload) */ protected static function updateClassic(string $endpoint, array $data) { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); } - $url = (static::$connection)->getAPIUrl($endpoint); - $client = static::$connection->getClient(); + + if (!self::$connection) { + self::$connection = new static::$connection_class(); + } + + $url = (self::$connection)->getAPIUrl($endpoint); + $client = self::$connection->getClient(); try { $response = $client->put($url, [ @@ -145,8 +165,8 @@ protected static function updateClassic(string $endpoint, array $data) RequestOptions::BODY => $data, ]); $httpcode = $response->getStatusCode(); - } catch (GuzzleException $e) { - return null; + } catch (GuzzleException) { + return false; } return ($httpcode === 201) ? true : $httpcode; @@ -160,17 +180,22 @@ protected static function updateClassic(string $endpoint, array $data) */ protected static function deleteClassic(string $endpoint) { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); + } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); } - $url = (static::$connection)->getAPIUrl($endpoint); - $client = static::$connection->getClient(); + + $url = (self::$connection)->getAPIUrl($endpoint); + $client = self::$connection->getClient(); try { $response = $client->delete($url); $httpcode = $response->getStatusCode(); - } catch (GuzzleException $e) { - return null; + } catch (GuzzleException) { + return false; } return ($httpcode === 200) ? true : $httpcode; @@ -186,7 +211,7 @@ private static function getParamStringClassic(array $params = []) { $param_str = ''; foreach ($params as $key => $value) { - $param_str = "{$param_str}/{$key}/{$value}"; + $param_str = sprintf('%s/%s/%s', $param_str, $key, $value); } return $param_str; @@ -203,10 +228,11 @@ private static function getParamStringClassic(array $params = []) public static function getItemsClassic(string $itemtype, array $params = [], $user_auth = false) { if ($user_auth && !PluginJamfUser_JSSAccount::canReadJSSItem($itemtype)) { - return null; + return []; } - $param_str = static::getParamStringClassic($params); - $endpoint = "$itemtype$param_str"; + + $param_str = self::getParamStringClassic($params); + $endpoint = $itemtype . $param_str; $response = static::getClassic($endpoint); // Strip first key (usually like mobile_devices or mobile_device) @@ -231,11 +257,12 @@ public static function addItemClassic(string $itemtype, string $payload, $user_a $param_str = ''; $meta = null; } + if ($user_auth && !PluginJamfUser_JSSAccount::canCreateJSSItem($itemtype, $meta)) { - return null; + return false; } - $endpoint = "$itemtype$param_str"; + $endpoint = $itemtype . $param_str; return static::addClassic($endpoint, $payload); } @@ -252,10 +279,11 @@ public static function addItemClassic(string $itemtype, string $payload, $user_a public static function updateItemClassic(string $itemtype, array $params = [], array $fields = [], $user_auth = false) { if ($user_auth && !PluginJamfUser_JSSAccount::canUpdateJSSItem($itemtype)) { - return null; + return false; } - $param_str = static::getParamStringClassic($params); - $endpoint = "$itemtype$param_str"; + + $param_str = self::getParamStringClassic($params); + $endpoint = $itemtype . $param_str; return static::updateClassic($endpoint, $fields); } @@ -271,17 +299,18 @@ public static function updateItemClassic(string $itemtype, array $params = [], a public static function deleteItemClassic(string $itemtype, array $params = [], $user_auth = false) { if ($user_auth && !PluginJamfUser_JSSAccount::canDeleteJSSItem($itemtype)) { - return null; + return false; } - $param_str = static::getParamStringClassic($params); - $endpoint = "$itemtype$param_str"; + + $param_str = self::getParamStringClassic($params); + $endpoint = $itemtype . $param_str; return static::deleteClassic($endpoint); } private static function getJSSGroupActionRights($groupid) { - $response = static::getClassic("accounts/groupid/$groupid"); + $response = static::getClassic('accounts/groupid/' . $groupid); return $response['group']['privileges']['jss_actions']; } @@ -291,8 +320,9 @@ public static function getJSSAccountRights($userid, $user_auth = false) if ($user_auth && !PluginJamfUser_JSSAccount::canReadJSSItem('accounts')) { return null; } - $response = static::getClassic("accounts/userid/$userid", true, 'application/xml'); - $account = simplexml_load_string($response); + + $response = static::getClassic('accounts/userid/' . $userid, true, 'application/xml'); + $account = simplexml_load_string((string) $response); $access_level = $account->access_level; $rights = [ @@ -305,7 +335,7 @@ public static function getJSSAccountRights($userid, $user_auth = false) //$groups = $account->groups->group; for ($i = 0; $i < $group_count; $i++) { $group = $account->groups->group[$i]; - if (isset($group->privileges->jss_objects)) { + if (property_exists($group->privileges, 'jss_objects') && $group->privileges->jss_objects !== null) { $c = count($group->privileges->jss_objects->privilege); if ($c > 0) { for ($j = 0; $j < $c; $j++) { @@ -313,11 +343,12 @@ public static function getJSSAccountRights($userid, $user_auth = false) } } } + // Why are jss_actions not included in the group when all other rights are? - $action_privileges = static::getJSSGroupActionRights(reset($group->id)); + $action_privileges = self::getJSSGroupActionRights(reset($group->id)); $rights['jss_actions'] = $action_privileges; - if (isset($group->privileges->jss_settings)) { + if (property_exists($group->privileges, 'jss_settings') && $group->privileges->jss_settings !== null) { $c = count($group->privileges->jss_settings->privilege); if ($c > 0) { for ($j = 0; $j < $c; $j++) { @@ -328,7 +359,7 @@ public static function getJSSAccountRights($userid, $user_auth = false) } } else { $privileges = $account->privileges; - if (isset($privileges->jss_objects)) { + if (property_exists($privileges, 'jss_objects') && $privileges->jss_objects !== null) { $c = count($privileges->jss_objects->privilege); if ($c > 0) { for ($j = 0; $j < $c; $j++) { @@ -336,7 +367,8 @@ public static function getJSSAccountRights($userid, $user_auth = false) } } } - if (isset($privileges->jss_actions)) { + + if (property_exists($privileges, 'jss_actions') && $privileges->jss_actions !== null) { $c = count($privileges->jss_actions->privilege); if ($c > 0) { for ($j = 0; $j < $c; $j++) { @@ -344,7 +376,8 @@ public static function getJSSAccountRights($userid, $user_auth = false) } } } - if (isset($privileges->jss_settings)) { + + if (property_exists($privileges, 'jss_settings') && $privileges->jss_settings !== null) { $c = count($privileges->jss_settings->privilege); if ($c > 0) { for ($j = 0; $j < $c; $j++) { @@ -363,7 +396,7 @@ public static function testClassicAPIConnection(): bool static::getItemsClassic('mobiledevices', ['match' => '?name=glpi_conn_test']); return true; - } catch (RuntimeException $e) { + } catch (RuntimeException) { return false; } } @@ -374,7 +407,7 @@ public static function testProAPIConnection(): bool static::getJamfProVersion(); return true; - } catch (RuntimeException $e) { + } catch (RuntimeException) { return false; } } @@ -384,10 +417,15 @@ public static function testProAPIConnection(): bool */ public static function getJamfProVersion(): string { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); + } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); } - $response = static::$connection->getClient()->get(static::$connection->getAPIUrl('v1/jamf-pro-version', true))->getBody()->getContents(); + + $response = self::$connection->getClient()->get(self::$connection->getAPIUrl('v1/jamf-pro-version', true))->getBody()->getContents(); return json_decode($response, true)['version']; } @@ -403,9 +441,14 @@ public static function sendMDMCommand(string $payload_xml, bool $user_auth = fal */ public static function getAllMobileDevices() { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); + } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); } + $all_results = []; $endpoint_base = '/v2/mobile-devices'; @@ -413,8 +456,8 @@ public static function getAllMobileDevices() 'page' => 0, 'page-size' => 1000, ]; - $client = static::$connection->getClient(); - $response = $client->get(static::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); + $client = self::$connection->getClient(); + $response = $client->get(self::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); $initial_response = json_decode($response->getBody()->getContents(), true); $total_results = $initial_response['totalCount']; $all_results = array_merge($all_results, $initial_response['results']); @@ -424,7 +467,7 @@ public static function getAllMobileDevices() $pages = ceil($total_results / 1000); for ($i = 1; $i < $pages; $i++) { $query_params['page'] = $i; - $response = $client->get(static::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); + $response = $client->get(self::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); $response = json_decode($response->getBody()->getContents(), true); $all_results = [...$all_results, ...$response['results']]; } @@ -441,11 +484,16 @@ public static function getAllMobileDevices() */ public static function getMobileDeviceByID(int $id, bool $detailed = false) { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); + } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); } - $endpoint = "/v2/mobile-devices/{$id}" . ($detailed ? '/detail' : ''); - $response = static::$connection->getClient()->get(static::$connection->getAPIUrl($endpoint, true)); + + $endpoint = '/v2/mobile-devices/' . $id . ($detailed ? '/detail' : ''); + $response = self::$connection->getClient()->get(self::$connection->getAPIUrl($endpoint, true)); return json_decode($response->getBody()->getContents(), true); } @@ -458,15 +506,20 @@ public static function getMobileDeviceByID(int $id, bool $detailed = false) */ public static function getMobileDeviceByUDID(string $udid, string $section = 'general'): ?array { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); + } + $query_params = [ 'section' => strtoupper($section), 'filter' => 'udid=="' . $udid . '"', ]; - $endpoint = '/v2/mobile-devices/detail' . '?' . http_build_query($query_params); - $response = static::$connection->getClient()->get(static::$connection->getAPIUrl($endpoint, true)); + $endpoint = '/v2/mobile-devices/detail?' . http_build_query($query_params); + $response = self::$connection->getClient()->get(self::$connection->getAPIUrl($endpoint, true)); $result = json_decode($response->getBody()->getContents(), true); if (isset($result['results']) && count($result['results']) > 0) { return $result['results'][0]; @@ -481,9 +534,14 @@ public static function getMobileDeviceByUDID(string $udid, string $section = 'ge */ public static function getAllComputers() { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); } + + if (!self::$connection) { + self::$connection = new static::$connection_class(); + } + $all_results = []; $endpoint_base = '/v1/computers-inventory'; @@ -491,8 +549,8 @@ public static function getAllComputers() 'page' => 0, 'page-size' => 1000, ]; - $client = static::$connection->getClient(); - $response = $client->get(static::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); + $client = self::$connection->getClient(); + $response = $client->get(self::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); $initial_response = json_decode($response->getBody()->getContents(), true); $total_results = $initial_response['totalCount']; $all_results = array_merge($all_results, $initial_response['results']); @@ -502,7 +560,7 @@ public static function getAllComputers() $pages = ceil($total_results / 1000); for ($i = 1; $i < $pages; $i++) { $query_params['page'] = $i; - $response = $client->get(static::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); + $response = $client->get(self::$connection->getAPIUrl($endpoint_base, true) . '?' . http_build_query($query_params)); $response = json_decode($response->getBody()->getContents(), true); $all_results = [...$all_results, ...$response['results']]; } @@ -513,11 +571,16 @@ public static function getAllComputers() public static function getComputerByID(int $id, bool $detailed = false) { - if (!static::$connection) { - static::$connection = new static::$connection_class(); + if (!is_a(static::$connection_class, PluginJamfConnection::class, true)) { + throw new RuntimeException(_x('error', 'Connection not configured properly', 'jamf')); } - $endpoint = '/v1/computer-inventory' . ($detailed ? '-detail' : '') . "/{$id}"; - $response = static::$connection->getClient()->get(static::$connection->getAPIUrl($endpoint, true)); + + if (!self::$connection) { + self::$connection = new static::$connection_class(); + } + + $endpoint = '/v1/computer-inventory' . ($detailed ? '-detail' : '') . ('/' . $id); + $response = self::$connection->getClient()->get(self::$connection->getAPIUrl($endpoint, true)); return json_decode($response->getBody()->getContents(), true); } diff --git a/inc/computer.class.php b/inc/computer.class.php index f4e1282..0224568 100644 --- a/inc/computer.class.php +++ b/inc/computer.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -48,34 +48,35 @@ public static function getTypeName($nb = 1) /** * Display the extra information for Jamf computers on the main Computer tab. - * @param array $params - * @return void|bool Displays HTML only if a supported item is in the params parameter. If there is any issue, false is returned. + * @return bool|null Displays HTML only if a supported item is in the params parameter. If there is any issue, false is returned. * @throws Exception * @since 2.0.0 Renamed from showForComputerOrPhoneMain to showForItem * @since 1.0.0 */ public static function showForItem(array $params) { + /** @var array $CFG_GLPI */ + global $CFG_GLPI; + $item = $params['item']; if (!self::canView() || $item::getType() !== 'Computer') { return false; } - $getYesNo = static function ($value) { - return $value ? __('Yes') : __('No'); - }; + $getYesNo = (static fn($value) => $value ? __('Yes') : __('No')); $jamf_item = static::getJamfItemForGLPIItem($item); - if ($jamf_item === null) { + if (!$jamf_item instanceof PluginJamfAbstractDevice) { return false; } + $match = $jamf_item->fields; $match = array_merge($match, $jamf_item->getJamfDeviceData()); $js = ''; if ($item->canUpdate()) { - $ajax_url = Plugin::getWebDir('jamf') . '/ajax/sync.php'; + $ajax_url = $CFG_GLPI['root_doc'] . '/plugins/jamf/ajax/sync.php'; $js = << [ 'caption' => _x('form_section', 'Jamf General Information', 'jamf'), @@ -134,7 +136,7 @@ function syncDevice(itemtype, items_id) { ], 'sync' => [ 'caption' => _x('action', 'Sync now', 'jamf'), - 'on_click' => "syncDevice(\"{$item::getType()}\", {$item->getID()}); return false;", + 'on_click' => sprintf('syncDevice("%s", %s); return false;', $item::getType(), $item->getID()), ], ], 'extra_js' => $js, @@ -144,6 +146,7 @@ function syncDevice(itemtype, items_id) { TemplateRenderer::getInstance()->display('@jamf/inventory_info.html.twig', [ 'info' => $info, ]); + return null; } /** @@ -155,7 +158,7 @@ public static function getJamfDeviceUrl(int $jamf_id): string { $config = PluginJamfConfig::getConfig(); - return "{$config['jssserver']}/computers.html?id={$jamf_id}"; + return sprintf('%s/computers.html?id=%d', $config['jssserver'], $jamf_id); } public function getMDMCommands() diff --git a/inc/computersoftware.class.php b/inc/computersoftware.class.php index 47b057b..2b9a608 100644 --- a/inc/computersoftware.class.php +++ b/inc/computersoftware.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf diff --git a/inc/computersync.class.php b/inc/computersync.class.php index 55e01c1..65c1394 100644 --- a/inc/computersync.class.php +++ b/inc/computersync.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -30,10 +30,12 @@ */ use Glpi\Event; +use Safe\DateTime; class PluginJamfComputerSync extends PluginJamfDeviceSync { protected static $jamfplugin_itemtype = 'PluginJamfComputer'; + protected static $jamf_itemtype = 'Computer'; protected function syncGeneral(): PluginJamfDeviceSync @@ -43,6 +45,7 @@ protected function syncGeneral(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; $hardware = $this->data['hardware']; @@ -50,6 +53,7 @@ protected function syncGeneral(): PluginJamfDeviceSync if (($general['name'] !== $this->item->fields['name'])) { $this->item_changes['name'] = $general['name']; } + $other_general_items = [ 'asset_tag' => 'otherserial', 'serial_number' => 'serial', @@ -89,11 +93,12 @@ protected function syncGeneral(): PluginJamfDeviceSync if ($this->item === null || $this->item->fields['states_id'] === 0) { $this->item_changes['states_id'] = $this->config['default_status']; } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncGeneral'] = self::STATUS_ERROR; return $this; } + $this->status['syncGeneral'] = self::STATUS_OK; return $this; @@ -106,6 +111,7 @@ protected function syncOS(): PluginJamfDeviceSync return $this; } + try { $hardware = $this->data['hardware']; $os = $this->applyDesiredState('OperatingSystem', [ @@ -130,11 +136,12 @@ protected function syncOS(): PluginJamfDeviceSync 'itemtype' => $this->item::getType(), 'items_id' => $this->item->getID(), ]); - } catch (Exception $e) { + } catch (Exception) { $this->status['syncOS'] = self::STATUS_ERROR; return $this; } + $this->status['syncOS'] = self::STATUS_OK; return $this; @@ -142,6 +149,7 @@ protected function syncOS(): PluginJamfDeviceSync protected function syncSoftware(): PluginJamfDeviceSync { + /** @var DBmysql $DB */ global $DB; if (!$this->config['sync_software'] || $this->item === null || !isset($this->data['software']['applications'])) { @@ -160,6 +168,7 @@ protected function syncSoftware(): PluginJamfDeviceSync if ($software_data === null) { continue; } + $software = $this->applyDesiredState('Software', [ 'name' => $this->db->escape($software_data['general']['name']), ], [ @@ -193,9 +202,7 @@ protected function syncSoftware(): PluginJamfDeviceSync $installed_bundles = array_column($applications, 'identifier'); $inventoried_software = PluginJamfComputerSoftware::getForGlpiItem($this->item); // We don't need to worry about versions because Apple mobile devices cannot have multiple installs for the same bundle id, so no multiple version support - $to_remove_software = array_filter($inventoried_software, static function ($software) use ($installed_bundles) { - return !in_array($software['bundle_id'], $installed_bundles, true); - }); + $to_remove_software = array_filter($inventoried_software, static fn($software) => !in_array($software['bundle_id'], $installed_bundles, true)); foreach ($to_remove_software as $to_remove) { $DB->delete(Item_SoftwareVersion::getTable(), [ SoftwareVersion::getTable() . '.softwares_id' => $to_remove['softwares_id'], @@ -218,7 +225,7 @@ protected function syncSoftware(): PluginJamfDeviceSync ], ]); } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncSoftware'] = self::STATUS_ERROR; return $this; @@ -236,6 +243,7 @@ protected function syncUser(): PluginJamfDeviceSync return $this; } + try { $location = $this->data['location']; $user = new User(); @@ -243,11 +251,12 @@ protected function syncUser(): PluginJamfDeviceSync if ($users_id) { $this->item_changes['users_id'] = $users_id; } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncUser'] = self::STATUS_ERROR; return $this; } + $this->status['syncUser'] = self::STATUS_OK; return $this; @@ -260,6 +269,7 @@ protected function syncPurchasing(): PluginJamfDeviceSync return $this; } + try { $purchasing = $this->data['purchasing']; $infocom_changes = []; @@ -274,9 +284,11 @@ protected function syncPurchasing(): PluginJamfDeviceSync $infocom_changes['warranty_duration'] = $warranty_length; } } + if (!empty($purchasing['applecare_id'])) { - $infocom_changes['warranty_info'] = "AppleCare ID: {$purchasing['applecare_id']}"; + $infocom_changes['warranty_info'] = 'AppleCare ID: ' . $purchasing['applecare_id']; } + if (!empty($purchasing['po_number'])) { $infocom_changes['order_number'] = $purchasing['po_number']; } @@ -285,11 +297,12 @@ protected function syncPurchasing(): PluginJamfDeviceSync 'itemtype' => $this->item::getType(), 'items_id' => $this->item->getID(), ]); - } catch (Exception $e) { + } catch (Exception) { $this->status['syncPurchasing'] = self::STATUS_ERROR; return $this; } + $this->status['syncPurchasing'] = self::STATUS_OK; return $this; @@ -306,6 +319,7 @@ protected function syncExtensionAttributes(): PluginJamfDeviceSync return $this; } + try { $extension_attributes = $this->data['extension_attributes']; $ext_attribute = new PluginJamfExtensionAttribute(); @@ -326,11 +340,12 @@ protected function syncExtensionAttributes(): PluginJamfDeviceSync ]); } } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncExtensionAttributes'] = self::STATUS_ERROR; return $this; } + $this->status['syncExtensionAttributes'] = self::STATUS_OK; return $this; @@ -343,14 +358,16 @@ protected function syncSecurity(): PluginJamfDeviceSync return $this; } + try { $security = $this->data['security']; $this->commondevice_changes['activation_lock_enabled'] = $security['activation_lock']; - } catch (Exception $e) { + } catch (Exception) { $this->status['syncSecurity'] = self::STATUS_ERROR; return $this; } + $this->status['syncSecurity'] = self::STATUS_OK; return $this; @@ -363,11 +380,12 @@ protected function syncNetwork(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; $hardware = $this->data['hardware']; - $expected_netcard_name = "Generic {$hardware['model']} Network Card"; + $expected_netcard_name = sprintf('Generic %s Network Card', $hardware['model']); if (isset($general['alt_mac_address']) && !empty($general['alt_mac_address'])) { $wifi_model = $this->createOrGetItem('DeviceNetworkCardModel', ['name' => $expected_netcard_name], [ @@ -446,11 +464,12 @@ protected function syncNetwork(): PluginJamfDeviceSync ]); } } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncNetwork'] = self::STATUS_ERROR; return $this; } + $this->status['syncNetwork'] = self::STATUS_OK; return $this; @@ -463,16 +482,19 @@ protected function syncGeneralJamfPluginItem(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; if (!empty($general['last_inventory_update_utc'])) { $last_inventory = PluginJamfToolbox::utcToLocal($general['last_inventory_update_utc']); $this->commondevice_changes['last_inventory'] = $last_inventory; } + if (!empty($general['initial_entry_date_utc'])) { $entry_date = PluginJamfToolbox::utcToLocal($general['initial_entry_date_utc']); $this->commondevice_changes['entry_date'] = $entry_date; } + if (!empty($general['last_enrollment_utc'])) { $enroll_date = PluginJamfToolbox::utcToLocal($general['last_enrollment_utc']); $this->commondevice_changes['enroll_date'] = $enroll_date; @@ -484,11 +506,12 @@ protected function syncGeneralJamfPluginItem(): PluginJamfDeviceSync $this->commondevice_changes['udid'] = $general['udid']; $this->commondevice_changes['managed'] = $general['remote_management']['managed']; $this->commondevice_changes['supervised'] = $general['supervised']; - } catch (Exception $e) { + } catch (Exception) { $this->status['syncGeneralJamfPluginItem'] = self::STATUS_ERROR; return $this; } + $this->status['syncGeneralJamfPluginItem'] = self::STATUS_OK; return $this; @@ -501,6 +524,7 @@ protected function syncComponents(): PluginJamfDeviceSync return $this; } + $hardware = $this->data['hardware']; // Boot firmware @@ -527,6 +551,7 @@ protected function syncComponents(): PluginJamfDeviceSync 'is_recursive' => 1, ]); } + $storage = $hardware['storage']; if (!empty($storage)) { foreach ($storage as $disk) { @@ -556,8 +581,9 @@ protected function syncComponents(): PluginJamfDeviceSync return $this; } - public static function discover(): bool + public static function discover(): int { + /** @var DBmysql $DB */ global $DB; $volume = 0; @@ -566,6 +592,7 @@ public static function discover(): bool // API error or device no longer exists in Jamf return -1; } + $imported = []; $iterator = $DB->request([ 'SELECT' => ['jamf_items_id'], @@ -575,6 +602,7 @@ public static function discover(): bool foreach ($iterator as $data) { $imported[] = $data['jamf_items_id']; } + $pending_iterator = $DB->request([ 'SELECT' => ['jamf_items_id'], 'FROM' => 'glpi_plugin_jamf_imports', @@ -597,21 +625,19 @@ public static function discover(): bool if ($result) { $volume++; } - } catch (Exception $e2) { + } catch (Exception) { // Some other error } - } else { - if (!in_array((int) $jamf_device['id'], $pending_import, true)) { - // Just discovered and cannot auto-import. Save to imports table instead. - $DB->insert('glpi_plugin_jamf_imports', [ - 'jamf_type' => 'Computer', - 'jamf_items_id' => $jamf_device['id'], - 'name' => $DB->escape($jamf_device['general']['name']), - 'type' => 'Computer', - 'date_discover' => $_SESSION['glpi_currenttime'], - 'udid' => $jamf_device['udid'], - ]); - } + } elseif (!in_array((int) $jamf_device['id'], $pending_import, true)) { + // Just discovered and cannot auto-import. Save to imports table instead. + $DB->insert('glpi_plugin_jamf_imports', [ + 'jamf_type' => 'Computer', + 'jamf_items_id' => $jamf_device['id'], + 'name' => $DB->escape($jamf_device['general']['name']), + 'type' => 'Computer', + 'date_discover' => $_SESSION['glpi_currenttime'], + 'udid' => $jamf_device['udid'], + ]); } } } @@ -621,12 +647,19 @@ public static function discover(): bool public static function import(string $itemtype, int $jamf_items_id, $use_transaction = true): bool { + /** @var DBmysql $DB */ global $DB; if (!self::isSupportedGlpiItemtype($itemtype)) { // Invalid itemtype for a mobile device return false; } + + if (!is_a($itemtype, CommonDBTM::class, true)) { + // Cannot import directly an ITIL object + return false; + } + $item = new $itemtype(); $jamf_item = static::$api::getItemsClassic('computers', ['id' => $jamf_items_id]); @@ -660,7 +693,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac ]); if ($iterator->count()) { // Already imported - Event::log(0, $itemtype, 4, 'Jamf plugin', "Jamf computer $jamf_items_id not imported. A {$itemtype::getTypeName(1)} exists with the same uuid."); + Event::log(0, $itemtype, 4, 'Jamf plugin', sprintf('Jamf computer %d not imported. A %s exists with the same uuid.', $jamf_items_id, $itemtype::getTypeName(1))); return false; } @@ -668,6 +701,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac if ($use_transaction) { $DB->beginTransaction(); } + // Import new device $items_id = $item->add([ 'name' => $DB->escape($jamf_item['general']['name']), @@ -692,6 +726,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac return false; } + $device_id = $DB->insertId(); $jamf_computer = new PluginJamfComputer(); $r2 = $jamf_computer->add([ @@ -704,6 +739,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac return false; } + if (self::sync($itemtype, $items_id, false)) { $DB->update('glpi_plugin_jamf_devices', [ 'import_date' => $_SESSION['glpi_currenttime'], @@ -715,10 +751,8 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac if ($use_transaction) { $DB->commit(); } - } else { - if ($use_transaction) { - $DB->rollBack(); - } + } elseif ($use_transaction) { + $DB->rollBack(); } } else { if ($use_transaction) { @@ -733,6 +767,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac protected static function getJamfDataForSyncingByGlpiItem(string $itemtype, int $items_id): array { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ @@ -744,9 +779,10 @@ protected static function getJamfDataForSyncingByGlpiItem(string $itemtype, int ], ]); - if (!count($iterator)) { + if (count($iterator) === 0) { return []; } + $jamf_item = $iterator->current(); return static::$api::getItemsClassic('computers', ['id' => $jamf_item['jamf_items_id']]) ?? []; diff --git a/inc/config.class.php b/inc/config.class.php index 73735b3..9aac45b 100644 --- a/inc/config.class.php +++ b/inc/config.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -41,7 +41,7 @@ class PluginJamfConfig extends CommonDBTM public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { if (!$withtemplate && $item::getType() === Config::class) { - return _x('plugin_info', 'Jamf plugin', 'jamf'); + return self::createTabEntry(__s("Jamf", "jamf"), 0, $item::getType(), self::getIcon()); } return ''; @@ -52,11 +52,20 @@ public function showForm($ID = -1, array $options = []) if (!Session::haveRight('config', UPDATE)) { return false; } + $config = self::getConfig(true); + $computer = new Computer(); + $phone = new Phone(); + $states_condition = [ + $computer->getStateVisibilityCriteria(), + $phone->getStateVisibilityCriteria(), + ]; + TemplateRenderer::getInstance()->display('@jamf/config.html.twig', [ 'config' => $config, 'url' => Toolbox::getItemTypeFormURL('Config'), + 'states_condition' => $states_condition, ]); return true; @@ -89,6 +98,7 @@ public static function getConfig(bool $force_all = false): array if ($config === null) { $config = Config::getConfigurationValues('plugin:Jamf'); } + if (!$force_all) { return self::undiscloseConfigValue($config); } @@ -104,4 +114,9 @@ public static function configUpdate($input) return $input; // TODO: Change the autogenerated stub } + + public static function getIcon() + { + return 'fas fa-tablet-alt'; + } } diff --git a/inc/connection.class.php b/inc/connection.class.php index 3705b1b..51740d4 100644 --- a/inc/connection.class.php +++ b/inc/connection.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -30,7 +30,11 @@ */ use GuzzleHttp\Client; -use GuzzleHttp\ClientTrait; + +use function Safe\curl_setopt; +use function Safe\json_decode; +use function Safe\curl_exec; +use function Safe\curl_init; /** * JamfConnection class @@ -43,7 +47,7 @@ class PluginJamfConnection private ?string $bearer_token = null; /** - * @var ClientTrait + * @var Client */ protected $client; @@ -69,8 +73,6 @@ public function __construct() */ public function setConnectionConfig($jssserver, $jssuser, $jsspassword) { - global $DB; - $glpikey = new GLPIKey(); $enc = $glpikey->encrypt($jsspassword); Config::setConfigurationValues('plugin:Jamf', [ @@ -105,14 +107,15 @@ public function getAPIUrl($endpoint, $pro_api = false) { $server_url = $this->config['jssserver']; // Remove trailing slash - if (str_ends_with($server_url, '/')) { - $server_url = substr($server_url, 0, -1); + if (str_ends_with((string) $server_url, '/')) { + $server_url = substr((string) $server_url, 0, -1); } + if ($pro_api) { - return "{$server_url}/api/{$endpoint}"; + return sprintf('%s/api/%s', $server_url, $endpoint); } - return "{$server_url}/JSSResource/{$endpoint}"; + return sprintf('%s/JSSResource/%s', $server_url, $endpoint); } public static function getUserAgentString(): string @@ -124,7 +127,6 @@ public static function getUserAgentString(): string * Sets all common curl options needed for the API calls. * * @param $curl - * @return void */ public function setCurlOptions(&$curl): void { @@ -151,6 +153,7 @@ protected function setCurlAuth(&$curl) //curl_setopt($auth_curl, CURLOPT_USERPWD, $this->config['jssuser'] . ':' . $this->config['jsspassword']); } + curl_setopt($auth_curl, CURLOPT_POST, true); curl_setopt($auth_curl, CURLOPT_POSTFIELDS, []); curl_setopt($auth_curl, CURLOPT_RETURNTRANSFER, true); @@ -192,6 +195,7 @@ private function fetchBearerToken() 'Authorization: Basic ' . $basic_auth, ]); } + curl_setopt($auth_curl, CURLOPT_POST, true); curl_setopt($auth_curl, CURLOPT_POSTFIELDS, []); curl_setopt($auth_curl, CURLOPT_RETURNTRANSFER, true); @@ -203,16 +207,14 @@ private function fetchBearerToken() } /** - * @return ClientTrait + * @return Client */ public function getClient() { - /** - * @var array $CFG_GLPI - */ + /** @var array $CFG_GLPI */ global $CFG_GLPI; - if (!isset($this->client)) { + if ($this->client === null) { if ($this->bearer_token === null) { $this->fetchBearerToken(); } @@ -227,10 +229,10 @@ public function getClient() ]; //TODO use Toolbox::getGuzzleClient in GLPI 10.1 if (!empty($CFG_GLPI['proxy_name'])) { - $proxy_creds = !empty($CFG_GLPI['proxy_user']) - ? $CFG_GLPI['proxy_user'] . ':' . (new GLPIKey())->decrypt($CFG_GLPI['proxy_passwd']) . '@' - : ''; - $proxy_string = "http://{$proxy_creds}" . $CFG_GLPI['proxy_name'] . ':' . $CFG_GLPI['proxy_port']; + $proxy_creds = empty($CFG_GLPI['proxy_user']) + ? '' + : $CFG_GLPI['proxy_user'] . ':' . (new GLPIKey())->decrypt($CFG_GLPI['proxy_passwd']) . '@'; + $proxy_string = 'http://' . $proxy_creds . $CFG_GLPI['proxy_name'] . ':' . $CFG_GLPI['proxy_port']; $options['proxy'] = $proxy_string; } diff --git a/inc/cron.class.php b/inc/cron.class.php index 2a26c00..1f8055a 100644 --- a/inc/cron.class.php +++ b/inc/cron.class.php @@ -22,13 +22,16 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use function Safe\file_get_contents; +use function Safe\file_put_contents; + /** * Contains all cron functions for Jamf plugin * @since 1.0.0 @@ -45,10 +48,11 @@ public static function cronSyncJamf(CronTask $task) $volume = 0; $engines = PluginJamfSync::getDeviceSyncEngines(); - foreach ($engines as $jamf_class => $engine) { + foreach ($engines as $engine) { $v = $engine::syncAll(); - $volume += $v >= 0 ? $v : 0; + $volume += max($v, 0); } + $task->addVolume($volume); return 1; @@ -59,10 +63,11 @@ public static function cronImportJamf(CronTask $task) $volume = 0; $engines = PluginJamfSync::getDeviceSyncEngines(); - foreach ($engines as $jamf_class => $engine) { + foreach ($engines as $engine) { $v = $engine::discover(); - $volume += $v >= 0 ? $v : 0; + $volume += max($v, 0); } + $task->addVolume($volume); return 1; @@ -74,27 +79,30 @@ public static function cronUpdatePMV(CronTask $task): int $out_file = GLPI_PLUGIN_DOC_DIR . '/jamf/pmv.json'; $json = file_get_contents($url); - if ($json === false) { + if ($json === "") { $task->log(__('Unable to fetch PMV JSON from Apple', 'jamf')); return 0; } + try { $json = json_decode($json, true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { + } catch (JsonException) { $task->log(__('Retrieved malformed PMV JSON', 'jamf')); return 0; } + unset($json['PublicAssetSets']); try { $json = json_encode($json, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { + } catch (JsonException) { $task->log(__('Unable to encode PMV JSON', 'jamf')); return 0; } - if (file_put_contents($out_file, $json) === false) { + + if (file_put_contents($out_file, $json) === 0) { $task->log(__('Unable to write PMV JSON to file', 'jamf')); return 0; diff --git a/inc/dbutil.class.php b/inc/dbutil.class.php index 2e13beb..0918deb 100644 --- a/inc/dbutil.class.php +++ b/inc/dbutil.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -37,19 +37,22 @@ class PluginJamfDBUtil { public static function dropTable(string $table) { + /** @var DBmysql $DB */ global $DB; - return $DB->query('DROP TABLE' . $DB::quoteName($table)); + return $DB->doQuery('DROP TABLE' . $DB::quoteName($table)); } public static function dropTableOrDie(string $table, string $message = '') { + /** @var DBmysql $DB */ global $DB; if (!$DB->tableExists($table)) { return true; } - $res = $DB->query('DROP TABLE' . $DB::quoteName($table)); + + $res = $DB->doQuery('DROP TABLE' . $DB::quoteName($table)); if (!$res) { //TRANS: %1$s is the description, %2$s is the query, %3$s is the error message $message = sprintf( @@ -58,12 +61,7 @@ public static function dropTableOrDie(string $table, string $message = '') $table, $DB->error(), ); - if (isCommandLine()) { - throw new RuntimeException($message); - } - - echo $message . "\n"; - die(1); + throw new RuntimeException($message); } return $res; @@ -71,10 +69,11 @@ public static function dropTableOrDie(string $table, string $message = '') public static function truncate($table) { + /** @var DBmysql $DB */ global $DB; $table_name = $DB::quoteName($table); - return $DB->query("TRUNCATE $table_name"); + return $DB->doQuery('TRUNCATE ' . $table_name); } } diff --git a/inc/devicesync.class.php b/inc/devicesync.class.php index 60d97fd..d1bbe6d 100644 --- a/inc/devicesync.class.php +++ b/inc/devicesync.class.php @@ -22,13 +22,15 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use Glpi\DBAL\QueryExpression; + abstract class PluginJamfDeviceSync extends PluginJamfSync { protected $commondevice_changes = []; @@ -36,7 +38,6 @@ abstract class PluginJamfDeviceSync extends PluginJamfSync /** * Sync general information such as name, serial number, etc. * All synced fields here are on the main GLPI item and not a plugin item type. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncGeneral(): PluginJamfDeviceSync @@ -48,7 +49,6 @@ protected function syncGeneral(): PluginJamfDeviceSync /** * Sync operating system information. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncOS(): PluginJamfDeviceSync @@ -60,7 +60,6 @@ protected function syncOS(): PluginJamfDeviceSync /** * Sync software information. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncSoftware(): PluginJamfDeviceSync @@ -72,7 +71,6 @@ protected function syncSoftware(): PluginJamfDeviceSync /** * Sync user information. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncUser(): PluginJamfDeviceSync @@ -84,7 +82,6 @@ protected function syncUser(): PluginJamfDeviceSync /** * Sync purchasing information. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncPurchasing(): PluginJamfDeviceSync @@ -96,7 +93,6 @@ protected function syncPurchasing(): PluginJamfDeviceSync /** * Sync extension attributes. This task will be deferred if run for a device that was not previously imported. - * @return PluginJamfDeviceSync * @since 1.1.0 */ protected function syncExtensionAttributes(): PluginJamfDeviceSync @@ -108,7 +104,6 @@ protected function syncExtensionAttributes(): PluginJamfDeviceSync /** * Sync security information. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncSecurity(): PluginJamfDeviceSync @@ -120,7 +115,6 @@ protected function syncSecurity(): PluginJamfDeviceSync /** * Sync network information. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncNetwork(): PluginJamfDeviceSync @@ -132,7 +126,6 @@ protected function syncNetwork(): PluginJamfDeviceSync /** * Sync general Jamf device information. All changes are made for the Jamf plugin item only. No GLPI item changes are made here. - * @return PluginJamfDeviceSync * @since 1.1.0 */ protected function syncGeneralJamfPluginItem(): PluginJamfDeviceSync @@ -144,7 +137,6 @@ protected function syncGeneralJamfPluginItem(): PluginJamfDeviceSync /** * Sync component information such as volumes. - * @return PluginJamfDeviceSync * @since 2.0.0 */ protected function syncComponents(): PluginJamfDeviceSync @@ -156,6 +148,7 @@ protected function syncComponents(): PluginJamfDeviceSync public static function syncExtensionAttributeDefinitions(): void { + /** @var DBmysql $DB */ global $DB; switch (static::$jamf_itemtype) { @@ -168,9 +161,11 @@ public static function syncExtensionAttributeDefinitions(): void default: $api_itemtype = null; } + if ($api_itemtype === null) { return; } + $ext_attr = new PluginJamfExtensionAttribute(); $all_attributes = static::$api::getItemsClassic($api_itemtype); if (is_array($all_attributes)) { @@ -190,6 +185,7 @@ public static function syncExtensionAttributeDefinitions(): void public static function syncAll(): int { + /** @var DBmysql $DB */ global $DB; $volume = 0; @@ -205,19 +201,20 @@ public static function syncAll(): int 'SELECT' => ['itemtype', 'items_id'], 'FROM' => 'glpi_plugin_jamf_devices', 'WHERE' => [ - new QueryExpression("sync_date < NOW() - INTERVAL {$config['sync_interval']} MINUTE"), + new QueryExpression(sprintf('sync_date < NOW() - INTERVAL %s MINUTE', $config['sync_interval'])), ], ]); if (!$iterator->count()) { return -1; } + foreach ($iterator as $data) { try { $result = static::sync($data['itemtype'], $data['items_id']); if ($result) { $volume++; } - } catch (Exception $e2) { + } catch (Exception) { // Some other error } } @@ -227,28 +224,31 @@ public static function syncAll(): int /** * Updates a device from data received from the Jamf API. The item must already exist in GLPI and be linked. - * @param string $itemtype - * @param int $items_id * @param bool $use_transaction True if a DB transaction should be used. * @return bool True if the update was successful. * @throws Exception Any exception that occurs during the update process. */ public static function sync(string $itemtype, int $items_id, bool $use_transaction = true): bool { + /** @var DBmysql $DB */ global $DB; - /** @var CommonDBTM $item */ + if (!is_a($itemtype, CommonDBTM::class, true)) { + Toolbox::logDebug('Invalid itemtype provided for sync: ' . $itemtype); + return false; + } + $item = new $itemtype(); if (!$item->getFromDB($items_id)) { $itemtype_name = $item::getTypeName(1); - Toolbox::logError(_x('error', "Attempted to sync non-existent {$itemtype_name} with ID {$items_id}", 'jamf')); + Toolbox::logDebug(_x('error', sprintf('Attempted to sync non-existent %s with ID %d', $itemtype_name, $items_id), 'jamf')); return false; } $data = static::getJamfDataForSyncingByGlpiItem($itemtype, $items_id); - if (empty($data)) { + if ($data === []) { // API error or device no longer exists in Jamf return false; } @@ -273,13 +273,12 @@ public static function sync(string $itemtype, int $items_id, bool $use_transacti ->finalizeSync(); // Evaluate final sync result. If any errors exist, count as failure. // Any tasks that are still deferred are also counted as failures. - $failed = array_filter($sync_result, static function ($v) { - return in_array($v, [self::STATUS_ERROR, self::STATUS_DEFERRED], true); - }, ARRAY_FILTER_USE_BOTH); - if (count($failed) !== 0) { + $failed = array_filter($sync_result, static fn($v) => in_array($v, [self::STATUS_ERROR, self::STATUS_DEFERRED], true), ARRAY_FILTER_USE_BOTH); + if ($failed !== []) { if ($use_transaction) { $DB->rollBack(); } + throw new RuntimeException('One or more sync actions failed [' . implode(', ', array_keys($failed)) . ']'); } @@ -288,8 +287,8 @@ public static function sync(string $itemtype, int $items_id, bool $use_transacti } return true; - } catch (Exception $e) { - trigger_error($e->getMessage()); + } catch (Exception $exception) { + trigger_error($exception->getMessage()); if ($use_transaction) { $DB->rollBack(); } @@ -308,6 +307,7 @@ protected function finalizeSync() if ($this->dummySync) { return $this->status; } + $this->commondevice_changes['sync_date'] = $_SESSION['glpi_currenttime']; // Update GLPI Item $this->item->update([ @@ -316,6 +316,7 @@ protected function finalizeSync() foreach ($this->extitem_changes as $key => $value) { PluginJamfExtField::setValue($this->item::getType(), $this->item->getID(), $key, $value); } + // Update or Add Jamf Item $this->db->updateOrInsert('glpi_plugin_jamf_devices', $this->commondevice_changes, [ 'itemtype' => $this->item::getType(), @@ -331,14 +332,20 @@ protected function finalizeSync() 'items_id' => $this->item->getID(), ], ]); - if (count($iterator)) { + if (count($iterator) > 0) { $device_id = $iterator->current()['id']; } + $this->db->updateOrInsert(static::$jamfplugin_itemtype::getTable(), $this->jamfplugin_item_changes, [ 'glpi_plugin_jamf_devices_id' => $device_id, ]); if ($this->jamfplugin_device === null || empty($this->jamfplugin_device->fields)) { + + if (!is_a(static::$jamfplugin_itemtype, CommonDBTM::class, true)) { + throw new RuntimeException('Invalid jamfplugin_itemtype: ' . static::$jamfplugin_itemtype); + } + $jamf_item = new static::$jamfplugin_itemtype(); $jamf_match = $this->db->request([ 'SELECT' => ['id'], @@ -356,7 +363,7 @@ protected function finalizeSync() ], ], ]); - if (count($jamf_match)) { + if (count($jamf_match) > 0) { $jamf_item->getFromDB(reset($jamf_match)['id']); $this->jamfplugin_device = $jamf_item; } @@ -384,7 +391,6 @@ protected function finalizeSync() /** * Sync all other data not handled by the built-in {@link \PluginJamfDeviceSync} sync methods. * - * @return PluginJamfDeviceSync * @since 1.0.0 */ protected function syncOther(): PluginJamfDeviceSync diff --git a/inc/exception/ratelimitexception.class.php b/inc/exception/ratelimitexception.class.php index 7103662..b3a3a2e 100644 --- a/inc/exception/ratelimitexception.class.php +++ b/inc/exception/ratelimitexception.class.php @@ -22,15 +22,11 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -if (!defined('GLPI_ROOT')) { - die("Sorry. You can't access this file directly"); -} - class PluginJamfRateLimitException extends Exception {} diff --git a/inc/extensionattribute.class.php b/inc/extensionattribute.class.php index 88734da..7731f36 100644 --- a/inc/extensionattribute.class.php +++ b/inc/extensionattribute.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -43,11 +43,13 @@ public static function getTypeName($nb = 1) public function addOrUpdate($input) { + /** @var DBmysql $DB */ global $DB; if (!isset($input['jamf_id'])) { return false; } + $jamf_id = $input['jamf_id']; unset($input['jamf_id']); @@ -56,6 +58,7 @@ public function addOrUpdate($input) public static function dashboardCards() { + /** @var DBmysql $DB */ global $DB; $table = self::getTable(); @@ -67,7 +70,7 @@ public static function dashboardCards() foreach ($iterator as $data) { $slug = strtolower(str_replace(' ', '_', $data['name'])); - $cards["plugin_jamf_extensionattribute_{$slug}"] = [ + $cards['plugin_jamf_extensionattribute_' . $slug] = [ 'widgettype' => ['halfdonut'], 'label' => sprintf(_x('dashboard', 'Jamf Attribute - %s', 'jamf'), $data['name']), 'provider' => 'PluginJamfExtensionAttribute::cardProvider', @@ -80,6 +83,7 @@ public static function dashboardCards() public static function cardProvider($name, array $params = []) { + /** @var DBmysql $DB */ global $DB; $rel_table = PluginJamfItem_ExtensionAttribute::getTable(); @@ -87,7 +91,7 @@ public static function cardProvider($name, array $params = []) $iterator = $DB->request([ 'SELECT' => [ 'value', - 'COUNT' => "{$rel_table}.id as cpt", + 'COUNT' => $rel_table . '.id as cpt', ], 'FROM' => $table, 'JOIN' => [ @@ -99,7 +103,7 @@ public static function cardProvider($name, array $params = []) ], ], 'WHERE' => ['name' => $name], - 'GROUP' => "{$rel_table}.value", + 'GROUP' => $rel_table . '.value', ]); $card_data = []; diff --git a/inc/extfield.class.php b/inc/extfield.class.php index f6dfb5b..0dfa381 100644 --- a/inc/extfield.class.php +++ b/inc/extfield.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -52,6 +52,7 @@ public static function getValue($itemtype, $items_id, $name) public static function setValue($itemtype, $items_id, $name, $value) { + /** @var DBmysql $DB */ global $DB; $DB->updateOrInsert(self::getTable(), [ diff --git a/inc/import.class.php b/inc/import.class.php index c30935d..418f5cb 100644 --- a/inc/import.class.php +++ b/inc/import.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf diff --git a/inc/item_extensionattribute.class.php b/inc/item_extensionattribute.class.php index 1141dbb..7dec6ce 100644 --- a/inc/item_extensionattribute.class.php +++ b/inc/item_extensionattribute.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -39,6 +39,7 @@ class PluginJamfItem_ExtensionAttribute extends CommonDBChild { public static $itemtype = 'itemtype'; + public static $items_id = 'items_id'; public static function getTypeName($nb = 1) @@ -48,14 +49,18 @@ public static function getTypeName($nb = 1) public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { - /** @var PluginJamfAbstractDevice $jamf_class */ + if (!$item instanceof CommonDBTM) { + return ''; + } + $jamf_class = PluginJamfAbstractDevice::getJamfItemClassForGLPIItem($item::getType(), $item->getID()); if ($jamf_class === null) { - return false; + return ''; } + $jamf_item = $jamf_class::getJamfItemForGLPIItem($item); - if ($jamf_class === null || !$jamf_class::canView()) { - return false; + if (!$jamf_class::canView()) { + return ''; } return self::createTabEntry(self::getTypeName(2), self::countForJamfItem($jamf_item)); @@ -71,12 +76,15 @@ public static function countForJamfItem($jamf_item) public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { + if (!$item instanceof CommonDBTM) { + return false; + } + return self::showForItem($item); } public static function showForItem(CommonDBTM $item) { - /** @var PluginJamfAbstractDevice $jamf_class */ $jamf_class = PluginJamfAbstractDevice::getJamfItemClassForGLPIItem($item::getType(), $item->getID()); if ($jamf_class === null || !$jamf_class::canView()) { return false; diff --git a/inc/item_mdmcommand.class.php b/inc/item_mdmcommand.class.php index 80765cd..363fb5b 100644 --- a/inc/item_mdmcommand.class.php +++ b/inc/item_mdmcommand.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -31,6 +31,9 @@ use Glpi\Application\View\TemplateRenderer; +use function Safe\file_get_contents; +use function Safe\json_decode; + /** * JSS Item_MDMCommand class * @@ -47,9 +50,13 @@ public static function getTypeName($nb = 0) public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { + if (!$item instanceof CommonDBTM) { + return ''; + } + $jamf_class = PluginJamfAbstractDevice::getJamfItemClassForGLPIItem($item::getType(), $item->getID()); if ($jamf_class !== PluginJamfMobileDevice::class || !PluginJamfMobileDevice::canView()) { - return false; + return ''; } return self::getTypeName(2); @@ -57,11 +64,14 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { + if (!$item instanceof CommonDBTM) { + return false; + } + return self::showForItem($item); } /** - * @param PluginJamfMobileDevice $mobiledevice * @return array * @since 1.0.0 */ @@ -124,6 +134,7 @@ public static function getApplicableCommands(PluginJamfMobileDevice $mobiledevic } } } + self::applySpecificParams($allcommands, $mobiledevice); return $allcommands; @@ -138,11 +149,7 @@ private static function getPMVData(): array if ($data === null) { $pmv_file = GLPI_PLUGIN_DOC_DIR . '/jamf/pmv.json'; - if (file_exists($pmv_file)) { - $data = json_decode(file_get_contents($pmv_file), true)['AssetSets']; - } else { - $data = []; - } + $data = file_exists($pmv_file) ? json_decode(file_get_contents($pmv_file), true)['AssetSets'] : []; } return $data; @@ -151,27 +158,20 @@ private static function getPMVData(): array private static function getAvailableUpdates($model_identifier): array { $data = self::getPMVData(); - if (empty($data)) { + if ($data === []) { return []; } - $data['iOS'] = array_filter($data['iOS'], static function ($item) use ($model_identifier) { - return $item['ExpirationDate'] > date('Y-m-d') && in_array($model_identifier, $item['SupportedDevices'], true); - }); - $data['macOS'] = array_filter($data['macOS'], static function ($item) use ($model_identifier) { - return $item['ExpirationDate'] > date('Y-m-d') && in_array($model_identifier, $item['SupportedDevices'], true); - }); + + $data['iOS'] = array_filter($data['iOS'], static fn($item) => $item['ExpirationDate'] > date('Y-m-d') && in_array($model_identifier, $item['SupportedDevices'], true)); + $data['macOS'] = array_filter($data['macOS'], static fn($item) => $item['ExpirationDate'] > date('Y-m-d') && in_array($model_identifier, $item['SupportedDevices'], true)); // Sort so newest updates are first - usort($data['iOS'], static function ($a, $b) { - return version_compare($b['ProductVersion'], $a['ProductVersion']); - }); - usort($data['macOS'], static function ($a, $b) { - return version_compare($b['ProductVersion'], $a['ProductVersion']); - }); - - if (count($data['iOS']) > 0) { + usort($data['iOS'], static fn($a, $b) => version_compare($b['ProductVersion'], $a['ProductVersion'])); + usort($data['macOS'], static fn($a, $b) => version_compare($b['ProductVersion'], $a['ProductVersion'])); + + if ($data['iOS'] !== []) { return array_column($data['iOS'], 'ProductVersion'); - } elseif (count($data['macOS']) > 0) { + } elseif ($data['macOS'] !== []) { return array_column($data['macOS'], 'ProductVersion'); } else { return []; @@ -182,7 +182,7 @@ public static function applySpecificParams(&$commands, PluginJamfAbstractDevice { if (isset($commands['ScheduleOSUpdate'])) { $applicable_updates = self::getAvailableUpdates($mobiledevice->getJamfDeviceData()['model_identifier']); - if (count($applicable_updates) > 0) { + if ($applicable_updates !== []) { // Replace product_version plain-text field with a dropdown of versions $commands['ScheduleOSUpdate']['params']['product_version']['type'] = 'dropdown'; $commands['ScheduleOSUpdate']['params']['product_version']['values'] = $applicable_updates; @@ -197,7 +197,7 @@ public static function showForItem(CommonDBTM $item) } $mobiledevice = PluginJamfMobileDevice::getJamfItemForGLPIItem($item); - if ($mobiledevice === null) { + if (!$mobiledevice instanceof PluginJamfMobileDevice) { return false; } diff --git a/inc/mdmcommand.class.php b/inc/mdmcommand.class.php index c57b690..25d0b56 100644 --- a/inc/mdmcommand.class.php +++ b/inc/mdmcommand.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -38,9 +38,6 @@ */ class PluginJamfMDMCommand { - /** - * @return array - */ public static function getAvailableCommands(): array { static $allcommands = null; diff --git a/inc/menu.class.php b/inc/menu.class.php index 4af0328..c8fec0a 100644 --- a/inc/menu.class.php +++ b/inc/menu.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -43,12 +43,12 @@ class PluginJamfMenu extends CommonGLPI */ public static function getTypeName($nb = 0) { - return _x('plugin_info', 'Jamf plugin', 'jamf'); + return __s('Jamf', 'jamf'); } public static function getMenuName() { - return _x('plugin_info', 'Jamf plugin', 'jamf'); + return __s('Jamf', 'jamf'); } public static function getIcon() @@ -58,10 +58,8 @@ public static function getIcon() /** * Check if can view item - * - * @return boolean */ - public static function canView() + public static function canView(): bool { return Config::canView(); } diff --git a/inc/migration.class.php b/inc/migration.class.php index 6bf673e..149edc8 100644 --- a/inc/migration.class.php +++ b/inc/migration.class.php @@ -22,13 +22,17 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use function Safe\preg_match; +use function Safe\Copy; +use function Safe\mkdir; + /** * Handles migrating between plugin versions. * @@ -37,6 +41,7 @@ final class PluginJamfMigration { private const BASE_VERSION = '1.0.0'; + /** * @var Migration */ @@ -47,8 +52,8 @@ final class PluginJamfMigration */ private $db; - /** @var PluginJamfAPI */ - private $api; + /** @var 'PluginJamfAPI' */ + private $api = 'PluginJamfAPI'; /** * PluginJamfMigration constructor. @@ -57,6 +62,7 @@ final class PluginJamfMigration */ public function __construct($version) { + /** @var DBmysql $DB */ global $DB; $this->glpiMigration = new Migration($version); @@ -66,13 +72,15 @@ public function __construct($version) public function applyMigrations() { $rc = new ReflectionClass($this); - $otherMigrationFunctions = array_map(static function ($rm) { - return $rm->getShortName(); - }, array_filter($rc->getMethods(), static function ($m) { - return preg_match('/(?<=^apply_)(.*)(?=_migration$)/', $m->getShortName()); - })); - - if (count($otherMigrationFunctions)) { + $otherMigrationFunctions = array_map( + static fn($rm) => $rm->getShortName(), + array_filter( + $rc->getMethods(), + static fn($m): bool => preg_match('/(?<=^apply_)(.*)(?=_migration$)/', $m->getShortName()) === 1, + ), + ); + + if ($otherMigrationFunctions !== []) { // Map versions to functions $versionMap = []; foreach ($otherMigrationFunctions as $function) { @@ -129,6 +137,9 @@ public function uninstall() 'plugin_version', ]); CronTask::unregister('Jamf'); + if (is_dir(GLPI_PLUGIN_DOC_DIR . "/jamf/")) { + Toolbox::deleteDir(GLPI_PLUGIN_DOC_DIR . "/jamf/"); + } } private function setPluginVersionInDB($version) @@ -162,7 +173,7 @@ public function apply_1_0_0_migration(): void PRIMARY KEY (`id`), UNIQUE KEY `unicity` (`jamf_items_id`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin imports table' . $this->db->error()); + $this->db->doQuery($query); } // Check mobile devices table (Extra data for mobile devices) @@ -196,7 +207,7 @@ public function apply_1_0_0_migration(): void UNIQUE KEY `unicity` (`itemtype`, `items_id`), KEY `udid` (`udid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"; - $this->db->queryOrDie($query, 'Error creating JAMF plugin mobile devices table' . $this->db->error()); + $this->db->doQuery($query); } // Check software table (Extra data for software). Also check the later name just to avoid useless SQL actions. @@ -208,7 +219,7 @@ public function apply_1_0_0_migration(): void `itunes_store_url` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin software table' . $this->db->error()); + $this->db->doQuery($query); } $jamfconfig = Config::getConfigurationValues('plugin:Jamf'); @@ -265,8 +276,9 @@ private function apply_1_1_0_migration() KEY `name` (`name`), UNIQUE KEY `jamf_id` (`jamf_id`, `itemtype`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin extension attribute table' . $this->db->error()); + $this->db->doQuery($query); } + if (!$this->db->tableExists('glpi_plugin_jamf_items_extensionattributes')) { $query = 'CREATE TABLE `glpi_plugin_jamf_items_extensionattributes` ( `id` int(11) NOT NULL auto_increment, @@ -278,8 +290,9 @@ private function apply_1_1_0_migration() KEY `item` (`itemtype`, `items_id`), UNIQUE `unicity` (`itemtype`, `items_id`, `glpi_plugin_jamf_extensionattributes_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin item extension attribute table' . $this->db->error()); + $this->db->doQuery($query); } + if (!$this->db->tableExists('glpi_plugin_jamf_extfields')) { $query = "CREATE TABLE `glpi_plugin_jamf_extfields` ( `id` int(11) NOT NULL auto_increment, @@ -291,8 +304,9 @@ private function apply_1_1_0_migration() KEY `item` (`itemtype`, `items_id`), UNIQUE `unicity` (`itemtype`, `items_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"; - $this->db->queryOrDie($query, 'Error creating JAMF plugin item extension field table' . $this->db->error()); + $this->db->doQuery($query); } + if (!$this->db->tableExists('glpi_plugin_jamf_users_jssaccounts')) { $query = 'CREATE TABLE `glpi_plugin_jamf_users_jssaccounts` ( `id` int(11) NOT NULL auto_increment, @@ -300,7 +314,7 @@ private function apply_1_1_0_migration() `jssaccounts_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin jss account link table' . $this->db->error()); + $this->db->doQuery($query); } $this->glpiMigration->addConfig([ @@ -338,7 +352,7 @@ private function apply_1_1_1_migration() private function apply_1_1_2_migration() { - $this->db->updateOrDie('glpi_crontasks', [ + $this->db->update('glpi_crontasks', [ 'allowmode' => 3, ], [ 'itemtype' => 'PluginJamfSync', @@ -357,6 +371,7 @@ private function apply_2_0_0_migration() 'name' => ['itemtype_iphone', 'itemtype_ipad', 'itemtype_appletv'], ]); } + if (isset($config['itemtype_iphone'])) { $this->db->delete(Config::getTable(), [ 'context' => 'plugin:Jamf', @@ -368,9 +383,11 @@ private function apply_2_0_0_migration() if (!isset($config['default_status'])) { $this->glpiMigration->addConfig(['default_status', null], 'plugin:Jamf'); } + if (!isset($config['sync_components'])) { $this->glpiMigration->addConfig(['sync_components', 0], 'plugin:Jamf'); } + if (!isset($config['jssignorecert'])) { $this->glpiMigration->addConfig(['jssignorecert', 0], 'plugin:Jamf'); } @@ -387,6 +404,7 @@ public function apply_2_1_0_migration() if ($this->db->fieldExists('glpi_plugin_jamf_extensionattributes', 'itemtype')) { $this->glpiMigration->dropField('glpi_plugin_jamf_extensionattributes', 'itemtype'); } + $this->glpiMigration->addKey('glpi_plugin_jamf_extensionattributes', ['jamf_type', 'jamf_id'], 'unicity', 'UNIQUE'); $this->glpiMigration->migrationOneTable('glpi_plugin_jamf_extensionattributes'); } @@ -419,7 +437,7 @@ public function apply_2_1_0_migration() UNIQUE KEY `unicity` (`itemtype`, `items_id`), KEY `udid` (`udid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"; - $this->db->queryOrDie($query, 'Error creating JAMF plugin devices table' . $this->db->error()); + $this->db->doQuery($query); $common_fields = ['jamf_items_id', 'items_id', 'itemtype', 'udid', 'last_inventory', 'entry_date', 'enroll_date', 'import_date', 'sync_date', 'managed', 'supervised', 'activation_lock_enabled']; $all_mobiledevices = getAllDataFromTable('glpi_plugin_jamf_mobiledevices'); @@ -430,6 +448,7 @@ public function apply_2_1_0_migration() foreach ($common_fields as $cf) { $field_map[$cf] = $mobiledevice[$cf]; } + $this->db->insert('glpi_plugin_jamf_devices', $field_map); $this->db->update('glpi_plugin_jamf_mobiledevices', ['glpi_plugin_jamf_devices_id' => $this->db->insertId()], ['id' => $mobiledevice['id']]); } @@ -437,6 +456,7 @@ public function apply_2_1_0_migration() foreach ($common_fields as $cf) { $this->glpiMigration->dropField('glpi_plugin_jamf_mobiledevices', $cf); } + $this->glpiMigration->migrationOneTable('glpi_plugin_jamf_mobiledevices'); } @@ -448,7 +468,7 @@ public function apply_2_1_0_migration() PRIMARY KEY (`id`), UNIQUE KEY `glpi_plugin_jamf_devices_id` (`glpi_plugin_jamf_devices_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin computers table' . $this->db->error()); + $this->db->doQuery($query); } // Convert old software table to mobile device software table @@ -464,7 +484,7 @@ public function apply_2_1_0_migration() `itunes_store_url` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'; - $this->db->queryOrDie($query, 'Error creating JAMF plugin computer software table' . $this->db->error()); + $this->db->doQuery($query); } $old_cron = $this->db->request([ @@ -503,7 +523,8 @@ public function apply_2_1_3_migration() foreach ($broken_msoftware_links as $data) { $m_ids[] = $data['id']; } - if (count($m_ids)) { + + if ($m_ids !== []) { $this->db->delete('glpi_plugin_jamf_mobiledevicesoftwares', [ 'id' => $m_ids, ]); @@ -528,7 +549,8 @@ public function apply_2_1_3_migration() foreach ($broken_csoftware_links as $data) { $c_ids[] = $data['id']; } - if (count($c_ids)) { + + if ($c_ids !== []) { $this->db->delete('glpi_plugin_jamf_computersoftwares', [ 'id' => $c_ids, ]); @@ -538,64 +560,64 @@ public function apply_2_1_3_migration() public function apply_3_0_0_migration(): void { - if ($this->db->tableExists('glpi_plugin_jamf_devices')) { - if (!$this->db->fieldExists('glpi_plugin_jamf_devices', 'model_identifier')) { - // Add model_identifier column (Needed specifically for some MDM commands like ScheduleOSUpdate to know applicable updates) - $this->glpiMigration->addField('glpi_plugin_jamf_devices', 'model_identifier', 'string'); - $this->glpiMigration->migrationOneTable('glpi_plugin_jamf_devices'); - // Fill model_identifier column for all existing devices (Need to query JSS) - $this->glpiMigration->log('Updating model_identifier for all existing devices. This may take a while...', false); - $devices = $this->db->request([ - 'SELECT' => ['id', 'jamf_type', 'jamf_items_id'], - 'FROM' => 'glpi_plugin_jamf_devices', - ]); + if ($this->db->tableExists('glpi_plugin_jamf_devices') && !$this->db->fieldExists('glpi_plugin_jamf_devices', 'model_identifier')) { + // Add model_identifier column (Needed specifically for some MDM commands like ScheduleOSUpdate to know applicable updates) + $this->glpiMigration->addField('glpi_plugin_jamf_devices', 'model_identifier', 'string'); + $this->glpiMigration->migrationOneTable('glpi_plugin_jamf_devices'); + // Fill model_identifier column for all existing devices (Need to query JSS) + $this->glpiMigration->log('Updating model_identifier for all existing devices. This may take a while...', false); + $devices = $this->db->request([ + 'SELECT' => ['id', 'jamf_type', 'jamf_items_id'], + 'FROM' => 'glpi_plugin_jamf_devices', + ]); + $jss_mobiledevices = count($devices) > 0 ? $this->api::getAllMobileDevices() : []; - if (count($devices)) { - $jss_mobiledevices = $this->api::getAllMobileDevices(); - } else { - $jss_mobiledevices = []; - } - foreach ($devices as $device) { - if ($device['jamf_type'] === 'MobileDevice') { - // We can get the model identifier directly from the list of mobile devices - foreach ($jss_mobiledevices as $jss_mobile) { - if ((int) $jss_mobile['id'] === $device['jamf_items_id']) { - $this->db->update( - 'glpi_plugin_jamf_devices', - [ - 'model_identifier' => $jss_mobile['modelIdentifier'], - ], - [ - 'id' => $device['id'], - ], - ); - break; - } - } - } elseif ($device['jamf_type'] === 'Computer') { - // We need to query the JSS for the computer's model identifier - $computer = $this->api::getComputerByID($device['jamf_items_id'], true); - if ($computer !== null) { + foreach ($devices as $device) { + if ($device['jamf_type'] === 'MobileDevice') { + // We can get the model identifier directly from the list of mobile devices + foreach ($jss_mobiledevices as $jss_mobile) { + if ((int) $jss_mobile['id'] === $device['jamf_items_id']) { $this->db->update( 'glpi_plugin_jamf_devices', [ - 'model_identifier' => $computer['hardware']['modelIdentifier'], + 'model_identifier' => $jss_mobile['modelIdentifier'], ], [ 'id' => $device['id'], ], ); + break; } - } else { - $this->glpiMigration->log('Found device with invalid Jamf type (ID: ' . $device['id'] . '). Ignoring.', true); } + } elseif ($device['jamf_type'] === 'Computer') { + // We need to query the JSS for the computer's model identifier + $computer = $this->api::getComputerByID($device['jamf_items_id'], true); + if ($computer !== null) { + $this->db->update( + 'glpi_plugin_jamf_devices', + [ + 'model_identifier' => $computer['hardware']['modelIdentifier'], + ], + [ + 'id' => $device['id'], + ], + ); + } + } else { + $this->glpiMigration->log('Found device with invalid Jamf type (ID: ' . $device['id'] . '). Ignoring.', true); } } } + + //create dir if needed + mkdir(GLPI_PLUGIN_DOC_DIR . '/jamf'); + // Copy default pmv from tools dir $pmv_file_path = GLPI_PLUGIN_DOC_DIR . '/jamf/pmv.json'; if (!file_exists($pmv_file_path)) { + Toolbox::logDebug($pmv_file_path); + Toolbox::logDebug(Plugin::getPhpDir('jamf') . '/tools/pmv.json'); copy(Plugin::getPhpDir('jamf') . '/tools/pmv.json', $pmv_file_path); } @@ -609,7 +631,7 @@ public function apply_3_0_0_migration(): void public function apply_3_0_1_migration(): void { // Change udid column in glpi_plugin_jamf_imports to allow NULL values - $this->db->queryOrDie('ALTER TABLE `glpi_plugin_jamf_imports` MODIFY `udid` VARCHAR(100) NULL DEFAULT NULL'); + $this->db->doQuery('ALTER TABLE `glpi_plugin_jamf_imports` MODIFY `udid` VARCHAR(100) NULL DEFAULT NULL'); } public function apply_3_1_1_migration(): void diff --git a/inc/mobiledevice.class.php b/inc/mobiledevice.class.php index a9cbfe1..de980f9 100644 --- a/inc/mobiledevice.class.php +++ b/inc/mobiledevice.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -38,7 +38,9 @@ class PluginJamfMobileDevice extends PluginJamfAbstractDevice { public static $itemtype = 'itemtype'; + public static $items_id = 'items_id'; + public static $rightname = 'plugin_jamf_mobiledevice'; public static function getTypeName($nb = 1) @@ -62,16 +64,16 @@ public static function getJamfDeviceURL(int $jamf_id): string { $config = PluginJamfConfig::getConfig(); - return "{$config['jssserver']}/mobileDevices.html?id={$jamf_id}"; + return sprintf('%s/mobileDevices.html?id=%d', $config['jssserver'], $jamf_id); } /** * Cleanup relations when an item is purged. - * @param CommonDBTM $item * @global CommonDBTM $DB */ private static function purgeItemCommon(CommonDBTM $item) { + /** @var DBmysql $DB */ global $DB; $DB->delete(self::getTable(), [ @@ -82,7 +84,6 @@ private static function purgeItemCommon(CommonDBTM $item) /** * Cleanup relations when a Computer is purged. - * @param Computer $item */ public static function plugin_jamf_purgeComputer(Computer $item) { @@ -91,11 +92,11 @@ public static function plugin_jamf_purgeComputer(Computer $item) /** * Cleanup relations when a Phone is purged. - * @param Phone $item * @global DBmysql $DB */ public static function plugin_jamf_purgePhone(Phone $item) { + /** @var DBmysql $DB */ global $DB; self::purgeItemCommon($item); @@ -136,6 +137,11 @@ public function getGLPIItem() { $device_data = $this->getJamfDeviceData(); $itemtype = $device_data['itemtype']; + + if (!is_a($itemtype, CommonDBTM::class, true)) { + throw new InvalidArgumentException('Invalid item type: ' . $itemtype); + } + $item = new $itemtype(); $item->getFromDB($device_data['items_id']); @@ -161,21 +167,22 @@ public function getSpecificType() { $item = $this->getGLPIItem(); $modelclass = $this->getJamfDeviceData()['itemtype'] . 'Model'; + + if (!is_a($modelclass, CommonDBTM::class, true)) { + throw new InvalidArgumentException('Invalid model class: ' . $modelclass); + } + if ($item->fields[getForeignKeyFieldForItemType($modelclass)] > 0) { /** @var CommonDropdown $model */ $model = new $modelclass(); $model->getFromDB($item->fields[getForeignKeyFieldForItemType($modelclass)]); $modelname = $model->fields['name']; - switch ($modelname) { - case strpos($modelname, 'iPad') !== false: - return 'ipad'; - case strpos($modelname, 'iPhone') !== false: - return 'iphone'; - case strpos($modelname, 'Apple TV') !== false: - return 'appletv'; - default: - return null; - } + return match (true) { + str_contains((string) $modelname, 'iPad') => 'ipad', + str_contains((string) $modelname, 'iPhone') => 'iphone', + str_contains((string) $modelname, 'Apple TV') => 'appletv', + default => null, + }; } return null; @@ -183,29 +190,24 @@ public function getSpecificType() public static function dashboardCards() { - $cards = []; - - $cards['plugin_jamf_mobile_lost'] = [ + return ['plugin_jamf_mobile_lost' => [ 'widgettype' => ['bigNumber'], 'label' => _x('dashboard', 'Jamf Lost Mobile Device Count', 'jamf'), 'provider' => 'PluginJamfMobileDevice::cardLostModeProvider', - ]; - $cards['plugin_jamf_mobile_managed'] = [ + ], 'plugin_jamf_mobile_managed' => [ 'widgettype' => ['bigNumber'], 'label' => _x('dashboard', 'Jamf Managed Mobile Device Count', 'jamf'), 'provider' => 'PluginJamfMobileDevice::cardManagedProvider', - ]; - $cards['plugin_jamf_mobile_supervised'] = [ + ], 'plugin_jamf_mobile_supervised' => [ 'widgettype' => ['bigNumber'], 'label' => _x('dashboard', 'Jamf Supervised Mobile Device Count', 'jamf'), 'provider' => 'PluginJamfMobileDevice::cardSupervisedProvider', - ]; - - return $cards; + ]]; } public static function cardLostModeProvider($params = []) { + /** @var DBmysql $DB */ global $DB; $table = self::getTable(); @@ -225,6 +227,7 @@ public static function cardLostModeProvider($params = []) public static function cardManagedProvider($params = []) { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ @@ -243,6 +246,7 @@ public static function cardManagedProvider($params = []) public static function cardSupervisedProvider($params = []) { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ @@ -261,28 +265,30 @@ public static function cardSupervisedProvider($params = []) public static function showForItem(array $params) { + /** @var array $CFG_GLPI */ + global $CFG_GLPI; + /** @var CommonDBTM $item */ $item = $params['item']; - if (!self::canView() || (!($item::getType() === 'Computer') && !($item::getType() === 'Phone'))) { + if (!self::canView() || ($item::getType() !== 'Computer' && $item::getType() !== 'Phone')) { return false; } - $getYesNo = static function ($value) { - return $value ? __('Yes') : __('No'); - }; + $getYesNo = (static fn($value) => $value ? __('Yes') : __('No')); $jamf_item = static::getJamfItemForGLPIItem($item); - if ($jamf_item === null) { + if (!$jamf_item instanceof PluginJamfAbstractDevice) { return false; } + $match = $jamf_item->fields; $match = array_merge($match, $jamf_item->getJamfDeviceData()); $js = ''; if ($item->canUpdate()) { - $ajax_url = Plugin::getWebDir('jamf') . '/ajax/sync.php'; + $ajax_url = $CFG_GLPI['root_doc'] . '/plugins/jamf/ajax/sync.php'; $js = << [ 'caption' => _x('form_section', 'Jamf General Information', 'jamf'), @@ -349,7 +356,7 @@ function syncDevice(itemtype, items_id) { ], 'sync' => [ 'caption' => _x('action', 'Sync now', 'jamf'), - 'on_click' => "syncDevice(\"{$item::getType()}\", {$item->getID()}); return false;", + 'on_click' => sprintf('syncDevice("%s", %d); return false;', $item::getType(), $item->getID()), ], ], 'extra_js' => $js, @@ -394,7 +401,7 @@ function syncDevice(itemtype, items_id) { ], 'lost_location' => [ 'caption' => _x('field', 'GPS', 'jamf'), - 'value' => Html::link("$lat, $long", "https://www.google.com/maps/place/$lat,$long", [ + 'value' => Html::link(sprintf('%s, %s', $lat, $long), sprintf('https://www.google.com/maps/place/%s,%s', $lat, $long), [ 'display' => false, ]), ], @@ -416,5 +423,6 @@ function syncDevice(itemtype, items_id) { TemplateRenderer::getInstance()->display('@jamf/inventory_info.html.twig', [ 'info' => $info, ]); + return null; } } diff --git a/inc/mobiledevicesoftware.class.php b/inc/mobiledevicesoftware.class.php index a7c53b1..fef5c55 100644 --- a/inc/mobiledevicesoftware.class.php +++ b/inc/mobiledevicesoftware.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf diff --git a/inc/mobilesync.class.php b/inc/mobilesync.class.php index bea9dd6..b91cce5 100644 --- a/inc/mobilesync.class.php +++ b/inc/mobilesync.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -30,10 +30,12 @@ */ use Glpi\Event; +use Safe\DateTime; class PluginJamfMobileSync extends PluginJamfDeviceSync { protected static $jamfplugin_itemtype = 'PluginJamfMobileDevice'; + protected static $jamf_itemtype = 'MobileDevice'; protected function syncGeneral(): PluginJamfDeviceSync @@ -43,6 +45,7 @@ protected function syncGeneral(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; $itemtype = $this->item::getType(); @@ -50,6 +53,7 @@ protected function syncGeneral(): PluginJamfDeviceSync if (($general['name'] !== $this->item->fields['name'])) { $this->item_changes['name'] = $general['name']; } + $other_general_items = [ 'asset_tag' => 'otherserial', 'serial_number' => 'serial', @@ -59,6 +63,7 @@ protected function syncGeneral(): PluginJamfDeviceSync } else { $this->extitem_changes['uuid'] = $general['udid']; } + foreach ($other_general_items as $jamf_field => $item_field) { if ($general[$jamf_field] !== $this->item->fields[$item_field]) { $this->item_changes[$item_field] = $this->db->escape($general[$jamf_field]); @@ -66,11 +71,7 @@ protected function syncGeneral(): PluginJamfDeviceSync } // Create or match model - if ($itemtype === 'Phone') { - $model_type = 'PhoneModel'; - } else { - $model_type = 'ComputerModel'; - } + $model_type = $itemtype === 'Phone' ? 'PhoneModel' : 'ComputerModel'; $model = $this->applyDesiredState($model_type, [ 'name' => $general['model'], @@ -95,11 +96,12 @@ protected function syncGeneral(): PluginJamfDeviceSync $this->item_changes['phonetypes_id'] = $preferred_type; } } else { - if (strpos($general['model'], 'Apple TV') === false) { + if (!str_contains((string) $general['model'], 'Apple TV')) { $preferred_type = $this->config['ipad_type']; } else { $preferred_type = $this->config['appletv_type']; } + if ($preferred_type) { $this->item_changes['computertypes_id'] = $preferred_type; } @@ -114,11 +116,12 @@ protected function syncGeneral(): PluginJamfDeviceSync if ($this->item === null || $this->item->fields['states_id'] === 0) { $this->item_changes['states_id'] = $this->config['default_status']; } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncGeneral'] = self::STATUS_ERROR; return $this; } + $this->status['syncGeneral'] = self::STATUS_OK; return $this; @@ -131,6 +134,7 @@ protected function syncOS(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; $os = $this->applyDesiredState('OperatingSystem', [ @@ -155,11 +159,12 @@ protected function syncOS(): PluginJamfDeviceSync 'itemtype' => $this->item::getType(), 'items_id' => $this->item->getID(), ]); - } catch (Exception $e) { + } catch (Exception) { $this->status['syncOS'] = self::STATUS_ERROR; return $this; } + $this->status['syncOS'] = self::STATUS_OK; return $this; @@ -167,6 +172,7 @@ protected function syncOS(): PluginJamfDeviceSync protected function syncSoftware(): PluginJamfDeviceSync { + /** @var DBmysql $DB */ global $DB; if (!$this->config['sync_software'] || $this->item === null || !isset($this->data['applications'])) { @@ -174,6 +180,7 @@ protected function syncSoftware(): PluginJamfDeviceSync return $this; } + try { $applications = $this->data['applications']; $software = new Software(); @@ -191,6 +198,7 @@ protected function syncSoftware(): PluginJamfDeviceSync if (is_null($software_data) || !isset($software_data['general'])) { continue; } + $software_matches = $software->find(['name' => $software_data['general']['name']]); if (!count($software_matches)) { $software_id = $software->add([ @@ -202,6 +210,7 @@ protected function syncSoftware(): PluginJamfDeviceSync } else { $software_id = reset($software_matches)['id']; } + $jamf_software->add([ 'softwares_id' => $software_id, 'bundle_id' => $application['identifier'], @@ -210,6 +219,7 @@ protected function syncSoftware(): PluginJamfDeviceSync } else { $software_id = array_values($jamfsoftware_matches)[0]['softwares_id']; } + $softwareversion_matches = $softwareversion->find([ 'softwares_id' => $software_id, 'name' => $application['application_version'], @@ -225,9 +235,11 @@ protected function syncSoftware(): PluginJamfDeviceSync } else { $softwareversion_id = array_keys($softwareversion_matches)[0]; } + if (!$softwareversion_id) { continue; } + $item_softwareversion = new Item_SoftwareVersion(); $item_softwareversion_matches = $item_softwareversion->find([ 'itemtype' => $this->item::getType(), @@ -249,9 +261,7 @@ protected function syncSoftware(): PluginJamfDeviceSync $installed_bundles = array_column($applications, 'identifier'); $inventoried_software = PluginJamfMobileDeviceSoftware::getForGlpiItem($this->item); // We don't need to worry about versions because Apple mobile devices cannot have multiple installs for the same bundle id, so no multiple version support - $to_remove_software = array_filter($inventoried_software, static function ($software) use ($installed_bundles) { - return !in_array($software['bundle_id'], $installed_bundles, true); - }); + $to_remove_software = array_filter($inventoried_software, static fn($software) => !in_array($software['bundle_id'], $installed_bundles, true)); foreach ($to_remove_software as $to_remove) { $DB->delete(Item_SoftwareVersion::getTable(), [ SoftwareVersion::getTable() . '.softwares_id' => $to_remove['softwares_id'], @@ -274,11 +284,12 @@ protected function syncSoftware(): PluginJamfDeviceSync ], ]); } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncSoftware'] = self::STATUS_ERROR; return $this; } + $this->status['syncSoftware'] = self::STATUS_OK; return $this; @@ -291,6 +302,7 @@ protected function syncUser(): PluginJamfDeviceSync return $this; } + try { $location = $this->data['location']; $user = new User(); @@ -299,11 +311,12 @@ protected function syncUser(): PluginJamfDeviceSync $user_match = reset($user_match); $this->item_changes['users_id'] = $user_match['id']; } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncUser'] = self::STATUS_ERROR; return $this; } + $this->status['syncUser'] = self::STATUS_OK; return $this; @@ -316,6 +329,7 @@ protected function syncPurchasing(): PluginJamfDeviceSync return $this; } + try { $purchasing = $this->data['purchasing']; $infocom_changes = []; @@ -330,9 +344,11 @@ protected function syncPurchasing(): PluginJamfDeviceSync $infocom_changes['warranty_duration'] = $warranty_length; } } + if (!empty($purchasing['applecare_id'])) { - $infocom_changes['warranty_info'] = "AppleCare ID: {$purchasing['applecare_id']}"; + $infocom_changes['warranty_info'] = 'AppleCare ID: ' . $purchasing['applecare_id']; } + if (!empty($purchasing['po_number'])) { $infocom_changes['order_number'] = $purchasing['po_number']; } @@ -341,11 +357,12 @@ protected function syncPurchasing(): PluginJamfDeviceSync 'itemtype' => $this->item::getType(), 'items_id' => $this->item->getID(), ]); - } catch (Exception $e) { + } catch (Exception) { $this->status['syncPurchasing'] = self::STATUS_ERROR; return $this; } + $this->status['syncPurchasing'] = self::STATUS_OK; return $this; @@ -362,6 +379,7 @@ protected function syncExtensionAttributes(): PluginJamfDeviceSync return $this; } + try { $extension_attributes = $this->data['extension_attributes']; $ext_attribute = new PluginJamfExtensionAttribute(); @@ -381,11 +399,12 @@ protected function syncExtensionAttributes(): PluginJamfDeviceSync ]); } } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncExtensionAttributes'] = self::STATUS_ERROR; return $this; } + $this->status['syncExtensionAttributes'] = self::STATUS_OK; return $this; @@ -398,16 +417,19 @@ protected function syncSecurity(): PluginJamfDeviceSync return $this; } + try { $security = $this->data['security']; if (!empty($security['lost_mode_enable_issued_utc'])) { $lost_mode_enable_date = PluginJamfToolbox::utcToLocal($security['lost_mode_enable_issued_utc'], 0); $this->jamfplugin_item_changes['lost_mode_enable_date'] = $lost_mode_enable_date; } + if (!empty($security['lost_location_utc'])) { $lost_location_date = PluginJamfToolbox::utcToLocal($security['lost_location_utc'], 0); $this->jamfplugin_item_changes['lost_location_date'] = $lost_location_date; } + $this->commondevice_changes['activation_lock_enabled'] = $security['activation_lock_enabled']; $this->jamfplugin_item_changes['lost_mode_enabled'] = $security['lost_mode_enabled']; $this->jamfplugin_item_changes['lost_mode_enforced'] = $security['lost_mode_enforced']; @@ -417,11 +439,12 @@ protected function syncSecurity(): PluginJamfDeviceSync $this->jamfplugin_item_changes['lost_location_longitude'] = $security['lost_location_longitude']; $this->jamfplugin_item_changes['lost_location_altitude'] = $security['lost_location_altitude']; $this->jamfplugin_item_changes['lost_location_speed'] = $security['lost_location_speed']; - } catch (Exception $e) { + } catch (Exception) { $this->status['syncSecurity'] = self::STATUS_ERROR; return $this; } + $this->status['syncSecurity'] = self::STATUS_OK; return $this; @@ -434,9 +457,10 @@ protected function syncNetwork(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; - $expected_netcard_name = "Generic {$general['model']} Network Card"; + $expected_netcard_name = sprintf('Generic %s Network Card', $general['model']); if (isset($general['wifi_mac_address']) && !empty($general['wifi_mac_address'])) { $wifi_model = $this->createOrGetItem('DeviceNetworkCardModel', ['name' => $expected_netcard_name], [ @@ -558,11 +582,12 @@ protected function syncNetwork(): PluginJamfDeviceSync 'mac' => $general['bluetooth_mac_address'], ]); } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncNetwork'] = self::STATUS_ERROR; return $this; } + $this->status['syncNetwork'] = self::STATUS_OK; return $this; @@ -575,16 +600,19 @@ protected function syncGeneralJamfPluginItem(): PluginJamfDeviceSync return $this; } + try { $general = $this->data['general']; if (!empty($general['last_inventory_update_utc'])) { $last_inventory = PluginJamfToolbox::utcToLocal($general['last_inventory_update_utc'], 0); $this->commondevice_changes['last_inventory'] = $last_inventory; } + if (!empty($general['initial_entry_date_utc'])) { $entry_date = PluginJamfToolbox::utcToLocal($general['initial_entry_date_utc'], 0); $this->commondevice_changes['entry_date'] = $entry_date; } + if (!empty($general['last_enrollment_utc'])) { $enroll_date = PluginJamfToolbox::utcToLocal($general['last_enrollment_utc'], 0); $this->commondevice_changes['enroll_date'] = $enroll_date; @@ -598,11 +626,12 @@ protected function syncGeneralJamfPluginItem(): PluginJamfDeviceSync $this->commondevice_changes['supervised'] = $general['supervised']; $this->jamfplugin_item_changes['shared'] = $general['shared']; $this->jamfplugin_item_changes['cloud_backup_enabled'] = $general['cloud_backup_enabled']; - } catch (Exception $e) { + } catch (Exception) { $this->status['syncGeneralJamfPluginItem'] = self::STATUS_ERROR; return $this; } + $this->status['syncGeneralJamfPluginItem'] = self::STATUS_OK; return $this; @@ -615,6 +644,7 @@ protected function syncComponents(): PluginJamfDeviceSync return $this; } + try { $config = PluginJamfConfig::getConfig(); $general = $this->data['general']; @@ -665,18 +695,20 @@ protected function syncComponents(): PluginJamfDeviceSync 'lines_id' => $line->getID(), ]); } - } catch (Exception $e) { + } catch (Exception) { $this->status['syncComponents'] = self::STATUS_ERROR; return $this; } + $this->status['syncComponents'] = self::STATUS_OK; return $this; } - public static function discover(): bool + public static function discover(): int { + /** @var DBmysql $DB */ global $DB; $volume = 0; @@ -685,6 +717,7 @@ public static function discover(): bool // API error or device no longer exists in Jamf return -1; } + $imported = []; $iterator = $DB->request([ 'SELECT' => ['udid'], @@ -694,6 +727,7 @@ public static function discover(): bool foreach ($iterator as $data) { $imported[] = $data['udid']; } + $pending_iterator = $DB->request([ 'SELECT' => ['jamf_items_id'], 'FROM' => 'glpi_plugin_jamf_imports', @@ -709,27 +743,25 @@ public static function discover(): bool $config = Config::getConfigurationValues('plugin:Jamf'); foreach ($jamf_devices as $jamf_device) { if (!in_array($jamf_device['id'], $imported, true)) { - $itemtype = strpos($jamf_device['model_identifier'], 'iPhone') !== false ? 'Phone' : 'Computer'; + $itemtype = str_contains((string) $jamf_device['model_identifier'], 'iPhone') ? 'Phone' : 'Computer'; if (isset($config['autoimport']) && $config['autoimport']) { try { $result = self::import($itemtype, $jamf_device['id']); if ($result) { $volume++; } - } catch (Exception $e2) { + } catch (Exception) { // Some other error } - } else { - if (!in_array((int) $jamf_device['id'], $pending_import, true)) { - $DB->insert('glpi_plugin_jamf_imports', [ - 'jamf_type' => 'MobileDevice', - 'jamf_items_id' => $jamf_device['id'], - 'name' => $DB->escape($jamf_device['name']), - 'type' => $itemtype, - 'udid' => $jamf_device['udid'], - 'date_discover' => $_SESSION['glpi_currenttime'], - ]); - } + } elseif (!in_array((int) $jamf_device['id'], $pending_import, true)) { + $DB->insert('glpi_plugin_jamf_imports', [ + 'jamf_type' => 'MobileDevice', + 'jamf_items_id' => $jamf_device['id'], + 'name' => $DB->escape($jamf_device['name']), + 'type' => $itemtype, + 'udid' => $jamf_device['udid'], + 'date_discover' => $_SESSION['glpi_currenttime'], + ]); } } } @@ -739,12 +771,19 @@ public static function discover(): bool public static function import(string $itemtype, int $jamf_items_id, $use_transaction = true): bool { + /** @var DBmysql $DB */ global $DB; if (!self::isSupportedGlpiItemtype($itemtype)) { // Invalid itemtype for a mobile device return false; } + + if (!is_a($itemtype, CommonDBTM::class, true)) { + Toolbox::logDebug(sprintf('Jamf import error: %s is not a valid GLPI itemtype.', $itemtype)); + return false; + } + $item = new $itemtype(); $jamf_item = static::$api::getItemsClassic('mobiledevices', ['id' => $jamf_items_id]); @@ -801,9 +840,10 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac 'LIMIT' => 1, ]); } + if ($iterator->count()) { // Already imported - Event::log(0, $itemtype, 4, 'Jamf plugin', "Jamf mobile device $jamf_items_id not imported. A {$itemtype::getTypeName(1)} exists with the same uuid."); + Event::log(0, $itemtype, 4, 'Jamf plugin', sprintf('Jamf mobile device %d not imported. A %s exists with the same uuid.', $jamf_items_id, $itemtype::getTypeName(1))); return false; } @@ -836,6 +876,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac return false; } + $device_id = $DB->insertId(); $jamf_mobiledevice = new PluginJamfMobileDevice(); $r2 = $jamf_mobiledevice->add([ @@ -848,6 +889,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac return false; } + if (self::sync($itemtype, $items_id, false)) { $DB->update('glpi_plugin_jamf_devices', [ 'import_date' => $_SESSION['glpi_currenttime'], @@ -859,10 +901,8 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac if ($use_transaction) { $DB->commit(); } - } else { - if ($use_transaction) { - $DB->rollBack(); - } + } elseif ($use_transaction) { + $DB->rollBack(); } } else { if ($use_transaction) { @@ -877,6 +917,7 @@ public static function import(string $itemtype, int $jamf_items_id, $use_transac protected static function getJamfDataForSyncingByGlpiItem(string $itemtype, int $items_id): array { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ @@ -888,9 +929,10 @@ protected static function getJamfDataForSyncingByGlpiItem(string $itemtype, int ], ]); - if (!count($iterator)) { + if (count($iterator) === 0) { return []; } + $jamf_item = $iterator->current(); return static::$api::getItemsClassic('mobiledevices', ['id' => $jamf_item['jamf_items_id']]) ?? []; diff --git a/inc/profile.class.php b/inc/profile.class.php index c14a102..f468e12 100644 --- a/inc/profile.class.php +++ b/inc/profile.class.php @@ -22,17 +22,13 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -if (!defined('GLPI_ROOT')) { - die("Sorry. You can't access this file directly"); -} - /** * PluginJamfProfile class. Adds plugin related rights tab to Profiles. * @since 1.0.0 @@ -48,38 +44,30 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { - $jamfprofile = new self(); - if ($item->fields['interface'] == 'central') { - $jamfprofile->showForm($item->getID()); - } else { - $jamfprofile->showFormHelpdesk($item->getID()); + if ($item instanceof Profile) { + $jamfprofile = new self(); + if ($item->fields['interface'] == 'central') { + $jamfprofile->showForm($item->getID()); + } else { + $jamfprofile->showFormHelpdesk($item->getID()); + } } return true; } - /** - * Print the Jamf plugin right form for the current profile - * - * @param int $profiles_id Current profile ID - * @param bool $openform Open the form (true by default) - * @param bool $closeform Close the form (true by default) - * - * @return bool|void - */ - public function showForm($profiles_id = 0, $openform = true, $closeform = true) + public function showForm($ID, array $options = []) { - global $CFG_GLPI; - if (!self::canView()) { return false; } echo "
"; $profile = new Profile(); - $profile->getFromDB($profiles_id); + $profile->getFromDB($ID); + $canedit = Session::haveRightsOr(self::$rightname, [CREATE, UPDATE, PURGE]); - if ($openform && $canedit) { + if ($canedit) { echo "
"; } @@ -113,30 +101,27 @@ public function showForm($profiles_id = 0, $openform = true, $closeform = true) $matrix_options['title'] = _x('plugin_info', 'Jamf plugin', 'jamf'); $profile->displayRightsChoiceMatrix($rights, $matrix_options); - if ($canedit - && $closeform) { + if ($canedit) { echo "
"; - echo Html::hidden('id', ['value' => $profiles_id]); + echo Html::hidden('id', ['value' => $ID]); echo Html::submit(_sx('button', 'Save'), ['name' => 'update']); echo "
\n"; Html::closeForm(); } + echo '
'; + return true; } /** * Print the Jamf plugin helpdesk right form for the current profile * * @param int $profiles_id Current profile ID - * @param bool $openform Open the form (true by default) - * @param bool $closeform Close the form (true by default) * - * @return bool|void + * @return bool */ - public function showFormHelpdesk($profiles_id = 0, $openform = true, $closeform = true) + public function showFormHelpdesk($profiles_id = 0) { - global $CFG_GLPI; - if (!self::canView()) { return false; } @@ -169,6 +154,8 @@ public function showFormHelpdesk($profiles_id = 0, $openform = true, $closeform } else { echo "\n"; } + echo ''; + return true; } } diff --git a/inc/ruleimport.class.php b/inc/ruleimport.class.php index 4bd7b96..416e33f 100644 --- a/inc/ruleimport.class.php +++ b/inc/ruleimport.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -37,6 +37,7 @@ class PluginJamfRuleImport extends Rule { public static $rightname = 'plugin_jamf_ruleimport'; + public $can_sort = true; public function getTitle() @@ -89,16 +90,14 @@ public function getActions() public function displayAdditionalRuleCondition($condition, $crit, $name, $value, $test = false) { - if (isset($crit['field'])) { - switch ($crit['field']) { - case 'itemtype': - Dropdown::showFromArray($name, [ - Computer::getType() => Computer::getTypeName(1), - Phone::getType() => Phone::getTypeName(1), - ]); - - return true; - } + if (isset($crit['field']) && $crit['field'] === 'itemtype') { + Dropdown::showFromArray($name, [ + Computer::getType() => Computer::getTypeName(1), + Phone::getType() => Phone::getTypeName(1), + ]); + return true; } + + return false; } } diff --git a/inc/ruleimportcollection.class.php b/inc/ruleimportcollection.class.php index bf77b99..6ae8393 100644 --- a/inc/ruleimportcollection.class.php +++ b/inc/ruleimportcollection.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -35,7 +35,9 @@ class PluginJamfRuleImportCollection extends RuleCollection { public $stop_on_first_match = true; + public static $rightname = 'plugin_jamf_ruleimport'; + public $menu_option = 'jamf_import'; public function getTitle() diff --git a/inc/software.class.php b/inc/software.class.php index 86bddaa..bda1f7a 100644 --- a/inc/software.class.php +++ b/inc/software.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -42,11 +42,11 @@ public static function getTypeName($nb = 0) /** * Cleanup relations when an item is purged. - * @param Software $item * @global DBmysql $DB */ public static function plugin_jamf_purgeSoftware(Software $item) { + /** @var DBmysql $DB */ global $DB; $software_classes = [PluginJamfComputerSoftware::class, PluginJamfMobileDeviceSoftware::class]; @@ -60,6 +60,7 @@ public static function plugin_jamf_purgeSoftware(Software $item) public static function getForGlpiItem(CommonDBTM $item): array { + /** @var DBmysql $DB */ global $DB; $iterator = $DB->request([ diff --git a/inc/sync.class.php b/inc/sync.class.php index a541b8a..23f5352 100644 --- a/inc/sync.class.php +++ b/inc/sync.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -80,17 +80,16 @@ abstract class PluginJamfSync protected $data = []; /** @var CommonDBTM */ - protected $item = null; + protected $item; /** @var CommonDBTM */ - protected $jamfplugin_device = null; + protected $jamfplugin_device; /** - * @var null * @since 1.0.0 * @since 2.0.0 Renamed jamf_itemtype to jamfplugin_itemtype */ - protected static $jamfplugin_itemtype = null; + protected static $jamfplugin_itemtype; /** * Textual identifier of the itemtype in Jamf that this sync engine works with. @@ -103,7 +102,7 @@ abstract class PluginJamfSync * @var string * @since 2.0.0 */ - protected static $jamf_itemtype = null; + protected static $jamf_itemtype; protected $status = []; @@ -113,26 +112,25 @@ abstract class PluginJamfSync protected $db; /** - * @var PluginJamfAPI + * @var class-string */ protected static $api = PluginJamfAPI::class; /** * PluginJamfSync constructor. - * @param CommonDBTM|null $item - * @param array $data */ - final public function __construct(CommonDBTM $item = null, array $data = []) + final public function __construct(?CommonDBTM $item = null, array $data = []) { /** @global DBmysql */ global $DB; $this->db = $DB; - if ($item === null) { + if (!$item instanceof CommonDBTM) { $this->dummySync = true; return; } + $this->config = PluginJamfConfig::getConfig(); $this->item = $item; $this->data = $data; @@ -159,6 +157,7 @@ protected function finalizeSync() if ($this->dummySync) { return $this->status; } + $this->jamfplugin_item_changes['sync_date'] = $_SESSION['glpi_currenttime']; $this->item->update([ 'id' => $this->item->getID(), @@ -166,17 +165,22 @@ protected function finalizeSync() foreach ($this->extitem_changes as $key => $value) { PluginJamfExtField::setValue($this->item::getType(), $this->item->getID(), $key, $value); } + $this->db->updateOrInsert(static::$jamfplugin_itemtype::getTable(), $this->jamfplugin_item_changes, [ 'itemtype' => $this->item::getType(), 'items_id' => $this->item->getID(), ]); if ($this->jamfplugin_device === null) { + if (!is_a(static::$jamfplugin_itemtype, 'CommonDBTM', true)) { + throw new Exception('Jamf plugin item type class ' . static::$jamfplugin_itemtype . ' must extend CommonDBTM'); + } + $jamf_item = new static::$jamfplugin_itemtype(); $jamf_match = $jamf_item->find([ 'itemtype' => $this->item::getType(), 'items_id' => $this->item->getID()], [], 1); - if (count($jamf_match)) { + if (count($jamf_match) > 0) { $jamf_item->getFromDB(reset($jamf_match)['id']); $this->jamfplugin_device = $jamf_item; } @@ -197,9 +201,13 @@ protected function finalizeSync() protected function createOrGetItem($itemtype, $criteria, $params) { + if (!is_a($itemtype, 'CommonDBTM', true)) { + throw new Exception('Item type class ' . $itemtype . ' must extend CommonDBTM'); + } + $item = new $itemtype(); $item_matches = $item->find($criteria); - if (!count($item_matches)) { + if (count($item_matches) === 0) { $items_id = $item->add($params); $item->getFromDB($items_id); } else { @@ -211,8 +219,9 @@ protected function createOrGetItem($itemtype, $criteria, $params) protected function applyDesiredState($itemtype, $match_criteria, $state, $options = []): CommonDBTM { - $opts = []; - $opts = array_replace($opts, $options); + if (!is_a($itemtype, 'CommonDBTM', true)) { + throw new Exception('Item type class ' . $itemtype . ' must extend CommonDBTM'); + } /** @var CommonDBTM $item */ $item = new $itemtype(); @@ -231,7 +240,7 @@ protected function applyDesiredState($itemtype, $match_criteria, $state, $option return $item; } - abstract public static function discover(): bool; + abstract public static function discover(): int; abstract public static function import(string $itemtype, int $jamf_items_id, $use_transaction = true): bool; @@ -243,7 +252,6 @@ abstract public static function syncAll(): int; * It is assumed that the GLPI item's existence was already verified. This function should verify that the GLPI item is linked to a Jamf item. * @param string $itemtype GLPI item type * @param int $items_id GLPI item ID - * @return array * @since 1.0.0 */ abstract protected static function getJamfDataForSyncingByGlpiItem(string $itemtype, int $items_id): array; @@ -258,7 +266,7 @@ public static function isSupportedGlpiItemtype(string $itemtype): bool } /** - * @return PluginJamfSync[] + * @return array> Mapping of GLPI itemtypes to their corresponding Jamf sync engine classes. * @since 1.0.0 */ final public static function getDeviceSyncEngines(): array diff --git a/inc/toolbox.class.php b/inc/toolbox.class.php index 303f52e..feab646 100644 --- a/inc/toolbox.class.php +++ b/inc/toolbox.class.php @@ -22,13 +22,17 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use Safe\DateTime; + +use function Safe\date_create; + class PluginJamfToolbox { public static function getHumanReadableTimeDiff($start, $end = null) @@ -36,26 +40,33 @@ public static function getHumanReadableTimeDiff($start, $end = null) if ($start === null || $start == 'NULL') { return null; } + if ($end === null) { $end = $_SESSION['glpi_currenttime']; } + $diff = date_diff(date_create($start), date_create($end)); $text_arr = []; if ($diff->y > 0) { $text_arr[] = sprintf('%d Y', $diff->y); } + if ($diff->m > 0) { $text_arr[] = sprintf('%d M', $diff->m); } + if ($diff->d > 0) { $text_arr[] = sprintf('%d D', $diff->d); } + if ($diff->h > 0) { $text_arr[] = sprintf('%d H', $diff->h); } + if ($diff->i > 0) { $text_arr[] = sprintf('%d m', $diff->i); } + if ($diff->s > 0) { $text_arr[] = sprintf('%d s', $diff->s); } @@ -66,7 +77,6 @@ public static function getHumanReadableTimeDiff($start, $end = null) /** * Helper function to convert the UTC timestamps from JSS to a local DateTime. * @param DateTime|string|null $utc The UTC DateTime from JSS. - * @param ?int $format * @return string The local date and time. * @throws Exception */ @@ -75,14 +85,17 @@ public static function utcToLocal($utc, ?int $format = null): string if ($utc === null) { return ''; } + if (!is_a($utc, DateTime::class)) { $utc = new DateTime($utc, new DateTimeZone('UTC')); } + $mask = 'Y-m-d H:i:s'; if ($format === null) { $format = $_SESSION['glpidate_format']; } + switch ($format) { case 1: // DD-MM-YYYY $mask = 'd-m-Y H:i:s'; @@ -91,6 +104,7 @@ public static function utcToLocal($utc, ?int $format = null): string $mask = 'm-d-Y H:i:s'; break; } + $tz = new DateTimeZone($_SESSION['glpi_tz'] ?? date_default_timezone_get()); $utc->setTimezone($tz); diff --git a/inc/user_jssaccount.class.php b/inc/user_jssaccount.class.php index 11986bf..9cb94ab 100644 --- a/inc/user_jssaccount.class.php +++ b/inc/user_jssaccount.class.php @@ -22,7 +22,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -41,7 +41,9 @@ class PluginJamfUser_JSSAccount extends CommonDBChild { public static $itemtype = 'User'; + public static $items_id = 'users_id'; + public static $rightname = 'plugin_jamf_jssaccount'; public const LINK = 256; @@ -53,6 +55,7 @@ public static function getTypeName($nb = 0) public function prepareInputForUpdate($input) { + /** @var DBmysql $DB */ global $DB; if ($input['jssaccounts_id'] === 0) { $DB->delete(self::getTable(), ['id' => $this->fields['id']]); @@ -66,7 +69,7 @@ public function prepareInputForUpdate($input) public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { if (!self::canView()) { - return false; + return ""; } return self::getTypeName(1); @@ -120,10 +123,12 @@ public static function canCreateJSSItem($itemtype, $meta) return self::haveJSSRight('jss_actions', $commands[$meta]['jss_right']); } + $map = self::getItemRightMap(); if (!isset($map[$itemtype])) { return false; } + $rights = $map[$itemtype]; foreach ($rights as $right) { if (!self::haveJSSRight('jss_objects', 'Create ' . $right)) { @@ -140,6 +145,7 @@ public static function canReadJSSItem($itemtype) if (!isset($map[$itemtype])) { return false; } + $rights = $map[$itemtype]; foreach ($rights as $right) { if (!self::haveJSSRight('jss_objects', 'Read ' . $right)) { @@ -156,6 +162,7 @@ public static function canUpdateJSSItem($itemtype) if (!isset($map[$itemtype])) { return false; } + $rights = $map[$itemtype]; foreach ($rights as $right) { if (!self::haveJSSRight('jss_objects', 'Update ' . $right)) { @@ -172,6 +179,7 @@ public static function canDeleteJSSItem($itemtype) if (!isset($map[$itemtype])) { return false; } + $rights = $map[$itemtype]; foreach ($rights as $right) { if (!self::haveJSSRight('jss_objects', 'Delete ' . $right)) { @@ -202,16 +210,18 @@ public static function haveJSSRight($type, $jss_right) 'users_id' => Session::getLoginUserID(), ]); } + if (count($matches) === 0) { // No JSS account link - Toolbox::logError(_x('error', 'Attempt to use JSS user rights without a linked account', 'jamf')); + Toolbox::logDebug(_x('error', 'Attempt to use JSS user rights without a linked account', 'jamf')); return false; } + $user_jssaccount->getFromDB(reset($matches)['id']); $type_rights = $user_jssaccount->getJSSPrivileges()[$type] ?? []; if (count($type_rights) === 0) { - //Toolbox::logError("Linked JSS account has no rights of type $type"); + //Toolbox::logDebug("Linked JSS account has no rights of type $type"); return false; } @@ -224,17 +234,14 @@ public static function showForUser($item) $user_jssaccount = new self(); $mylink = $user_jssaccount->find(['users_id' => $item->getID()]); - if (count($mylink)) { - $mylink = reset($mylink); - } else { - $mylink = null; - } + $mylink = count($mylink) ? reset($mylink) : null; $allusers = PluginJamfAPI::getItemsClassic('accounts')['users']; $values = []; foreach ($allusers as $user) { $values[$user['id']] = $user['name']; } + if (!$canedit) { $values = [$mylink['jssaccounts_id'] => $values[$mylink['jssaccounts_id']]]; } @@ -258,7 +265,7 @@ public static function getJSSAccountURL($jssaccount_id) { $config = PluginJamfConfig::getConfig(); - return "{$config['jssserver']}/accounts.html?id={$jssaccount_id}"; + return sprintf('%s/accounts.html?id=%s', $config['jssserver'], $jssaccount_id); } public function getRights($interface = 'central') diff --git a/phpstan.neon b/phpstan.neon index 0a22e0c..456697d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,15 +1,22 @@ +includes: + - ../../vendor/glpi-project/phpstan-glpi/extension.neon + - ../../vendor/phpstan/phpstan-deprecation-rules/rules.neon + - ../../vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon + parameters: - parallel: - maximumNumberOfProcesses: 2 - level: 1 - bootstrapFiles: - - ../../inc/based_config.php - - ../../inc/db.function.php + level: 5 paths: - ajax - front - inc - - setup.php + - tests - hook.php - stubFiles: + - setup.php + scanDirectories: + - ../../src + - ../../tests + bootstrapFiles: - ../../stubs/glpi_constants.php + - ../../vendor/autoload.php + - setup.php + treatPhpDocTypesAsCertain: false diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..49dd805 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/js/jamf.js b/public/js/jamf.js similarity index 91% rename from js/jamf.js rename to public/js/jamf.js index 5c600be..50ac621 100644 --- a/js/jamf.js +++ b/public/js/jamf.js @@ -20,7 +20,7 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf @@ -28,7 +28,6 @@ */ /* global GLPI_PLUGINS_PATH */ -/* global CFG_GLPI */ (function () { window.JamfPlugin = function () { /** @@ -61,7 +60,7 @@ this.jamf_id = args.jamf_id; this.itemtype = args.itemtype; this.items_id = args.items_id; - this.ajax_root = args.ajax_root || CFG_GLPI.root_doc + "/" + GLPI_PLUGINS_PATH.jamf + "/ajax/"; + this.ajax_root = args.ajax_root || `${CFG_GLPI.root_doc }/plugins/myplugin/ajax/`; } }; @@ -85,7 +84,7 @@ const showMDMCommandForm = (command) => { $.ajax({ method: 'GET', - url: (this.ajax_root + "getMDMCommandForm.php"), + url: (`${this.ajax_root }getMDMCommandForm.php`), data: { command: command, jamf_id: this.jamf_id, @@ -101,10 +100,10 @@ @@ -160,7 +159,7 @@ } $.ajax({ method: 'POST', - url: (this.ajax_root + "sendMDMCommand.php"), + url: (`${this.ajax_root }sendMDMCommand.php`), data: { command: command, fields: params, @@ -168,7 +167,7 @@ itemtype: this.itemtype, items_id: this.items_id } - }).always(function () { + }).always(() => { location.reload(); }); }; diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..3c3e284 --- /dev/null +++ b/rector.php @@ -0,0 +1,59 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2024-2025 by Teclib' + * @copyright Copyright (C) 2019-2024 by Curtis Conard + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/jamf + * ------------------------------------------------------------------------- + */ + +require_once __DIR__ . '/../../src/Plugin.php'; + +use Rector\Caching\ValueObject\Storage\FileCacheStorage; +use Rector\Config\RectorConfig; +use Rector\ValueObject\PhpVersion; + +return RectorConfig::configure() + ->withPaths([ + __DIR__ . '/ajax', + __DIR__ . '/front', + __DIR__ . '/inc', + __DIR__ . '/tests', + ]) + ->withPhpVersion(PhpVersion::PHP_82) + ->withCache( + cacheDirectory: __DIR__ . '/var/rector', + cacheClass: FileCacheStorage::class, + ) + ->withRootFiles() + ->withParallel(timeoutSeconds: 300) + ->withImportNames(removeUnusedImports: true) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + ) + ->withPhpSets(php82: true) // apply PHP sets up to PHP 8.2 +; diff --git a/setup.php b/setup.php index 21e31de..3a81dfc 100644 --- a/setup.php +++ b/setup.php @@ -22,16 +22,21 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +use Glpi\Plugin\Hooks; + +use function Safe\define; +use function Safe\preg_replace; + define('PLUGIN_JAMF_VERSION', '3.1.2'); -define('PLUGIN_JAMF_MIN_GLPI', '10.0.0'); -define('PLUGIN_JAMF_MAX_GLPI', '10.1.0'); +define('PLUGIN_JAMF_MIN_GLPI', '11.0.0'); +define('PLUGIN_JAMF_MAX_GLPI', '11.1.0'); function plugin_init_jamf() { @@ -44,7 +49,7 @@ function plugin_init_jamf() Plugin::registerClass('PluginJamfConfig', ['addtabon' => 'Config']); $PLUGIN_HOOKS['post_item_form']['jamf'] = 'plugin_jamf_showJamfInfoForItem'; $PLUGIN_HOOKS['pre_item_update']['jamf']['Phone'] = ['PluginJamfMobileDevice', 'preUpdatePhone']; - $PLUGIN_HOOKS['undiscloseConfigValue']['jamf'] = [PluginJamfConfig::class, 'undiscloseConfigValue']; + $PLUGIN_HOOKS['undiscloseConfigValue']['jamf'] = PluginJamfConfig::undiscloseConfigValue(...); Plugin::registerClass('PluginJamfRuleImportCollection', ['rulecollections_types' => true]); Plugin::registerClass('PluginJamfProfile', ['addtabon' => ['Profile']]); Plugin::registerClass('PluginJamfItem_ExtensionAttribute', ['addtabon' => [ @@ -59,6 +64,7 @@ function plugin_init_jamf() if (Session::haveRight('plugin_jamf_mobiledevice', READ)) { $PLUGIN_HOOKS['menu_toadd']['jamf'] = ['tools' => 'PluginJamfMenu']; } + $PLUGIN_HOOKS['post_init']['jamf'] = 'plugin_jamf_postinit'; $PLUGIN_HOOKS['item_purge']['jamf'] = [ 'Computer' => ['PluginJamfAbstractDevice', 'plugin_jamf_purgeComputer'], @@ -66,6 +72,8 @@ function plugin_init_jamf() 'Software' => ['PluginJamfSoftware', 'plugin_jamf_purgeSoftware'], ]; + $PLUGIN_HOOKS[Hooks::CONFIG_PAGE]['jamf'] = 'front/menu.php'; + // Dashboards $PLUGIN_HOOKS['dashboard_cards']['jamf'] = 'plugin_jamf_dashboardCards'; @@ -76,7 +84,7 @@ function plugin_init_jamf() function plugin_version_jamf() { return [ - 'name' => _x('plugin_info', 'JAMF Plugin for GLPI', 'jamf'), + 'name' => 'Jamf', 'version' => PLUGIN_JAMF_VERSION, 'author' => "Teclib' & Curtis Conard", 'license' => 'GPLv2', @@ -92,21 +100,19 @@ function plugin_version_jamf() function plugin_jamf_check_prerequisites() { - if (!method_exists('Plugin', 'checkGlpiVersion')) { - $version = preg_replace('/^((\d+\.?)+).*$/', '$1', GLPI_VERSION); - $matchMinGlpiReq = version_compare($version, PLUGIN_JAMF_MIN_GLPI, '>='); - $matchMaxGlpiReq = version_compare($version, PLUGIN_JAMF_MAX_GLPI, '<'); - if (!$matchMinGlpiReq || !$matchMaxGlpiReq) { - echo vsprintf( - 'This plugin requires GLPI >= %1$s and < %2$s.', - [ - PLUGIN_JAMF_MIN_GLPI, - PLUGIN_JAMF_MAX_GLPI, - ], - ); - - return false; - } + $version = preg_replace('/^((\d+\.?)+).*$/', '$1', GLPI_VERSION); + $matchMinGlpiReq = version_compare($version, PLUGIN_JAMF_MIN_GLPI, '>='); + $matchMaxGlpiReq = version_compare($version, PLUGIN_JAMF_MAX_GLPI, '<'); + if (!$matchMinGlpiReq || !$matchMaxGlpiReq) { + echo vsprintf( + 'This plugin requires GLPI >= %1$s and < %2$s.', + [ + PLUGIN_JAMF_MIN_GLPI, + PLUGIN_JAMF_MAX_GLPI, + ], + ); + + return false; } return true; diff --git a/templates/config.html.twig b/templates/config.html.twig index 2b22ac7..5a94cbb 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% import 'components/form/fields_macros.html.twig' as fields %} @@ -36,7 +34,7 @@
-
+
@@ -90,10 +88,7 @@ {{ fields.dropdownField('ComputerType', 'appletv_type', config.appletv_type|default(0), _x('config', 'AppleTV Type', 'jamf')) }} {{ fields.dropdownField('State', 'default_status', config.default_status|default(0), _x('config', 'Default status', 'jamf'), { entity: 0, - condition: { - is_visible_computer: true, - is_visible_phone: true, - } + condition: states_condition }) }}
diff --git a/templates/ext_attributes.html.twig b/templates/ext_attributes.html.twig index c96fc96..703463b 100644 --- a/templates/ext_attributes.html.twig +++ b/templates/ext_attributes.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} diff --git a/templates/import.html.twig b/templates/import.html.twig index 385435c..8b06298 100644 --- a/templates/import.html.twig +++ b/templates/import.html.twig @@ -1,38 +1,36 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {{ include('components/pager.html.twig', { count: total_count, - href: get_plugin_web_dir('jamf') ~ '/front/import.php', + href: path('/plugins/jamf/front/import.php'), start: _get['start']|default(0), }) }} @@ -75,7 +73,7 @@
{% set limitdropdown = include('components/dropdown/limit.html.twig', { - href: get_plugin_web_dir('jamf') ~ '/front/import.php', + href: path('/plugins/jamf/front/import.php'), }) %}
{{ __('Show %s entries')|format(limitdropdown)|raw }} @@ -90,16 +88,18 @@ const import_ids = $(':checkbox:checked').filter(':not([name^="_checkall"])').map(function() { return this.name.replace("import","").substring(1).split('_'); }).toArray(); + $.ajax({ type: "POST", - url: "{{ get_plugin_web_dir('jamf') }}/ajax/import.php", + url: "{{ path('/plugins/jamf/ajax/import.php') }}", data: { action: "import", import_ids: import_ids }, contentType: 'application/json', beforeSend: () => { - $('button[name="import"]').prop('disabled', true).html('{{ _x('action', 'Importing', 'jamf') }}'); + $('button[name="import"]').prop('disabled', true) + .html('{{ _x("action", "Importing", "jamf") }}'); $('button[name="discover"]').prop('disabled', true); $('button[name="clear"]').prop('disabled', true); }, @@ -108,15 +108,17 @@ } }); } + function discoverNow() { $.ajax({ type: "POST", - url: "{{ get_plugin_web_dir('jamf') }}/ajax/cron.php", - data: {crontask: "importJamf"}, + url: "{{ path('/plugins/jamf/ajax/cron.php') }}", + data: { crontask: "importJamf" }, contentType: 'application/json', beforeSend: () => { $('button[name="import"]').prop('disabled', true); - $('button[name="discover"]').prop('disabled', true).html('{{ _x('action', 'Discovering', 'jamf') }}'); + $('button[name="discover"]').prop('disabled', true) + .html('{{ _x("action", "Discovering", "jamf") }}'); $('button[name="clear"]').prop('disabled', true); }, complete: () => { @@ -124,10 +126,11 @@ } }); } + function clearPendingImports() { $.ajax({ type: "POST", - url: "{{ get_plugin_web_dir('jamf') }}/ajax/import.php", + url: "{{ path('/plugins/jamf/ajax/import.php') }}", data: { action: "clear" }, @@ -135,13 +138,15 @@ beforeSend: () => { $('button[name="import"]').prop('disabled', true); $('button[name="discover"]').prop('disabled', true); - $('button[name="clear"]').prop('disabled', true).html('{{ _x('action', 'Clearing', 'jamf') }}'); + $('button[name="clear"]').prop('disabled', true) + .html('{{ _x("action", "Clearing", "jamf") }}'); }, complete: () => { window.location.reload(); } }); } +
diff --git a/templates/inventory_info.html.twig b/templates/inventory_info.html.twig index 23f7e4c..5396056 100644 --- a/templates/inventory_info.html.twig +++ b/templates/inventory_info.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% import 'components/form/fields_macros.html.twig' as fields %} diff --git a/templates/mdm_command.html.twig b/templates/mdm_command.html.twig index 9d36ab9..4cd3362 100644 --- a/templates/mdm_command.html.twig +++ b/templates/mdm_command.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% import 'components/form/fields_macros.html.twig' as fields %} diff --git a/templates/mdm_commands.html.twig b/templates/mdm_commands.html.twig index ca4d0a5..b858553 100644 --- a/templates/mdm_commands.html.twig +++ b/templates/mdm_commands.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% import 'components/form/fields_macros.html.twig' as fields %} diff --git a/templates/menu.html.twig b/templates/menu.html.twig index a7c936e..d14a4a9 100644 --- a/templates/menu.html.twig +++ b/templates/menu.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% if links|length > 0 %}
diff --git a/templates/merge.html.twig b/templates/merge.html.twig index 2e7740a..ebc63db 100644 --- a/templates/merge.html.twig +++ b/templates/merge.html.twig @@ -1,40 +1,38 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% import 'components/form/fields_macros.html.twig' as fields %}
{{ include('components/pager.html.twig', { count: total_count, - href: get_plugin_web_dir('jamf') ~ '/front/merge.php', + href: path('/plugins/jamf/front/merge.php'), start: _get['start']|default(0), }) }} @@ -77,7 +75,7 @@
{% set limitdropdown = include('components/dropdown/limit.html.twig', { - href: get_plugin_web_dir('jamf') ~ '/front/merge.php', + href: path('/plugins/jamf/front/merge.php'), }) %}
{{ __('Show %s entries')|format(limitdropdown)|raw }} @@ -104,7 +102,7 @@ } $.ajax({ type: "POST", - url: "{{ get_plugin_web_dir('jamf') }}/ajax/merge.php", + url: "{{ path('/plugins/jamf/ajax/merge.php') }}", data: {action: "merge", item_ids: post_data}, contentType: 'application/json', beforeSend: () => { diff --git a/templates/user_jssaccount.html.twig b/templates/user_jssaccount.html.twig index 1c5bd0c..046b17d 100644 --- a/templates/user_jssaccount.html.twig +++ b/templates/user_jssaccount.html.twig @@ -1,33 +1,31 @@ {# -/** - * ------------------------------------------------------------------------- - * JAMF plugin for GLPI - * ------------------------------------------------------------------------- - * - * LICENSE - * - * This file is part of JAMF plugin for GLPI. - * - * JAMF plugin for GLPI is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * JAMF plugin for GLPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with JAMF plugin for GLPI. If not, see . - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ -#} + # ------------------------------------------------------------------------- + # JAMF plugin for GLPI + # ------------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of JAMF plugin for GLPI. + # + # JAMF plugin for GLPI is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # JAMF plugin for GLPI is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with JAMF plugin for GLPI. If not, see . + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2024-2025 by Teclib' + # @copyright Copyright (C) 2019-2024 by Curtis Conard + # @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + # @link https://github.com/pluginsGLPI/jamf + # ------------------------------------------------------------------------- + #} {% import 'components/form/fields_macros.html.twig' as fields %} diff --git a/tests/AbstractDBTest.php b/tests/AbstractDBTest.php index 44b82fa..6ea786a 100644 --- a/tests/AbstractDBTest.php +++ b/tests/AbstractDBTest.php @@ -22,36 +22,38 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +namespace GlpiPlugin\Jamf\Tests; + +use InvalidArgumentException; +use Auth; +use CommonDBTM; +use DBmysql; use PHPUnit\Framework\TestCase; use Glpi\Tests\Log\TestHandler; -use Glpi\Toolbox\Sanitizer; use Psr\Log\LogLevel; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ReflectionClass; +use Session; +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +use function Safe\preg_match; class AbstractDBTest extends TestCase { - private int $int; - - private string $str; - - /** - * @var TestHandler - */ private static TestHandler $php_log_handler; - /** - * @var TestHandler - */ - private static TestHandler $sql_log_handler; - public static function setUpBeforeClass(): void { + /** @var DBmysql $DB */ global $DB; $DB->beginTransaction(); static::resetSession(); @@ -60,13 +62,11 @@ public static function setUpBeforeClass(): void global $GLPI_CACHE; $GLPI_CACHE->clear(); - // Init log handlers - global $PHPLOGGER, $SQLLOGGER; - /** @var Monolog\Logger $PHPLOGGER */ - static::$php_log_handler = new TestHandler(LogLevel::DEBUG); - $PHPLOGGER->setHandlers([static::$php_log_handler]); - static::$sql_log_handler = new TestHandler(LogLevel::DEBUG); - $SQLLOGGER->setHandlers([static::$sql_log_handler]); + /** @var LoggerInterface $PHPLOGGER */ + global $PHPLOGGER; + /** @var Logger $PHPLOGGER */ + self::$php_log_handler = new TestHandler(LogLevel::DEBUG); + $PHPLOGGER->setHandlers([self::$php_log_handler]); $default_config = [ 'autoimport' => 0, @@ -96,6 +96,7 @@ public static function setUpBeforeClass(): void public static function tearDownAfterClass(): void { + /** @var DBmysql $DB */ global $DB; $DB->rollback(); } @@ -108,38 +109,16 @@ protected static function resetSession() $_SESSION['glpi_use_mode'] = Session::NORMAL_MODE; $_SESSION['glpiactive_entity'] = 0; + /** @var array $CFG_GLPI */ global $CFG_GLPI; + foreach ($CFG_GLPI['user_pref_field'] as $field) { - if (!isset($_SESSION["glpi$field"]) && isset($CFG_GLPI[$field])) { - $_SESSION["glpi$field"] = $CFG_GLPI[$field]; + if (!isset($_SESSION['glpi' . $field]) && isset($CFG_GLPI[$field])) { + $_SESSION['glpi' . $field] = $CFG_GLPI[$field]; } } } - /** - * Get a unique random string - */ - protected function getUniqueString() - { - if (is_null($this->str)) { - return $this->str = uniqid('str', false); - } - - return $this->str .= 'x'; - } - - /** - * Get a unique random integer - */ - protected function getUniqueInteger() - { - if (is_null($this->int)) { - return $this->int = random_int(1000, 10000); - } - - return $this->int++; - } - /** * Connect (using the test user per default) * @@ -147,17 +126,15 @@ protected function getUniqueInteger() * @param string $user_pass user password (defaults to TU_PASS) * @param bool $noauto disable autologin (from CAS by example) * @param bool $expected bool result expected from login return - * - * @return \Auth */ protected function login( string $user_name = TU_USER, string $user_pass = TU_PASS, bool $noauto = true, - bool $expected = true - ): \Auth { - \Session::destroy(); - \Session::start(); + bool $expected = true, + ): Auth { + Session::destroy(); + Session::start(); $auth = new Auth(); $this->assertEquals($expected, $auth->login($user_name, $user_pass, $noauto)); @@ -173,7 +150,7 @@ protected function login( protected function logOut() { $ctime = $_SESSION['glpi_currenttime']; - \Session::destroy(); + Session::destroy(); $_SESSION['glpi_currenttime'] = $ctime; } @@ -203,8 +180,6 @@ protected function setEntity($entityname, $subtree) */ protected function checkInput(CommonDBTM $object, $id = 0, $input = []) { - $input = Sanitizer::dbUnescapeRecursive($input); // slashes in input should not be stored in DB - $this->assertGreaterThan(0, $id, 'ID is not valid'); $this->assertTrue($object->getFromDB($id), 'Object not found in DB'); $this->assertEquals($id, $object->getID(), 'Object could not be loaded'); @@ -212,8 +187,8 @@ protected function checkInput(CommonDBTM $object, $id = 0, $input = []) if (count($input)) { foreach ($input as $k => $v) { $this->assertEquals($v, $object->fields[$k], " - '$k' key current value '{$object->fields[$k]}' (" . gettype($object->fields[$k]) . ") - is not equal to '$v' (" . gettype($v) . ')'); + '{$k}' key current value '{$object->fields[$k]}' (" . gettype($object->fields[$k]) . ") + is not equal to '{$v}' (" . gettype($v) . ')'); } } } @@ -252,11 +227,12 @@ protected function getClasses($function = false, array $excludes = []) $is_excluded = false; foreach ($excludes as $exclude) { - if ($classname === $exclude || @preg_match($exclude, $classname) === 1) { + if ($classname === $exclude || @preg_match($exclude, (string) $classname) === 1) { $is_excluded = true; break; } } + if ($is_excluded) { continue; } @@ -264,13 +240,14 @@ protected function getClasses($function = false, array $excludes = []) if (!class_exists($classname)) { continue; } + $reflectionClass = new ReflectionClass($classname); if ($reflectionClass->isAbstract()) { continue; } if ($function) { - if (method_exists($classname, $function)) { + if (method_exists($classname, (string) $function)) { $classes[] = $classname; } } else { @@ -287,20 +264,19 @@ protected function getClasses($function = false, array $excludes = []) * @param string $itemtype * @param array $input * @param array $skip_fields Fields that wont be checked after creation - * - * @return CommonDBTM */ protected function createItem($itemtype, $input, $skip_fields = []): CommonDBTM { + if (!is_a($itemtype, CommonDBTM::class, true)) { + throw new InvalidArgumentException(sprintf("Itemtype '%s' is not a valid CommonDBTM class", $itemtype)); + } + $item = new $itemtype(); - $input = Sanitizer::sanitize($input); $id = $item->add($input); $this->assertGreaterThan(0, $id, 'ID is not valid'); // Remove special fields - $input = array_filter($input, static function ($key) use ($skip_fields) { - return !in_array($key, $skip_fields, true) && !str_starts_with($key, '_'); - }, ARRAY_FILTER_USE_KEY); + $input = array_filter($input, static fn($key) => !in_array($key, $skip_fields, true) && !str_starts_with((string) $key, '_'), ARRAY_FILTER_USE_KEY); $this->checkInput($item, $id, $input); @@ -315,14 +291,17 @@ protected function createItem($itemtype, $input, $skip_fields = []): CommonDBTM */ protected function updateItem($itemtype, $id, $input) { + if (!is_a($itemtype, CommonDBTM::class, true)) { + throw new InvalidArgumentException(sprintf("Itemtype '%s' is not a valid CommonDBTM class", $itemtype)); + } + $item = new $itemtype(); $input['id'] = $id; - $input = Sanitizer::sanitize($input); $success = $item->update($input); $this->assertTrue($success); // Remove special fields - $input = array_filter($input, static fn($key) => !str_starts_with($key, '_'), ARRAY_FILTER_USE_KEY); + $input = array_filter($input, static fn($key) => !str_starts_with((string) $key, '_'), ARRAY_FILTER_USE_KEY); $this->checkInput($item, $id, $input); } diff --git a/tests/apitest.class.php b/tests/PluginJamfApiTest.php similarity index 89% rename from tests/apitest.class.php rename to tests/PluginJamfApiTest.php index 5b75996..abde35a 100644 --- a/tests/apitest.class.php +++ b/tests/PluginJamfApiTest.php @@ -22,13 +22,18 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +namespace GlpiPlugin\Jamf\Tests; + +use GlpiPlugin\Jamf\Tests\PluginJamfConnectionTest; +use PluginJamfAPI; + class PluginJamfApiTest extends PluginJamfAPI { protected static $connection_class = PluginJamfConnectionTest::class; diff --git a/tests/computertestsync.class.php b/tests/PluginJamfComputerTestSync.php similarity index 93% rename from tests/computertestsync.class.php rename to tests/PluginJamfComputerTestSync.php index f173c09..a8718cd 100644 --- a/tests/computertestsync.class.php +++ b/tests/PluginJamfComputerTestSync.php @@ -22,13 +22,17 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +namespace GlpiPlugin\Jamf\Tests; + +use PluginJamfComputerSync; + class PluginJamfComputerTestSync extends PluginJamfComputerSync { protected static $api = PluginJamfApiTest::class; diff --git a/tests/PluginJamfConnectionTest.php b/tests/PluginJamfConnectionTest.php new file mode 100644 index 0000000..07917e9 --- /dev/null +++ b/tests/PluginJamfConnectionTest.php @@ -0,0 +1,66 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2024-2025 by Teclib' + * @copyright Copyright (C) 2019-2024 by Curtis Conard + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/jamf + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Jamf\Tests; + +use GuzzleHttp\Client; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use PluginJamfConnection; + +use function Safe\file_get_contents; + +class PluginJamfConnectionTest extends PluginJamfConnection +{ + public function getClient(): Client + { + $mock = new MockHandler([ + function ($request, $options) { + $endpoint = $request->getUri()->getPath(); + $endpoint = str_contains($endpoint, '?') ? explode('?', $endpoint)[0] : $endpoint; + + $response_type = $request->getHeaderLine('Accept') ?: 'application/json'; + $response_ext = $response_type === 'application/xml' ? 'xml' : 'json'; + $mock_file_path = GLPI_ROOT . '/plugins/jamf/tools/samples/' . $endpoint . '.' . $response_ext; + + $body = file_get_contents($mock_file_path); + + return new Response(200, [], $body); + }, + ]); + + $handlerStack = HandlerStack::create($mock); + $this->client = new Client(['handler' => $handlerStack]); + + return $this->client; + } +} diff --git a/tests/mobiletestsync.class.php b/tests/PluginJamfMobileTestSync.php similarity index 93% rename from tests/mobiletestsync.class.php rename to tests/PluginJamfMobileTestSync.php index 3b44bfc..7228666 100644 --- a/tests/mobiletestsync.class.php +++ b/tests/PluginJamfMobileTestSync.php @@ -22,13 +22,17 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ +namespace GlpiPlugin\Jamf\Tests; + +use PluginJamfMobileSync; + class PluginJamfMobileTestSync extends PluginJamfMobileSync { protected static $api = PluginJamfApiTest::class; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index fc5cfb0..e684ffd 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -22,42 +22,16 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -global $CFG_GLPI; +require __DIR__ . '/../../../tests/bootstrap.php'; +require __DIR__ . '/../vendor/autoload.php'; -define('GLPI_ROOT', dirname(dirname(dirname(__DIR__)))); - -if (file_exists('vendor/autoload.php')) { - require_once 'vendor/autoload.php'; -} -include GLPI_ROOT . '/inc/includes.php'; -//include_once GLPI_ROOT . '/tests/GLPITestCase.php'; -//include_once GLPI_ROOT . '/tests/DbTestCase.php'; -include_once 'AbstractDBTest.php'; - -$plugin = new Plugin(); -$plugin->checkPluginState('jamf'); -$plugin->getFromDBbyDir('jamf'); - -if (!plugin_jamf_check_prerequisites()) { - echo "\nPrerequisites are not met!"; - die(1); +if (!Plugin::isPluginActive('jamf')) { + throw new RuntimeException('Plugin jamf is not active in the test database'); } - -if (!$plugin->isInstalled('jamf')) { - $plugin->install($plugin->getID()); -} -if (!$plugin->isActivated('jamf')) { - $plugin->activate($plugin->getID()); -} - -include_once 'apitest.class.php'; -include_once 'connectiontest.class.php'; -include_once 'mobiletestsync.class.php'; -include_once 'computertestsync.class.php'; diff --git a/tests/connectiontest.class.php b/tests/connectiontest.class.php deleted file mode 100644 index 6b8b20b..0000000 --- a/tests/connectiontest.class.php +++ /dev/null @@ -1,74 +0,0 @@ -. - * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' - * @copyright Copyright (C) 2019-2024 by Curtis Conard - * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html - * @link https://github.com/pluginsGLPI/jamf - * ------------------------------------------------------------------------- - */ - -use GuzzleHttp\ClientTrait; -use Psr\Http\Client\ClientInterface; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; - -class PluginJamfConnectionTest extends PluginJamfConnection -{ - public function getClient(): ClientInterface - { - if (!isset($this->client)) { - $this->client = new class implements ClientInterface { - use ClientTrait; - - public function sendRequest(RequestInterface $request): ResponseInterface - { - $endpoint = $request->getUri()->getPath(); - var_dump($endpoint); - // remove query parameters - $endpoint = str_contains($endpoint, '?') ? explode('?', $endpoint)[0] : $endpoint; - $response_type = $request->getHeader('Accept')[0] ?? 'application/json'; - $response_ext = $response_type === 'application/xml' ? 'xml' : 'json'; - $mock_file_path = GLPI_ROOT . '/plugins/jamf/tools/samples/' . $endpoint . '.' . $response_ext; - - return new \GuzzleHttp\Psr7\Response(200, [], file_get_contents($mock_file_path)); - } - - public function request(string $method, $uri, array $options = []): ResponseInterface - { - $request = new \GuzzleHttp\Psr7\Request($method, $uri, $options['headers'] ?? []); - - return $this->sendRequest($request); - } - - public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface - { - return \GuzzleHttp\Promise\Create::promiseFor($this->request($method, $uri, $options)); - } - }; - } - - return $this->client; - } -} diff --git a/tests/units/PluginJamfComputerSync.php b/tests/units/PluginJamfComputerSync.php index 7c08e76..f36a334 100644 --- a/tests/units/PluginJamfComputerSync.php +++ b/tests/units/PluginJamfComputerSync.php @@ -22,25 +22,29 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -namespace tests\units; +namespace GlpiPlugin\Jamf\Tests\units; +use Computer; +use DBmysql; +use GlpiPlugin\Jamf\Tests\AbstractDBTest; +use GlpiPlugin\Jamf\Tests\PluginJamfComputerTestSync; use PluginJamfComputer; -use PluginJamfComputerTestSync; use PluginJamfExtensionAttribute; use PluginJamfImport; use PluginJamfItem_ExtensionAttribute; -class PluginJamfComputerSync extends \AbstractDBTest +class PluginJamfComputerSync extends AbstractDBTest { public function testDiscover() { + /** @var DBmysql $DB */ global $DB; PluginJamfComputerTestSync::discover(); @@ -53,6 +57,7 @@ public function testDiscover() public function testSyncExtensionAttributeDefinitions() { + /** @var DBmysql $DB */ global $DB; PluginJamfComputerTestSync::syncExtensionAttributeDefinitions(); @@ -79,6 +84,7 @@ public function testSyncExtensionAttributeDefinitions() public function testImport() { + /** @var DBmysql $DB */ global $DB; PluginJamfComputerTestSync::syncExtensionAttributeDefinitions(); @@ -87,7 +93,7 @@ public function testImport() // Make sure the computer was created $iterator = $DB->request([ - 'FROM' => \Computer::getTable(), + 'FROM' => Computer::getTable(), 'WHERE' => [ 'name' => 'CConardMBA', ], diff --git a/tests/units/PluginJamfMobileSync.php b/tests/units/PluginJamfMobileSync.php index a52fc34..615e8df 100644 --- a/tests/units/PluginJamfMobileSync.php +++ b/tests/units/PluginJamfMobileSync.php @@ -22,15 +22,19 @@ * You should have received a copy of the GNU General Public License * along with JAMF plugin for GLPI. If not, see . * ------------------------------------------------------------------------- - * @copyright Copyright (C) 2024-2024 by Teclib' + * @copyright Copyright (C) 2024-2025 by Teclib' * @copyright Copyright (C) 2019-2024 by Curtis Conard * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html * @link https://github.com/pluginsGLPI/jamf * ------------------------------------------------------------------------- */ -namespace tests\units; +namespace GlpiPlugin\Jamf\Tests\units; +use Computer; +use DBmysql; +use GlpiPlugin\Jamf\Tests\AbstractDBTest; +use GlpiPlugin\Jamf\Tests\PluginJamfMobileTestSync; use Phone; use PluginJamfAbstractDevice; use PluginJamfExtensionAttribute; @@ -38,14 +42,14 @@ use PluginJamfImport; use PluginJamfItem_ExtensionAttribute; use PluginJamfMobileDevice; -use PluginJamfMobileTestSync; use PluginJamfSync; use ReflectionClass; -class PluginJamfMobileSync extends \AbstractDBTest +class PluginJamfMobileSync extends AbstractDBTest { public function testDiscover() { + /** @var DBmysql $DB */ global $DB; PluginJamfMobileTestSync::discover(); @@ -59,6 +63,7 @@ public function testDiscover() public function testSyncExtensionAttributeDefinitions() { + /** @var DBmysql $DB */ global $DB; PluginJamfMobileTestSync::syncExtensionAttributeDefinitions(); @@ -85,6 +90,7 @@ public function testSyncExtensionAttributeDefinitions() public function testImportAsComputer() { + /** @var DBmysql $DB */ global $DB; // Force sync extension attribute definitions @@ -94,7 +100,7 @@ public function testImportAsComputer() // Make sure the computer was created $iterator = $DB->request([ - 'FROM' => \Computer::getTable(), + 'FROM' => Computer::getTable(), 'WHERE' => [ 'name' => 'Test iPad 3', ], @@ -140,6 +146,7 @@ public function testImportAsComputer() public function testImportAsPhone() { + /** @var DBmysql $DB */ global $DB; PluginJamfMobileTestSync::import('Phone', 5, false); @@ -186,7 +193,7 @@ public function testImportAsPhone() $this->assertEquals('1aec6610a9401d2cc47cb55e1a2f7b500ab75864', $ext_field['value']); } - public function deviceSyncEnginesProvider() + public static function deviceSyncEnginesProvider() { $engines = PluginJamfSync::getDeviceSyncEngines(); $result = []; diff --git a/tools/HEADER b/tools/HEADER new file mode 100644 index 0000000..1e6780a --- /dev/null +++ b/tools/HEADER @@ -0,0 +1,26 @@ +------------------------------------------------------------------------- +JAMF plugin for GLPI +------------------------------------------------------------------------- + +LICENSE + +This file is part of JAMF plugin for GLPI. + +JAMF plugin for GLPI is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +JAMF plugin for GLPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with JAMF plugin for GLPI. If not, see . +------------------------------------------------------------------------- +@copyright Copyright (C) 2024-2025 by Teclib' +@copyright Copyright (C) 2019-2024 by Curtis Conard +@license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html +@link https://github.com/pluginsGLPI/jamf +------------------------------------------------------------------------- diff --git a/tools/extract_template.sh b/tools/extract_template.sh index 959470c..6684b89 100755 --- a/tools/extract_template.sh +++ b/tools/extract_template.sh @@ -1,36 +1,33 @@ #!/bin/bash -# /** -# * --------------------------------------------------------------------- -# * Modified by Curtis Conard for the Jamf for GLPI Plugin 2020 -# * -# * GLPI - Gestionnaire Libre de Parc Informatique -# * Copyright (C) 2015-2020 Teclib' and contributors. -# * -# * http://glpi-project.org -# * -# * based on GLPI - Gestionnaire Libre de Parc Informatique -# * Copyright (C) 2003-2014 by the INDEPNET Development Team. -# * -# * --------------------------------------------------------------------- -# * -# * LICENSE -# * -# * This file is part of GLPI. -# * -# * GLPI is free software; you can redistribute it and/or modify -# * it under the terms of the GNU General Public License as published by -# * the Free Software Foundation; either version 2 of the License, or -# * (at your option) any later version. -# * -# * GLPI is distributed in the hope that it will be useful, -# * but WITHOUT ANY WARRANTY; without even the implied warranty of -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# * GNU General Public License for more details. -# * -# * You should have received a copy of the GNU General Public License -# * along with GLPI. If not, see . -# * --------------------------------------------------------------------- -# */ + +# +# ------------------------------------------------------------------------- +# JAMF plugin for GLPI +# ------------------------------------------------------------------------- +# +# LICENSE +# +# This file is part of JAMF plugin for GLPI. +# +# JAMF plugin for GLPI is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# JAMF plugin for GLPI is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with JAMF plugin for GLPI. If not, see . +# ------------------------------------------------------------------------- +# @copyright Copyright (C) 2024-2025 by Teclib' +# @copyright Copyright (C) 2019-2024 by Curtis Conard +# @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html +# @link https://github.com/pluginsGLPI/jamf +# ------------------------------------------------------------------------- +# # Clean existing file rm -f locales/jamf.pot && touch locales/jamf.pot