From 53417ff97fe4a0e2e2fb0910b7636b40fb6ee362 Mon Sep 17 00:00:00 2001 From: odain Date: Mon, 6 Oct 2025 15:55:33 +0200 Subject: [PATCH 01/15] poc php-cs-fixer --- .gitignore | 1 + .php-cs-fixer.dist.php | 40 + tools/composer.json | 6 + tools/composer.lock | 2655 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2702 insertions(+) create mode 100644 .php-cs-fixer.dist.php create mode 100644 tools/composer.json create mode 100644 tools/composer.lock diff --git a/.gitignore b/.gitignore index df991db82c..88d6c976d6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ # composer reserver directory, from sources, populate/update using "composer install" vendor/* tests/*/vendor/* +tools/vendor # all conf but listing prevention /conf/** diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000000..50bc9768b4 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,40 @@ +exclude('oql') + //->in(__DIR__ . '/addons') +// ->in(__DIR__ . '/application') + //->in(__DIR__ . '/core') +// ->in(__DIR__ . '/datamodels') +// ->in(__DIR__ . '/dictionaries') +// ->in(__DIR__ . '/pages') +// ->in(__DIR__ . '/portal') +// ->in(__DIR__ . '/setup') +// ->in(__DIR__ . '/sources') +// ->in(__DIR__ . '/synchro') +// ->in(__DIR__ . '/tests') + ->in(__DIR__ . '/webservices') + ; + +$config = new PhpCsFixer\Config(); +return $config->setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + // We use PSR12 with consistent brace placement. + 'curly_braces_position' => [ + 'functions_opening_brace' => 'same_line', + 'classes_opening_brace' => 'same_line', + ], + // declare(strict_types=1) on the same line as false, + 'declare_strict_types' => true, + // Keep argument formatting for now. + 'method_argument_space' => ['on_multiline' => 'ignore'], + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_trim' => true, + 'no_empty_phpdoc' => true, + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], + 'no_extra_blank_lines' => true, + ]) + ->setFinder($finder) +; \ No newline at end of file diff --git a/tools/composer.json b/tools/composer.json new file mode 100644 index 0000000000..b990d13f5b --- /dev/null +++ b/tools/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "friendsofphp/php-cs-fixer": "^3.10", + "phpstan/phpstan": "^2.0" + } +} diff --git a/tools/composer.lock b/tools/composer.lock new file mode 100644 index 0000000000..aeb87a424c --- /dev/null +++ b/tools/composer.lock @@ -0,0 +1,2655 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d76c2ddfcd673d9164eff7c412640c4e", + "packages": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "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" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "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" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "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/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "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": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "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": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.87.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992", + "reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/promise": "^3.3", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.7", + "infection/infection": "^0.29.14", + "justinrainbow/json-schema": "^6.5", + "keradus/cli-executor": "^2.2", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.8", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", + "symfony/polyfill-php84": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2", + "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2" + }, + "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.87.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-09-10T09:51:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.27", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "25da374959afa391992792691093550b3098ef1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/25da374959afa391992792691093550b3098ef1e", + "reference": "25da374959afa391992792691093550b3098ef1e", + "shasum": "" + }, + "require": { + "php": "^7.4|^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-09-17T09:55:13+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/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": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "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/3.0.2" + }, + "time": "2024-09-11T13:17:53+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.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 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.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+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/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "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/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-25T06:35:40+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "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/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-13T11:49:31+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-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/v3.6.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-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "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/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-07T08:17:47+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "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/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "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/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-05T10:16:07+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "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": { + "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" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "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": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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 Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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 backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-18T09:42:54+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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 writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.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": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^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/v7.3.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": "2025-02-24T10:49:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-25T06:35:40+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} From 01cf699ffadd1b0ace2709ad988f2307f774c7f7 Mon Sep 17 00:00:00 2001 From: odain Date: Mon, 6 Oct 2025 16:18:23 +0200 Subject: [PATCH 02/15] PSR2 for now --- .php-cs-fixer.dist.php | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 50bc9768b4..96e85359b0 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,38 +2,24 @@ $finder = PhpCsFixer\Finder::create() ->exclude('oql') - //->in(__DIR__ . '/addons') -// ->in(__DIR__ . '/application') - //->in(__DIR__ . '/core') -// ->in(__DIR__ . '/datamodels') -// ->in(__DIR__ . '/dictionaries') -// ->in(__DIR__ . '/pages') -// ->in(__DIR__ . '/portal') -// ->in(__DIR__ . '/setup') -// ->in(__DIR__ . '/sources') -// ->in(__DIR__ . '/synchro') -// ->in(__DIR__ . '/tests') + ->in(__DIR__.'/addons') + ->in(__DIR__.'/application') + ->in(__DIR__.'/core') + ->in(__DIR__.'/datamodels') + ->in(__DIR__.'/dictionaries') + ->in(__DIR__.'/pages') + ->in(__DIR__.'/portal') + ->in(__DIR__.'/setup') + ->in(__DIR__.'/sources') + ->in(__DIR__.'/synchro') + ->in(__DIR__.'/tests') ->in(__DIR__ . '/webservices') ; $config = new PhpCsFixer\Config(); return $config->setRiskyAllowed(true) ->setRules([ - '@PSR12' => true, - // We use PSR12 with consistent brace placement. - 'curly_braces_position' => [ - 'functions_opening_brace' => 'same_line', - 'classes_opening_brace' => 'same_line', - ], - // declare(strict_types=1) on the same line as false, - 'declare_strict_types' => true, - // Keep argument formatting for now. - 'method_argument_space' => ['on_multiline' => 'ignore'], - 'phpdoc_align' => ['align' => 'left'], - 'phpdoc_trim' => true, - 'no_empty_phpdoc' => true, - 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], + '@PSR2' => true, 'no_extra_blank_lines' => true, ]) ->setFinder($finder) From c273fbb7002bc2665fc1943d25ebcff51443767a Mon Sep 17 00:00:00 2001 From: odain Date: Thu, 9 Oct 2025 14:27:59 +0200 Subject: [PATCH 03/15] PSR12 + array short --- .php-cs-fixer.dist.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 96e85359b0..035c187804 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -19,8 +19,9 @@ $config = new PhpCsFixer\Config(); return $config->setRiskyAllowed(true) ->setRules([ - '@PSR2' => true, + '@PSR12' => true, 'no_extra_blank_lines' => true, + 'array_syntax' => ['syntax' => 'short'], ]) ->setFinder($finder) ; \ No newline at end of file From 5769b5a1dd59258e6416725cd92fa860dc4d7d26 Mon Sep 17 00:00:00 2001 From: odain Date: Thu, 9 Oct 2025 16:46:00 +0200 Subject: [PATCH 04/15] move php-cs-fixer stuff in tests/php-code-style --- .gitignore | 1 - .../php-code-style/.php-cs-fixer.dist.php | 0 {tools => tests/php-code-style}/composer.json | 0 {tools => tests/php-code-style}/composer.lock | 0 4 files changed, 1 deletion(-) rename .php-cs-fixer.dist.php => tests/php-code-style/.php-cs-fixer.dist.php (100%) rename {tools => tests/php-code-style}/composer.json (100%) rename {tools => tests/php-code-style}/composer.lock (100%) diff --git a/.gitignore b/.gitignore index 88d6c976d6..df991db82c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ # composer reserver directory, from sources, populate/update using "composer install" vendor/* tests/*/vendor/* -tools/vendor # all conf but listing prevention /conf/** diff --git a/.php-cs-fixer.dist.php b/tests/php-code-style/.php-cs-fixer.dist.php similarity index 100% rename from .php-cs-fixer.dist.php rename to tests/php-code-style/.php-cs-fixer.dist.php diff --git a/tools/composer.json b/tests/php-code-style/composer.json similarity index 100% rename from tools/composer.json rename to tests/php-code-style/composer.json diff --git a/tools/composer.lock b/tests/php-code-style/composer.lock similarity index 100% rename from tools/composer.lock rename to tests/php-code-style/composer.lock From d3910ee04d1dc8effdd4b9af1e339e617a4556f9 Mon Sep 17 00:00:00 2001 From: odain Date: Thu, 9 Oct 2025 16:51:10 +0200 Subject: [PATCH 05/15] add README --- tests/php-code-style/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/php-code-style/README.md diff --git a/tests/php-code-style/README.md b/tests/php-code-style/README.md new file mode 100644 index 0000000000..15224342b0 --- /dev/null +++ b/tests/php-code-style/README.md @@ -0,0 +1,17 @@ +Code formatting tool used by iTop is PHP-CS-Fixer: +https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/master + + +to check code style issues (no path provided means whole iTop code base): + +``` +cd tests/php-code-style/; composer install; cd - +tests/php-code-style/vendor/bin/php-cs-fixer check [PATH] +``` + +to respect iTop code standards and re-format (no path provided means whole iTop code base): + +``` +tests/php-code-style/vendor/bin/php-cs-fixer fix [PATH] + +``` \ No newline at end of file From 614fa1d97ed311782abc30a59f8ce66e8a68baa9 Mon Sep 17 00:00:00 2001 From: odain Date: Tue, 14 Oct 2025 08:43:40 +0200 Subject: [PATCH 06/15] fix php-cs-fixer move into php-code-style --- tests/php-code-style/.php-cs-fixer.dist.php | 27 ++++++++++++--------- tests/php-code-style/README.md | 4 +-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/php-code-style/.php-cs-fixer.dist.php b/tests/php-code-style/.php-cs-fixer.dist.php index 035c187804..601ff41cdb 100644 --- a/tests/php-code-style/.php-cs-fixer.dist.php +++ b/tests/php-code-style/.php-cs-fixer.dist.php @@ -1,19 +1,22 @@ exclude('oql') - ->in(__DIR__.'/addons') - ->in(__DIR__.'/application') - ->in(__DIR__.'/core') - ->in(__DIR__.'/datamodels') - ->in(__DIR__.'/dictionaries') - ->in(__DIR__.'/pages') - ->in(__DIR__.'/portal') - ->in(__DIR__.'/setup') - ->in(__DIR__.'/sources') - ->in(__DIR__.'/synchro') - ->in(__DIR__.'/tests') - ->in(__DIR__ . '/webservices') + ->in($APPROOT.'/addons') + ->in($APPROOT.'/application') + ->in($APPROOT.'/core') + ->in($APPROOT.'/datamodels') + ->in($APPROOT.'/dictionaries') + ->in($APPROOT.'/pages') + ->in($APPROOT.'/portal') + ->in($APPROOT.'/setup') + ->in($APPROOT.'/sources') + ->in($APPROOT.'/synchro') + ->in($APPROOT.'/tests') + ->in($APPROOT . '/webservices') ; $config = new PhpCsFixer\Config(); diff --git a/tests/php-code-style/README.md b/tests/php-code-style/README.md index 15224342b0..cc063439b1 100644 --- a/tests/php-code-style/README.md +++ b/tests/php-code-style/README.md @@ -6,12 +6,12 @@ to check code style issues (no path provided means whole iTop code base): ``` cd tests/php-code-style/; composer install; cd - -tests/php-code-style/vendor/bin/php-cs-fixer check [PATH] +tests/php-code-style/vendor/bin/php-cs-fixer check --config tests/php-code-style/.php-cs-fixer.dist.php [PATH] ``` to respect iTop code standards and re-format (no path provided means whole iTop code base): ``` -tests/php-code-style/vendor/bin/php-cs-fixer fix [PATH] +tests/php-code-style/vendor/bin/php-cs-fixer fix --config tests/php-code-style/.php-cs-fixer.dist.php [PATH] ``` \ No newline at end of file From ffd3544267802af1d325ec08ac24ef56ab5acead Mon Sep 17 00:00:00 2001 From: odain Date: Tue, 14 Oct 2025 08:44:02 +0200 Subject: [PATCH 07/15] illustrate code reformatting on webservices folder --- webservices/backoffice.dataloader.php | 160 ++- webservices/createfrommail.php | 201 ++- webservices/cron.php | 869 ++++++------- webservices/export-v2.php | 1053 ++++++++-------- webservices/export.php | 560 ++++----- webservices/import.php | 1528 +++++++++++------------ webservices/itop.wsdl.php | 41 +- webservices/itoprest.examples.php | 637 +++++----- webservices/itopsoap.examples.php | 195 ++- webservices/itopsoaptypes.class.inc.php | 225 ++-- webservices/rest.php | 408 +++--- webservices/soapserver.php | 111 +- webservices/status.php | 17 +- webservices/webservices.basic.php | 493 ++++---- webservices/webservices.class.inc.php | 1064 ++++++++-------- 15 files changed, 3514 insertions(+), 4048 deletions(-) diff --git a/webservices/backoffice.dataloader.php b/webservices/backoffice.dataloader.php index c73ca047f0..effadf4344 100644 --- a/webservices/backoffice.dataloader.php +++ b/webservices/backoffice.dataloader.php @@ -1,4 +1,5 @@ p("No memory limit has been defined in this instance of PHP"); - } - else - { - // Check that the limit will allow us to load the data - // - $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); - if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) - { - if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE) - { - $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); - } - else - { - $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); - } - } - } + $sMemoryLimit = trim(ini_get('memory_limit')); + if (empty($sMemoryLimit)) { + // On some PHP installations, memory_limit does not exist as a PHP setting! + // (encountered on a 5.2.0 under Windows) + // In that case, ini_set will not work, let's keep track of this and proceed with the data load + $oP->p("No memory limit has been defined in this instance of PHP"); + } else { + // Check that the limit will allow us to load the data + // + $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); + if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) { + if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === false) { + $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); + } else { + $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); + } + } + } } - //////////////////////////////////////////////////////////////////////////////// // // Main @@ -87,68 +79,60 @@ function SetMemoryLimit($oP) $oP = new WebPage("iTop - Backoffice data loader"); - -try -{ - // Note: the data model must be loaded first - $oDataLoader = new XMLDataLoader(); - - if (empty($sFileName)) { - throw(new Exception("Missing argument 'file'")); - } - if (!file_exists($sFileName)) { - throw(new Exception("File $sFileName does not exist")); - } - - SetMemoryLimit($oP); - - - // The XMLDataLoader constructor has initialized the DB, let's start a transaction - CMDBSource::Query('START TRANSACTION'); - - $oP->p("Starting data load."); - CMDBObject::SetCurrentChangeFromParams('Initialization WS'); - $oDataLoader->StartSession(CMDBObject::GetCurrentChange()); - $oDataLoader->LoadFile($sFileName); - - $oP->p("Ending data load session"); - if ($oDataLoader->EndSession(true /* strict */)) { - $iCountCreated = $oDataLoader->GetCountCreated(); - CMDBSource::Query('COMMIT'); - - $oP->p("Data successfully written into the DB: $iCountCreated objects created"); - } else { - CMDBSource::Query('ROLLBACK'); - $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data"); - $aErrors = $oDataLoader->GetErrors(); - if (count($aErrors) > 0) { - $oP->p('Errors ('.count($aErrors).')'); - foreach ($aErrors as $sMsg) { - $oP->p(' * '.$sMsg); - } - } - $aWarnings = $oDataLoader->GetWarnings(); - if (count($aWarnings) > 0) - { - $oP->p('Warnings ('.count($aWarnings).')'); - foreach ($aWarnings as $sMsg) - { - $oP->p(' * '.$sMsg); - } - } - } - -} -catch(Exception $e) -{ - $oP->p("An error happened while loading the data: ".$e->getMessage()); - $oP->p("Aborting (no data written)..."); - CMDBSource::Query('ROLLBACK'); +try { + // Note: the data model must be loaded first + $oDataLoader = new XMLDataLoader(); + + if (empty($sFileName)) { + throw(new Exception("Missing argument 'file'")); + } + if (!file_exists($sFileName)) { + throw(new Exception("File $sFileName does not exist")); + } + + SetMemoryLimit($oP); + + // The XMLDataLoader constructor has initialized the DB, let's start a transaction + CMDBSource::Query('START TRANSACTION'); + + $oP->p("Starting data load."); + CMDBObject::SetCurrentChangeFromParams('Initialization WS'); + $oDataLoader->StartSession(CMDBObject::GetCurrentChange()); + $oDataLoader->LoadFile($sFileName); + + $oP->p("Ending data load session"); + if ($oDataLoader->EndSession(true /* strict */)) { + $iCountCreated = $oDataLoader->GetCountCreated(); + CMDBSource::Query('COMMIT'); + + $oP->p("Data successfully written into the DB: $iCountCreated objects created"); + } else { + CMDBSource::Query('ROLLBACK'); + $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data"); + $aErrors = $oDataLoader->GetErrors(); + if (count($aErrors) > 0) { + $oP->p('Errors ('.count($aErrors).')'); + foreach ($aErrors as $sMsg) { + $oP->p(' * '.$sMsg); + } + } + $aWarnings = $oDataLoader->GetWarnings(); + if (count($aWarnings) > 0) { + $oP->p('Warnings ('.count($aWarnings).')'); + foreach ($aWarnings as $sMsg) { + $oP->p(' * '.$sMsg); + } + } + } + +} catch (Exception $e) { + $oP->p("An error happened while loading the data: ".$e->getMessage()); + $oP->p("Aborting (no data written)..."); + CMDBSource::Query('ROLLBACK'); } -if (function_exists('memory_get_peak_usage')) -{ - $oP->p("Information: memory peak usage: ".memory_get_peak_usage()); +if (function_exists('memory_get_peak_usage')) { + $oP->p("Information: memory peak usage: ".memory_get_peak_usage()); } $oP->Output(); diff --git a/webservices/createfrommail.php b/webservices/createfrommail.php index a83488754a..35edac0f50 100644 --- a/webservices/createfrommail.php +++ b/webservices/createfrommail.php @@ -41,14 +41,13 @@ function GetSender($aHeaders) { - $aResult = array('name' => '', 'email' => ''); - $aResult['name'] = $aHeaders['From']; - $aMatches = array(); - if (preg_match('/\(([0-9a-zA-Z\._]+)@(.+)@(.+)\)/U', array_pop($aHeaders['Received']), $aMatches)) - { - $aResult['email'] = $aMatches[1].'@'.$aMatches[2]; - } - return $aResult; + $aResult = ['name' => '', 'email' => '']; + $aResult['name'] = $aHeaders['From']; + $aMatches = []; + if (preg_match('/\(([0-9a-zA-Z\._]+)@(.+)@(.+)\)/U', array_pop($aHeaders['Received']), $aMatches)) { + $aResult['email'] = $aMatches[1].'@'.$aMatches[2]; + } + return $aResult; } /** @@ -60,44 +59,38 @@ function GetSender($aHeaders) */ function CreateTicket($sSenderEmail, $sSubject, $sBody) { - $oTicket = null; - try - { - $oContactSearch = new DBObjectSearch('Contact'); // Can be either a Person or a Team, but must be a valid Contact - $oContactSearch->AddCondition('email', $sSenderEmail, '='); - $oSet = new DBObjectSet($oContactSearch); - if ($oSet->Count() == 1) - { - $oContact = $oSet->Fetch(); - $oOrganization = MetaModel::GetObject('Organization', $oContact->Get('org_id')); - $oTicket = new UserRequest; - $oTicket->Set('title', $sSubject); - $oTicket->Set('description', $sBody); - $oTicket->Set('org_id', $oOrganization->GetKey()); - $oTicket->Set('caller_id', $oContact->GetKey()); - $oTicket->Set('impact', DEFAULT_IMPACT); - $oTicket->Set('urgency', DEFAULT_URGENCY); - $oTicket->Set('product', DEFAULT_PRODUCT); - $oTicket->Set('service_id', DEFAULT_SERVICE_ID); // Can be replaced by a search for a valid service for this 'org_id' - $oTicket->Set('servicesubcategory_id', DEFAULT_SUBSERVICE_ID); // Same as above... - $oTicket->Set('workgroup_id', DEFAULT_WORKGROUP_ID); // Same as above... + $oTicket = null; + try { + $oContactSearch = new DBObjectSearch('Contact'); // Can be either a Person or a Team, but must be a valid Contact + $oContactSearch->AddCondition('email', $sSenderEmail, '='); + $oSet = new DBObjectSet($oContactSearch); + if ($oSet->Count() == 1) { + $oContact = $oSet->Fetch(); + $oOrganization = MetaModel::GetObject('Organization', $oContact->Get('org_id')); + $oTicket = new UserRequest(); + $oTicket->Set('title', $sSubject); + $oTicket->Set('description', $sBody); + $oTicket->Set('org_id', $oOrganization->GetKey()); + $oTicket->Set('caller_id', $oContact->GetKey()); + $oTicket->Set('impact', DEFAULT_IMPACT); + $oTicket->Set('urgency', DEFAULT_URGENCY); + $oTicket->Set('product', DEFAULT_PRODUCT); + $oTicket->Set('service_id', DEFAULT_SERVICE_ID); // Can be replaced by a search for a valid service for this 'org_id' + $oTicket->Set('servicesubcategory_id', DEFAULT_SUBSERVICE_ID); // Same as above... + $oTicket->Set('workgroup_id', DEFAULT_WORKGROUP_ID); // Same as above... - // Record the change information about the object - $sUserString = $oContact->GetName().', submitted by email'; - CMDBObject::SetTrackInfo($sUserString); - $oTicket->DBInsert(); - } - else - { - echo "No contact found in iTop having the email: $sSenderEmail, email message ignored.\n"; - } - } - catch(Exception $e) - { - echo "Error: exception ".$e->getMessage(); - $oTicket = null; - } - return $oTicket; + // Record the change information about the object + $sUserString = $oContact->GetName().', submitted by email'; + CMDBObject::SetTrackInfo($sUserString); + $oTicket->DBInsert(); + } else { + echo "No contact found in iTop having the email: $sSenderEmail, email message ignored.\n"; + } + } catch (Exception $e) { + echo "Error: exception ".$e->getMessage(); + $oTicket = null; + } + return $oTicket; } /** * Main program @@ -111,72 +104,62 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) // Note: it is expected that the sender of the email exists a valid contact as a 'Contact' // in iTop (identified by her/his email address), otherwise the ticket creation will fail $iNbMessages = $oPop3->numMsg(); -for($index = 1; $index <= $iNbMessages; $index++) -{ - $params['include_bodies'] = true; - $params['decode_bodies'] = true; - $params['decode_headers'] = true; - $params['crlf'] = "\r\n"; - $aHeaders = $oPop3->getParsedHeaders($index); - $aSender = GetSender($aHeaders); - $oDecoder = new Mail_mimeDecode( $oPop3->getRawHeaders($index).$params['crlf'].$oPop3->getBody($index) ); - $oStructure = $oDecoder->decode($params); - $sSubject = $aHeaders['Subject']; - // Search for the text/plain body part - $iPartIndex = 0; - $bFound = false; - $sTextBody = ''; - //echo "
\n";
-	//print_r($oStructure);
-	//echo "
\n"; - if (!isset($oStructure->parts) || count($oStructure->parts) == 0) - { - $sTextBody = $oStructure->body; - } - else - { - // Find the first "part" of the body which is in text/plain - while( ($iPartIndex < count($oStructure->parts)) && (!$bFound) ) - { - //echo "

Reading part $iPartIndex

\n"; - if ( ($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && - ($oStructure->parts[$iPartIndex]->ctype_secondary == 'plain') ) - { - $sTextBody = $oStructure->parts[$iPartIndex]->body; - $bFound = true; - //echo "

Plain text found ! ($sTextBody)

\n"; - } - $iPartIndex++; - } - // Try again but this time look for an HTML part - if (!$bFound) - { - while( ($iPartIndex < count($oStructure->parts)) && (!$bFound) ) - { - //echo "

Reading part $iPartIndex

\n"; - if ( ($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && - ($oStructure->parts[$iPartIndex]->ctype_secondary == 'html') ) - { - $sTextBody = $oStructure->parts[$iPartIndex]->body; - $bFound = true; - //echo "

HTML text found ! (".htmlentities($sTextBody, ENT_QUOTES, 'UTF-8').")

\n"; - } - $iPartIndex++; - } - } - } +for ($index = 1; $index <= $iNbMessages; $index++) { + $params['include_bodies'] = true; + $params['decode_bodies'] = true; + $params['decode_headers'] = true; + $params['crlf'] = "\r\n"; + $aHeaders = $oPop3->getParsedHeaders($index); + $aSender = GetSender($aHeaders); + $oDecoder = new Mail_mimeDecode($oPop3->getRawHeaders($index).$params['crlf'].$oPop3->getBody($index)); + $oStructure = $oDecoder->decode($params); + $sSubject = $aHeaders['Subject']; + // Search for the text/plain body part + $iPartIndex = 0; + $bFound = false; + $sTextBody = ''; + //echo "
\n";
+    //print_r($oStructure);
+    //echo "
\n"; + if (!isset($oStructure->parts) || count($oStructure->parts) == 0) { + $sTextBody = $oStructure->body; + } else { + // Find the first "part" of the body which is in text/plain + while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { + //echo "

Reading part $iPartIndex

\n"; + if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && + ($oStructure->parts[$iPartIndex]->ctype_secondary == 'plain')) { + $sTextBody = $oStructure->parts[$iPartIndex]->body; + $bFound = true; + //echo "

Plain text found ! ($sTextBody)

\n"; + } + $iPartIndex++; + } + // Try again but this time look for an HTML part + if (!$bFound) { + while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { + //echo "

Reading part $iPartIndex

\n"; + if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && + ($oStructure->parts[$iPartIndex]->ctype_secondary == 'html')) { + $sTextBody = $oStructure->parts[$iPartIndex]->body; + $bFound = true; + //echo "

HTML text found ! (".htmlentities($sTextBody, ENT_QUOTES, 'UTF-8').")

\n"; + } + $iPartIndex++; + } + } + } - // Bug: depending on the email, the email address could be found in : - // email => 'john.foo@combodo.com' - // name => 'john foo + // Bug: depending on the email, the email address could be found in : + // email => 'john.foo@combodo.com' + // name => 'john foo - $oTicket = CreateTicket($aSender['email'], $sSubject, $sTextBody); - if ($oTicket != null) - { - // Ticket created, delete the email - $oPop3->deleteMsg($index); - echo "Ticket: ".$oTicket->GetName()." created.\n"; - } + $oTicket = CreateTicket($aSender['email'], $sSubject, $sTextBody); + if ($oTicket != null) { + // Ticket created, delete the email + $oPop3->deleteMsg($index); + echo "Ticket: ".$oTicket->GetName()." created.\n"; + } } $oPop3->disconnect(); ?> diff --git a/webservices/cron.php b/webservices/cron.php index f51a46f64c..56cd8d066d 100644 --- a/webservices/cron.php +++ b/webservices/cron.php @@ -1,4 +1,5 @@ p("ERROR: Missing argument '$sParam'\n"); - UsageAndExit($oP); - } - - return trim($sValue); + $sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter); + if (is_null($sValue)) { + $oP->p("ERROR: Missing argument '$sParam'\n"); + UsageAndExit($oP); + } + + return trim($sValue); } function UsageAndExit($oP) { - $bModeCLI = ($oP instanceof CLIPage); - - if ($bModeCLI) - { - $oP->p("USAGE:\n"); - $oP->p("php cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1] [--debug=1] [--status_only=1]\n"); - } - else - { - $oP->p("Optional parameters: verbose, param_file, status_only\n"); - } - $oP->output(); - exit(EXIT_CODE_FATAL); + $bModeCLI = ($oP instanceof CLIPage); + + if ($bModeCLI) { + $oP->p("USAGE:\n"); + $oP->p("php cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1] [--debug=1] [--status_only=1]\n"); + } else { + $oP->p("Optional parameters: verbose, param_file, status_only\n"); + } + $oP->output(); + exit(EXIT_CODE_FATAL); } /** @@ -92,102 +87,84 @@ function UsageAndExit($oP) */ function RunTask(BackgroundTask $oTask, $iTimeLimit) { - $TaskClass = $oTask->Get('class_name'); - $oProcess = new $TaskClass; - $oRefClass = new ReflectionClass(get_class($oProcess)); - $oDateStarted = new DateTime(); - $oDatePlanned = new DateTime($oTask->Get('next_run_date')); - $fStart = microtime(true); - $oCtx = new ContextTag('CRON:Task:'.$TaskClass); - - $sMessage = ''; - $oExceptionToThrow = null; - try - { - // Record (when starting) that this task was started, just in case it crashes during the execution - $oTask->Set('latest_run_date', $oDateStarted->format('Y-m-d H:i:s')); - // Record the current user running the cron - $oTask->Set('system_user', utils::GetCurrentUserName()); - $oTask->Set('running', 1); - $oTask->DBUpdate(); - // Time in seconds allowed to the task - $iCurrTimeLimit = $iTimeLimit; - // Compute allowed time - if ($oRefClass->implementsInterface('iScheduledProcess') === false) - { - // Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity()) - $iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time'); - $iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime; - // If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0 - if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) - { - $iCurrTimeLimit = $iTaskLimit; - } - } - $sMessage = $oProcess->Process($iCurrTimeLimit); - $oTask->Set('running', 0); - } - catch (MySQLHasGoneAwayException $e) - { - throw $e; - } - catch (ProcessFatalException $e) - { - $oExceptionToThrow = $e; - } - catch (Exception $e) // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running - { - if ($oTask->IsDebug()) - { - $sMessage = 'Processing failed with message: '. $e->getMessage() . '. ' . $e->getTraceAsString(); - } - else - { - $sMessage = 'Processing failed with message: '. $e->getMessage(); - } - } - $fDuration = microtime(true) - $fStart; - if ($oTask->Get('total_exec_count') == 0) - { - // First execution - $oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s')); - } - $oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics - - // Update the timestamp since we want to be able to re-order the tasks based on the time they finished - $oDateEnded = new DateTime(); - $oTask->Set('latest_run_date', $oDateEnded->format('Y-m-d H:i:s')); - - if ($oRefClass->implementsInterface('iScheduledProcess')) - { - // Schedules process do repeat at specific moments - $oPlannedStart = $oProcess->GetNextOccurrence(); - } - else - { - // Background processes do repeat periodically - $oPlannedStart = clone $oDatePlanned; - // Let's schedule from the previous planned date of execution to avoid shift - $oPlannedStart->modify($oProcess->GetPeriodicity().' seconds'); - $oEnd = new DateTime(); - while ($oPlannedStart->format('U') < $oEnd->format('U')) - { - // Next planned start is already in the past, increase it again by a period - $oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds'); - } - } - - $oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s')); - $oTask->DBUpdate(); - - if ($oExceptionToThrow) - { - throw $oExceptionToThrow; - } - - unset($oCtx); - - return $sMessage; + $TaskClass = $oTask->Get('class_name'); + $oProcess = new $TaskClass(); + $oRefClass = new ReflectionClass(get_class($oProcess)); + $oDateStarted = new DateTime(); + $oDatePlanned = new DateTime($oTask->Get('next_run_date')); + $fStart = microtime(true); + $oCtx = new ContextTag('CRON:Task:'.$TaskClass); + + $sMessage = ''; + $oExceptionToThrow = null; + try { + // Record (when starting) that this task was started, just in case it crashes during the execution + $oTask->Set('latest_run_date', $oDateStarted->format('Y-m-d H:i:s')); + // Record the current user running the cron + $oTask->Set('system_user', utils::GetCurrentUserName()); + $oTask->Set('running', 1); + $oTask->DBUpdate(); + // Time in seconds allowed to the task + $iCurrTimeLimit = $iTimeLimit; + // Compute allowed time + if ($oRefClass->implementsInterface('iScheduledProcess') === false) { + // Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity()) + $iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time'); + $iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime; + // If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0 + if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) { + $iCurrTimeLimit = $iTaskLimit; + } + } + $sMessage = $oProcess->Process($iCurrTimeLimit); + $oTask->Set('running', 0); + } catch (MySQLHasGoneAwayException $e) { + throw $e; + } catch (ProcessFatalException $e) { + $oExceptionToThrow = $e; + } catch (Exception $e) { // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running + if ($oTask->IsDebug()) { + $sMessage = 'Processing failed with message: '. $e->getMessage() . '. ' . $e->getTraceAsString(); + } else { + $sMessage = 'Processing failed with message: '. $e->getMessage(); + } + } + $fDuration = microtime(true) - $fStart; + if ($oTask->Get('total_exec_count') == 0) { + // First execution + $oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s')); + } + $oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics + + // Update the timestamp since we want to be able to re-order the tasks based on the time they finished + $oDateEnded = new DateTime(); + $oTask->Set('latest_run_date', $oDateEnded->format('Y-m-d H:i:s')); + + if ($oRefClass->implementsInterface('iScheduledProcess')) { + // Schedules process do repeat at specific moments + $oPlannedStart = $oProcess->GetNextOccurrence(); + } else { + // Background processes do repeat periodically + $oPlannedStart = clone $oDatePlanned; + // Let's schedule from the previous planned date of execution to avoid shift + $oPlannedStart->modify($oProcess->GetPeriodicity().' seconds'); + $oEnd = new DateTime(); + while ($oPlannedStart->format('U') < $oEnd->format('U')) { + // Next planned start is already in the past, increase it again by a period + $oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds'); + } + } + + $oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s')); + $oTask->DBUpdate(); + + if ($oExceptionToThrow) { + throw $oExceptionToThrow; + } + + unset($oCtx); + + return $sMessage; } /** @@ -207,147 +184,128 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) * @throws \OQLException * @throws \ReflectionException */ -function CronExec($oP, $bVerbose, $bDebug=false) +function CronExec($oP, $bVerbose, $bDebug = false) { - $iStarted = time(); - $iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time'); - $iTimeLimit = $iStarted + $iMaxDuration; - $iCronSleep = MetaModel::GetConfig()->Get('cron_sleep'); - - if ($bVerbose) - { - $oP->p("Planned duration = $iMaxDuration seconds"); - $oP->p("Loop pause = $iCronSleep seconds"); - } - - ReSyncProcesses($oP, $bVerbose, $bDebug); - - while (time() < $iTimeLimit) - { - CheckMaintenanceMode($oP); - - $oNow = new DateTime(); - $sNow = $oNow->format('Y-m-d H:i:s'); - $oSearch = new DBObjectSearch('BackgroundTask'); - $oSearch->AddCondition('next_run_date', $sNow, '<='); - $oSearch->AddCondition('status', 'active'); - $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); - $bWorkDone = false; - - if ($oTasks->CountExceeds(0)) - { - $bWorkDone = true; - $aTasks = array(); - if ($bVerbose) - { - $sCount = $oTasks->Count(); - $oP->p("$sCount Tasks planned to run now ($sNow):"); - $oP->p('+---------------------------+---------+---------------------+---------------------+'); - $oP->p('| Task Class | Status | Last Run | Next Run |'); - $oP->p('+---------------------------+---------+---------------------+---------------------+'); - } - while ($oTask = $oTasks->Fetch()) - { - $aTasks[$oTask->Get('class_name')] = $oTask; - if ($bVerbose) - { - $sTaskName = $oTask->Get('class_name'); - $sStatus = $oTask->Get('status'); - $sLastRunDate = $oTask->Get('latest_run_date'); - $sNextRunDate = $oTask->Get('next_run_date'); - $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate)); - } - } - if ($bVerbose) - { - $oP->p('+---------------------------+---------+---------------------+---------------------+'); - } - $aRunTasks = []; - foreach ($aTasks as $oTask) - { - $sTaskClass = $oTask->Get('class_name'); - $aRunTasks[] = $sTaskClass; - - // N°3219 for each process will use a specific CMDBChange object with a specific track info - // Any BackgroundProcess can overrides this as needed - CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)"); - - // Run the task and record its next run time - if ($bVerbose) - { - $oNow = new DateTime(); - $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=49s", ' '.$sTaskClass.' ')); - } - try - { - $sMessage = RunTask($aTasks[$sTaskClass], $iTimeLimit); - } catch (MySQLHasGoneAwayException $e) - { - $oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")"); - exit(EXIT_CODE_FATAL); - } catch (ProcessFatalException $e) - { - $oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")"); - IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')'); - } - if ($bVerbose) - { - if (!empty($sMessage)) - { - $oP->p("$sTaskClass: $sMessage"); - } - $oEnd = new DateTime(); - $sNextRunDate = $oTask->Get('next_run_date'); - $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=42s", ' '.$sTaskClass.' ')." Next: $sNextRunDate"); - } - if (time() > $iTimeLimit) - { - break 2; - } - CheckMaintenanceMode($oP); - } - - // Tasks to run later - if ($bVerbose) - { - $oP->p('--'); - $oSearch = new DBObjectSearch('BackgroundTask'); - $oSearch->AddCondition('next_run_date', $sNow, '>'); - $oSearch->AddCondition('status', 'active'); - $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); - while ($oTask = $oTasks->Fetch()) - { - if (!in_array($oTask->Get('class_name'), $aRunTasks)) - { - $oP->p(sprintf("-- Skipping task: %-'-40s", $oTask->Get('class_name').' ')." until: ".$oTask->Get('next_run_date')); - } - } - } - } - - if ($bVerbose && $bWorkDone) - { - $oP->p("Sleeping...\n"); - } - sleep($iCronSleep); - } - if ($bVerbose) - { - $oP->p(''); - DisplayStatus($oP, ['next_run_date' => true]); - $oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)"); - } + $iStarted = time(); + $iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time'); + $iTimeLimit = $iStarted + $iMaxDuration; + $iCronSleep = MetaModel::GetConfig()->Get('cron_sleep'); + + if ($bVerbose) { + $oP->p("Planned duration = $iMaxDuration seconds"); + $oP->p("Loop pause = $iCronSleep seconds"); + } + + ReSyncProcesses($oP, $bVerbose, $bDebug); + + while (time() < $iTimeLimit) { + CheckMaintenanceMode($oP); + + $oNow = new DateTime(); + $sNow = $oNow->format('Y-m-d H:i:s'); + $oSearch = new DBObjectSearch('BackgroundTask'); + $oSearch->AddCondition('next_run_date', $sNow, '<='); + $oSearch->AddCondition('status', 'active'); + $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); + $bWorkDone = false; + + if ($oTasks->CountExceeds(0)) { + $bWorkDone = true; + $aTasks = []; + if ($bVerbose) { + $sCount = $oTasks->Count(); + $oP->p("$sCount Tasks planned to run now ($sNow):"); + $oP->p('+---------------------------+---------+---------------------+---------------------+'); + $oP->p('| Task Class | Status | Last Run | Next Run |'); + $oP->p('+---------------------------+---------+---------------------+---------------------+'); + } + while ($oTask = $oTasks->Fetch()) { + $aTasks[$oTask->Get('class_name')] = $oTask; + if ($bVerbose) { + $sTaskName = $oTask->Get('class_name'); + $sStatus = $oTask->Get('status'); + $sLastRunDate = $oTask->Get('latest_run_date'); + $sNextRunDate = $oTask->Get('next_run_date'); + $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate)); + } + } + if ($bVerbose) { + $oP->p('+---------------------------+---------+---------------------+---------------------+'); + } + $aRunTasks = []; + foreach ($aTasks as $oTask) { + $sTaskClass = $oTask->Get('class_name'); + $aRunTasks[] = $sTaskClass; + + // N°3219 for each process will use a specific CMDBChange object with a specific track info + // Any BackgroundProcess can overrides this as needed + CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)"); + + // Run the task and record its next run time + if ($bVerbose) { + $oNow = new DateTime(); + $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=49s", ' '.$sTaskClass.' ')); + } + try { + $sMessage = RunTask($aTasks[$sTaskClass], $iTimeLimit); + } catch (MySQLHasGoneAwayException $e) { + $oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")"); + exit(EXIT_CODE_FATAL); + } catch (ProcessFatalException $e) { + $oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")"); + IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')'); + } + if ($bVerbose) { + if (!empty($sMessage)) { + $oP->p("$sTaskClass: $sMessage"); + } + $oEnd = new DateTime(); + $sNextRunDate = $oTask->Get('next_run_date'); + $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=42s", ' '.$sTaskClass.' ')." Next: $sNextRunDate"); + } + if (time() > $iTimeLimit) { + break 2; + } + CheckMaintenanceMode($oP); + } + + // Tasks to run later + if ($bVerbose) { + $oP->p('--'); + $oSearch = new DBObjectSearch('BackgroundTask'); + $oSearch->AddCondition('next_run_date', $sNow, '>'); + $oSearch->AddCondition('status', 'active'); + $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); + while ($oTask = $oTasks->Fetch()) { + if (!in_array($oTask->Get('class_name'), $aRunTasks)) { + $oP->p(sprintf("-- Skipping task: %-'-40s", $oTask->Get('class_name').' ')." until: ".$oTask->Get('next_run_date')); + } + } + } + } + + if ($bVerbose && $bWorkDone) { + $oP->p("Sleeping...\n"); + } + sleep($iCronSleep); + } + if ($bVerbose) { + $oP->p(''); + DisplayStatus($oP, ['next_run_date' => true]); + $oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)"); + } } /** * @param WebPage $oP */ -function CheckMaintenanceMode(Page $oP) { -// Verify files instead of reloading the full config each time - if (file_exists(MAINTENANCE_MODE_FILE) || file_exists(READONLY_MODE_FILE)) { - $oP->p("Maintenance detected, exiting"); - exit(EXIT_CODE_ERROR); - } +function CheckMaintenanceMode(Page $oP) +{ + // Verify files instead of reloading the full config each time + if (file_exists(MAINTENANCE_MODE_FILE) || file_exists(READONLY_MODE_FILE)) { + $oP->p("Maintenance detected, exiting"); + exit(EXIT_CODE_ERROR); + } } /** @@ -362,23 +320,29 @@ function CheckMaintenanceMode(Page $oP) { */ function DisplayStatus($oP, $aTaskOrderBy = []) { - $oSearch = new DBObjectSearch('BackgroundTask'); - $oTasks = new DBObjectSet($oSearch, $aTaskOrderBy); - $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); - $oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |'); - $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); - while ($oTask = $oTasks->Fetch()) - { - $sTaskName = $oTask->Get('class_name'); - $sStatus = $oTask->Get('status'); - $sLastRunDate = $oTask->Get('latest_run_date'); - $sNextRunDate = $oTask->Get('next_run_date'); - $iNbRun = (int)$oTask->Get('total_exec_count'); - $sAverageRunTime = $oTask->Get('average_run_duration'); - $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', $sTaskName, $sStatus, - $sLastRunDate, $sNextRunDate, $iNbRun, $sAverageRunTime)); - } - $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); + $oSearch = new DBObjectSearch('BackgroundTask'); + $oTasks = new DBObjectSet($oSearch, $aTaskOrderBy); + $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); + $oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |'); + $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); + while ($oTask = $oTasks->Fetch()) { + $sTaskName = $oTask->Get('class_name'); + $sStatus = $oTask->Get('status'); + $sLastRunDate = $oTask->Get('latest_run_date'); + $sNextRunDate = $oTask->Get('next_run_date'); + $iNbRun = (int)$oTask->Get('total_exec_count'); + $sAverageRunTime = $oTask->Get('average_run_duration'); + $oP->p(sprintf( + '| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', + $sTaskName, + $sStatus, + $sLastRunDate, + $sNextRunDate, + $iNbRun, + $sAverageRunTime + )); + } + $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); } /** @@ -397,99 +361,83 @@ function DisplayStatus($oP, $aTaskOrderBy = []) */ function ReSyncProcesses($oP, $bVerbose, $bDebug) { - // Enumerate classes implementing BackgroundProcess - // - $oSearch = new DBObjectSearch('BackgroundTask'); - $oTasks = new DBObjectSet($oSearch); - $aTasks = array(); - while ($oTask = $oTasks->Fetch()) - { - $aTasks[$oTask->Get('class_name')] = $oTask; - } - $oNow = new DateTime(); - - $aProcesses = array(); - foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProcess::class) as $sTaskClass) - { - $oProcess = new $sTaskClass; - $aProcesses[$sTaskClass] = $oProcess; - - // Create missing entry if needed - if (!array_key_exists($sTaskClass, $aTasks)) - { - // New entry, let's create a new BackgroundTask record, and plan the first execution - $oTask = new BackgroundTask(); - $oTask->SetDebug($bDebug); - $oTask->Set('class_name', $sTaskClass); - $oTask->Set('total_exec_count', 0); - $oTask->Set('min_run_duration', 99999.999); - $oTask->Set('max_run_duration', 0); - $oTask->Set('average_run_duration', 0); - $oRefClass = new ReflectionClass($sTaskClass); - if ($oRefClass->implementsInterface('iScheduledProcess')) - { - $oNextOcc = $oProcess->GetNextOccurrence(); - $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); - } - else - { - // Background processes do start asap, i.e. "now" - $oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); - } - if ($bVerbose) - { - $oP->p('Creating record for: '.$sTaskClass); - $oP->p('First execution planned at: '.$oTask->Get('next_run_date')); - } - $oTask->DBInsert(); - } - else - { - /** @var \BackgroundTask $oTask */ - $oTask = $aTasks[$sTaskClass]; - if ($oTask->Get('next_run_date') == '3000-01-01 00:00:00') - { - // check for rescheduled tasks - $oRefClass = new ReflectionClass($sTaskClass); - if ($oRefClass->implementsInterface('iScheduledProcess')) - { - $oNextOcc = $oProcess->GetNextOccurrence(); - $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); - $oTask->DBUpdate(); - } - } - // Reactivate task if necessary - if ($oTask->Get('status') == 'removed') - { - $oTask->Set('status', 'active'); - $oTask->DBUpdate(); - } - // task having a real class to execute - unset($aTasks[$sTaskClass]); - } - } - - // Remove all the tasks not having a valid class - foreach ($aTasks as $oTask) - { - $sTaskClass = $oTask->Get('class_name'); - if (!class_exists($sTaskClass)) - { - $oTask->Set('status', 'removed'); - $oTask->DBUpdate(); - } - } - - if ($bVerbose) - { - $aDisplayProcesses = array(); - foreach ($aProcesses as $oExecInstance) - { - $aDisplayProcesses[] = get_class($oExecInstance); - } - $sDisplayProcesses = implode(', ', $aDisplayProcesses); - $oP->p("Background processes: ".$sDisplayProcesses); - } + // Enumerate classes implementing BackgroundProcess + // + $oSearch = new DBObjectSearch('BackgroundTask'); + $oTasks = new DBObjectSet($oSearch); + $aTasks = []; + while ($oTask = $oTasks->Fetch()) { + $aTasks[$oTask->Get('class_name')] = $oTask; + } + $oNow = new DateTime(); + + $aProcesses = []; + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProcess::class) as $sTaskClass) { + $oProcess = new $sTaskClass(); + $aProcesses[$sTaskClass] = $oProcess; + + // Create missing entry if needed + if (!array_key_exists($sTaskClass, $aTasks)) { + // New entry, let's create a new BackgroundTask record, and plan the first execution + $oTask = new BackgroundTask(); + $oTask->SetDebug($bDebug); + $oTask->Set('class_name', $sTaskClass); + $oTask->Set('total_exec_count', 0); + $oTask->Set('min_run_duration', 99999.999); + $oTask->Set('max_run_duration', 0); + $oTask->Set('average_run_duration', 0); + $oRefClass = new ReflectionClass($sTaskClass); + if ($oRefClass->implementsInterface('iScheduledProcess')) { + $oNextOcc = $oProcess->GetNextOccurrence(); + $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); + } else { + // Background processes do start asap, i.e. "now" + $oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); + } + if ($bVerbose) { + $oP->p('Creating record for: '.$sTaskClass); + $oP->p('First execution planned at: '.$oTask->Get('next_run_date')); + } + $oTask->DBInsert(); + } else { + /** @var \BackgroundTask $oTask */ + $oTask = $aTasks[$sTaskClass]; + if ($oTask->Get('next_run_date') == '3000-01-01 00:00:00') { + // check for rescheduled tasks + $oRefClass = new ReflectionClass($sTaskClass); + if ($oRefClass->implementsInterface('iScheduledProcess')) { + $oNextOcc = $oProcess->GetNextOccurrence(); + $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); + $oTask->DBUpdate(); + } + } + // Reactivate task if necessary + if ($oTask->Get('status') == 'removed') { + $oTask->Set('status', 'active'); + $oTask->DBUpdate(); + } + // task having a real class to execute + unset($aTasks[$sTaskClass]); + } + } + + // Remove all the tasks not having a valid class + foreach ($aTasks as $oTask) { + $sTaskClass = $oTask->Get('class_name'); + if (!class_exists($sTaskClass)) { + $oTask->Set('status', 'removed'); + $oTask->DBUpdate(); + } + } + + if ($bVerbose) { + $aDisplayProcesses = []; + foreach ($aProcesses as $oExecInstance) { + $aDisplayProcesses[] = get_class($oExecInstance); + } + $sDisplayProcesses = implode(', ', $aDisplayProcesses); + $oP->p("Background processes: ".$sDisplayProcesses); + } } //////////////////////////////////////////////////////////////////////////////// @@ -500,117 +448,86 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) set_time_limit(0); // Some background actions may really take long to finish (like backup) $bIsModeCLI = utils::IsModeCLI(); -if ($bIsModeCLI) -{ - $oP = new CLIPage("iTop - cron"); +if ($bIsModeCLI) { + $oP = new CLIPage("iTop - cron"); - SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); -} -else -{ - $oP = new WebPage("iTop - cron"); + SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); +} else { + $oP = new WebPage("iTop - cron"); } -try -{ - utils::UseParamFile(); - - $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */); - $bDebug = utils::ReadParam('debug', false, true /* Allow CLI */); - - if ($bIsModeCLI) - { - // Next steps: - // specific arguments: 'csv file' - // - $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); - $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) - { - UserRights::Login($sAuthUser); // Login & set the user's language - } - else - { - $oP->p("Access wrong credentials ('$sAuthUser')"); - $oP->output(); - exit(EXIT_CODE_ERROR); - } - } - else - { - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - } - - if (!UserRights::IsAdministrator()) - { - $oP->p("Access restricted to administrators"); - $oP->Output(); - exit(EXIT_CODE_ERROR); - } - - - if (utils::ReadParam('status_only', false, true /* Allow CLI */)) - { - // Display status and exit - DisplayStatus($oP); - exit(0); - } - - require_once(APPROOT.'core/mutex.class.inc.php'); - $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')'); -} -catch (Exception $e) -{ - $oP->p("Error: ".$e->GetMessage()); - $oP->output(); - exit(EXIT_CODE_FATAL); +try { + utils::UseParamFile(); + + $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */); + $bDebug = utils::ReadParam('debug', false, true /* Allow CLI */); + + if ($bIsModeCLI) { + // Next steps: + // specific arguments: 'csv file' + // + $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); + $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + $oP->p("Access wrong credentials ('$sAuthUser')"); + $oP->output(); + exit(EXIT_CODE_ERROR); + } + } else { + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed + } + + if (!UserRights::IsAdministrator()) { + $oP->p("Access restricted to administrators"); + $oP->Output(); + exit(EXIT_CODE_ERROR); + } + + if (utils::ReadParam('status_only', false, true /* Allow CLI */)) { + // Display status and exit + DisplayStatus($oP); + exit(0); + } + + require_once(APPROOT.'core/mutex.class.inc.php'); + $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')'); +} catch (Exception $e) { + $oP->p("Error: ".$e->GetMessage()); + $oP->output(); + exit(EXIT_CODE_FATAL); } -try -{ - $oMutex = new iTopMutex('cron'); - if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) - { - $oP->p("A maintenance is ongoing"); - } - else - { - if ($oMutex->TryLock()) - { - CronExec($oP, $bVerbose, $bDebug); - } - else - { - // Exit silently - $oP->p("Already running..."); - } - } -} -catch (Exception $e) -{ - $oP->p("ERROR: '".$e->getMessage()."'"); - if ($bDebug) - { - // Might contain verb parameters such a password... - $oP->p($e->getTraceAsString()); - } -} -finally -{ - try - { - $oMutex->Unlock(); - } - catch (Exception $e) - { - $oP->p("ERROR: '".$e->getMessage()."'"); - if ($bDebug) - { - // Might contain verb parameters such a password... - $oP->p($e->getTraceAsString()); - } - } +try { + $oMutex = new iTopMutex('cron'); + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { + $oP->p("A maintenance is ongoing"); + } else { + if ($oMutex->TryLock()) { + CronExec($oP, $bVerbose, $bDebug); + } else { + // Exit silently + $oP->p("Already running..."); + } + } +} catch (Exception $e) { + $oP->p("ERROR: '".$e->getMessage()."'"); + if ($bDebug) { + // Might contain verb parameters such a password... + $oP->p($e->getTraceAsString()); + } +} finally { + try { + $oMutex->Unlock(); + } catch (Exception $e) { + $oP->p("ERROR: '".$e->getMessage()."'"); + if ($bDebug) { + // Might contain verb parameters such a password... + $oP->p($e->getTraceAsString()); + } + } } $oP->p("Exiting: ".time().' ('.date('Y-m-d H:i:s').')'); diff --git a/webservices/export-v2.php b/webservices/export-v2.php index a902bf0442..e505ca6c34 100644 --- a/webservices/export-v2.php +++ b/webservices/export-v2.php @@ -1,4 +1,5 @@ p('ERROR: '.utils::HtmlEntities($sErrorMessage)); - $oP->output(); - exit(EXIT_CODE_ERROR); - } - else - { - $oP = new WebPage("iTop - Export"); - $oP->add_http_headers(); - $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); - $oP->output(); - exit(EXIT_CODE_ERROR); - } + if (utils::IsModeCLI()) { + $oP = new CLIPage("iTop - Export"); + $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); + $oP->output(); + exit(EXIT_CODE_ERROR); + } else { + $oP = new WebPage("iTop - Export"); + $oP->add_http_headers(); + $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); + $oP->output(); + exit(EXIT_CODE_ERROR); + } } function ReportErrorAndUsage($sErrorMessage) { - if (utils::IsModeCLI()) - { - $oP = new CLIPage("iTop - Export"); - $oP->p('ERROR: '.$sErrorMessage); - Usage($oP); - $oP->output(); - exit(EXIT_CODE_ERROR); - } - else { - $oP = new WebPage("iTop - Export"); - $oP->add_http_headers(); - $oP->p('ERROR: '.$sErrorMessage); - Usage($oP); - $oP->output(); - exit(EXIT_CODE_ERROR); - } + if (utils::IsModeCLI()) { + $oP = new CLIPage("iTop - Export"); + $oP->p('ERROR: '.$sErrorMessage); + Usage($oP); + $oP->output(); + exit(EXIT_CODE_ERROR); + } else { + $oP = new WebPage("iTop - Export"); + $oP->add_http_headers(); + $oP->p('ERROR: '.$sErrorMessage); + Usage($oP); + $oP->output(); + exit(EXIT_CODE_ERROR); + } } function Usage(Page $oP) { - if (Utils::IsModeCLI()) - { - $oP->p('Usage: php '.basename(__FILE__).' --auth_user= --auth_pwd= --expression= --query= [--arg_xxx=] [--no_localize=0|1] [--format=] [--format-options...]'); - $oP->p("Parameters:"); - $oP->p(" * auth_user: the iTop user account for authentication"); - $oP->p(" * auth_pwd: the password of the iTop user account"); - } - else - { - $oP->p("Parameters:"); - } - $oP->p(" * expression: an OQL expression (e.g. SELECT Contact WHERE name LIKE 'm%')"); - $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); - if (Utils::IsModeCLI()) - { - $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); - } - else - { - $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); - } - $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); - $aSupportedFormats = BulkExport::FindSupportedFormats(); - $oP->p(" * format: (optional, default is html) the desired output format. Can be one of '".implode("', '", array_keys($aSupportedFormats))."'"); - foreach($aSupportedFormats as $sFormatCode => $sLabel) - { - $oExporter = BulkExport::FindExporter($sFormatCode); - if ($oExporter !== null) - { - if (!Utils::IsModeCLI()) - { - $oP->add('
'); - } - $oExporter->DisplayUsage($oP); - if (!Utils::IsModeCLI()) - { - $oP->add(''); - } - } - } - //if (!Utils::IsModeCLI()) - //{ - // $oP->add(''); - //} + if (Utils::IsModeCLI()) { + $oP->p('Usage: php '.basename(__FILE__).' --auth_user= --auth_pwd= --expression= --query= [--arg_xxx=] [--no_localize=0|1] [--format=] [--format-options...]'); + $oP->p("Parameters:"); + $oP->p(" * auth_user: the iTop user account for authentication"); + $oP->p(" * auth_pwd: the password of the iTop user account"); + } else { + $oP->p("Parameters:"); + } + $oP->p(" * expression: an OQL expression (e.g. SELECT Contact WHERE name LIKE 'm%')"); + $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); + if (Utils::IsModeCLI()) { + $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); + } else { + $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); + } + $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); + $aSupportedFormats = BulkExport::FindSupportedFormats(); + $oP->p(" * format: (optional, default is html) the desired output format. Can be one of '".implode("', '", array_keys($aSupportedFormats))."'"); + foreach ($aSupportedFormats as $sFormatCode => $sLabel) { + $oExporter = BulkExport::FindExporter($sFormatCode); + if ($oExporter !== null) { + if (!Utils::IsModeCLI()) { + $oP->add('
'); + } + $oExporter->DisplayUsage($oP); + if (!Utils::IsModeCLI()) { + $oP->add(''); + } + } + } + //if (!Utils::IsModeCLI()) + //{ + // $oP->add(''); + //} } function DisplayExpressionForm(WebPage $oP, $sAction, $sExpression = '', $sExceptionMessage = '', $oForm = null) { - $oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('Core:BulkExport:ScopeDefinition')); - if ($oForm == null) { - $oForm = FormUIBlockFactory::MakeStandard('export-form'); - $oForm->SetAction($sAction); - $oP->AddSubBlock($oForm); - } - $oForm->AddSubBlock($oPanel); - - $oPanel->AddSubBlock(InputUIBlockFactory::MakeForHidden('interactive', '1')); - - $oFieldQuery = FieldUIBlockFactory::MakeStandard(''); - $oTextArea = new TextArea('expression', utils::EscapeHtml($sExpression), "textarea_oql", 70, 8); - $oTextArea->SetPlaceholder(Dict::S('Core:BulkExportQueryPlaceholder')); - $oTextArea->AddCSSClasses(["ibo-input-text", "ibo-query-oql", "ibo-is-code"]); - $oFieldQuery->AddSubBlock($oTextArea); - $oPanel->AddSubBlock($oFieldQuery); - if (!empty($sExceptionMessage)) { - $oAlert = AlertUIBlockFactory::MakeForFailure($sExceptionMessage); - $oAlert->SetIsCollapsible(false); - $oPanel->AddSubBlock($oAlert); - } - - $oFieldPhraseBook = FieldUIBlockFactory::MakeStandard(''); - $oSelect = SelectUIBlockFactory::MakeForSelect('query', "select_phrasebook"); - $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:SelectOne'), false)); - - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL'); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - while ($oQuery = $oQueries->Fetch()) { - $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($oQuery->GetKey(), $oQuery->Get('name'), false)); - } - $oFieldPhraseBook->AddSubBlock($oSelect); - $oPanel->AddSubBlock($oFieldPhraseBook); - - $oPanel->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Next'), "", "", true, "next-btn")); - $oP->p(''.Dict::S('Core:BulkExportCanRunNonInteractive').''); - $oP->p(''.Dict::S('Core:BulkExportLegacyExport').''); - $sJSEmptyOQL = json_encode(Dict::S('Core:BulkExportMessageEmptyOQL')); - $sJSEmptyQueryId = json_encode(Dict::S('Core:BulkExportMessageEmptyPhrasebookEntry')); - - $oP->add_ready_script( - <<SetAction($sAction); + $oP->AddSubBlock($oForm); + } + $oForm->AddSubBlock($oPanel); + + $oPanel->AddSubBlock(InputUIBlockFactory::MakeForHidden('interactive', '1')); + + $oFieldQuery = FieldUIBlockFactory::MakeStandard(''); + $oTextArea = new TextArea('expression', utils::EscapeHtml($sExpression), "textarea_oql", 70, 8); + $oTextArea->SetPlaceholder(Dict::S('Core:BulkExportQueryPlaceholder')); + $oTextArea->AddCSSClasses(["ibo-input-text", "ibo-query-oql", "ibo-is-code"]); + $oFieldQuery->AddSubBlock($oTextArea); + $oPanel->AddSubBlock($oFieldQuery); + if (!empty($sExceptionMessage)) { + $oAlert = AlertUIBlockFactory::MakeForFailure($sExceptionMessage); + $oAlert->SetIsCollapsible(false); + $oPanel->AddSubBlock($oAlert); + } + + $oFieldPhraseBook = FieldUIBlockFactory::MakeStandard(''); + $oSelect = SelectUIBlockFactory::MakeForSelect('query', "select_phrasebook"); + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:SelectOne'), false)); + + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL'); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + while ($oQuery = $oQueries->Fetch()) { + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($oQuery->GetKey(), $oQuery->Get('name'), false)); + } + $oFieldPhraseBook->AddSubBlock($oSelect); + $oPanel->AddSubBlock($oFieldPhraseBook); + + $oPanel->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Next'), "", "", true, "next-btn")); + $oP->p(''.Dict::S('Core:BulkExportCanRunNonInteractive').''); + $oP->p(''.Dict::S('Core:BulkExportLegacyExport').''); + $sJSEmptyOQL = json_encode(Dict::S('Core:BulkExportMessageEmptyOQL')); + $sJSEmptyQueryId = json_encode(Dict::S('Core:BulkExportMessageEmptyPhrasebookEntry')); + + $oP->add_ready_script( + <<add_script(DateTimeFormat::GetJSSQLToCustomFormat()); - $sJSDefaultDateTimeFormat = json_encode((string)AttributeDateTime::GetFormat()); - $oP->add_script( - <<add_script(DateTimeFormat::GetJSSQLToCustomFormat()); + $sJSDefaultDateTimeFormat = json_encode((string)AttributeDateTime::GetFormat()); + $oP->add_script( + <<LinkScriptFromAppRoot('js/tabularfieldsselector.js'); - $oP->LinkScriptFromAppRoot('js/jquery.dragtable.js'); - $oP->LinkStylesheetFromAppRoot('css/dragtable.css'); - - $oForm = FormUIBlockFactory::MakeStandard("export-form"); - $oForm->SetAction($sAction); - $oForm->AddDataAttribute("state", "not-yet-started"); - $oP->AddSubBlock($oForm); - - $bExpressionIsValid = true; - $sExpressionError = ''; - if (($sExpression === null) && ($sQueryId === null)) { - $bExpressionIsValid = false; - } else if ($sExpression !== '') { - try { - $oExportSearch = DBObjectSearch::FromOQL($sExpression); - $oExportSearch->UpdateContextFromUser(); - } - catch (OQLException $e) { - $bExpressionIsValid = false; - $sExpressionError = $e->getMessage(); - } - } - - if (!$bExpressionIsValid) { - DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError,$oForm); - - return; - } - - if ($sExpression !== '') { - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", $sExpression)); - $oExportSearch = DBObjectSearch::FromOQL($sExpression); - $oExportSearch->UpdateContextFromUser(); - } else { - $oQuery = MetaModel::GetObject('QueryOQL', $sQueryId); - $oExportSearch = DBObjectSearch::FromOQL($oQuery->Get('oql')); - $oExportSearch->UpdateContextFromUser(); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", $sQueryId)); - } - $aFormPartsByFormat = array(); - $aAllFormParts = array(); - if ($sFormat == null) { - // No specific format chosen - $sDefaultFormat = utils::ReadParam('format', 'xlsx'); - - - $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel("format", Dict::S('Core:BulkExport:ExportFormatPrompt'), "format_selector"); - $oSelect->SetIsLabelBefore(true); - $oForm->AddSubBlock($oSelect); - - $aSupportedFormats = BulkExport::FindSupportedFormats(); - asort($aSupportedFormats); - foreach ($aSupportedFormats as $sFormatCode => $sLabel) { - $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sFormatCode, $sLabel, ($sFormatCode == $sDefaultFormat))); - $oExporter = BulkExport::FindExporter($sFormatCode); - $oExporter->SetObjectList($oExportSearch); - $aParts = $oExporter->EnumFormParts(); - foreach ($aParts as $sPartId => $void) { - $aAllFormParts[$sPartId] = $oExporter; - } - $aFormPartsByFormat[$sFormatCode] = array_keys($aParts); - } - - } else { - // One specific format was chosen - $oSelect = InputUIBlockFactory::MakeForHidden("format", utils::EscapeHtml($sFormat)); - $oForm->AddSubBlock($oSelect); - - $oExporter = BulkExport::FindExporter($sFormat, $oExportSearch); - $aParts = $oExporter->EnumFormParts(); - foreach ($aParts as $sPartId => $void) { - $aAllFormParts[$sPartId] = $oExporter; - } - $aFormPartsByFormat[$sFormat] = array_keys($aAllFormParts); - } - foreach ($aAllFormParts as $sPartId => $oExport) { - $UIContentBlock = UIContentBlockUIBlockFactory::MakeStandard('form_part_'.$sPartId)->AddCSSClass('form_part'); - $oForm->AddSubBlock($UIContentBlock); - $UIContentBlock->AddSubBlock($oExport->GetFormPart($oP, $sPartId)); - } - //end of form - $oBlockExport = UIContentBlockUIBlockFactory::MakeStandard("export-feedback")->SetIsHidden(true); - $oBlockExport->AddSubBlock(new Html('

'.Dict::S('ExcelExport:PreparingExport').'

')); - $oBlockExport->AddSubBlock(new Html('
')); - $oP->AddSubBlock($oBlockExport); - if ($sFormat == null) {//if it's global export - $oP->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction('export', Dict::S('UI:Button:Export'), 'export', false, 'export-btn')); - } - $oBlockResult = UIContentBlockUIBlockFactory::MakeStandard("export_text_result")->SetIsHidden(true); - $oBlockResult->AddSubBlock(new Html(Dict::S('Core:BulkExport:ExportResult'))); - - $oTextArea = new TextArea('export_content', '', 'export_content'); - $oTextArea->AddCSSClass('ibo-input-text--export'); - $oBlockResult->AddSubBlock($oTextArea); - $oP->AddSubBlock($oBlockResult); - - $sJSParts = json_encode($aFormPartsByFormat); - $oP->add_ready_script( - <<LinkScriptFromAppRoot('js/tabularfieldsselector.js'); + $oP->LinkScriptFromAppRoot('js/jquery.dragtable.js'); + $oP->LinkStylesheetFromAppRoot('css/dragtable.css'); + + $oForm = FormUIBlockFactory::MakeStandard("export-form"); + $oForm->SetAction($sAction); + $oForm->AddDataAttribute("state", "not-yet-started"); + $oP->AddSubBlock($oForm); + + $bExpressionIsValid = true; + $sExpressionError = ''; + if (($sExpression === null) && ($sQueryId === null)) { + $bExpressionIsValid = false; + } elseif ($sExpression !== '') { + try { + $oExportSearch = DBObjectSearch::FromOQL($sExpression); + $oExportSearch->UpdateContextFromUser(); + } catch (OQLException $e) { + $bExpressionIsValid = false; + $sExpressionError = $e->getMessage(); + } + } + + if (!$bExpressionIsValid) { + DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError, $oForm); + + return; + } + + if ($sExpression !== '') { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", $sExpression)); + $oExportSearch = DBObjectSearch::FromOQL($sExpression); + $oExportSearch->UpdateContextFromUser(); + } else { + $oQuery = MetaModel::GetObject('QueryOQL', $sQueryId); + $oExportSearch = DBObjectSearch::FromOQL($oQuery->Get('oql')); + $oExportSearch->UpdateContextFromUser(); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", $sQueryId)); + } + $aFormPartsByFormat = []; + $aAllFormParts = []; + if ($sFormat == null) { + // No specific format chosen + $sDefaultFormat = utils::ReadParam('format', 'xlsx'); + + $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel("format", Dict::S('Core:BulkExport:ExportFormatPrompt'), "format_selector"); + $oSelect->SetIsLabelBefore(true); + $oForm->AddSubBlock($oSelect); + + $aSupportedFormats = BulkExport::FindSupportedFormats(); + asort($aSupportedFormats); + foreach ($aSupportedFormats as $sFormatCode => $sLabel) { + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sFormatCode, $sLabel, ($sFormatCode == $sDefaultFormat))); + $oExporter = BulkExport::FindExporter($sFormatCode); + $oExporter->SetObjectList($oExportSearch); + $aParts = $oExporter->EnumFormParts(); + foreach ($aParts as $sPartId => $void) { + $aAllFormParts[$sPartId] = $oExporter; + } + $aFormPartsByFormat[$sFormatCode] = array_keys($aParts); + } + + } else { + // One specific format was chosen + $oSelect = InputUIBlockFactory::MakeForHidden("format", utils::EscapeHtml($sFormat)); + $oForm->AddSubBlock($oSelect); + + $oExporter = BulkExport::FindExporter($sFormat, $oExportSearch); + $aParts = $oExporter->EnumFormParts(); + foreach ($aParts as $sPartId => $void) { + $aAllFormParts[$sPartId] = $oExporter; + } + $aFormPartsByFormat[$sFormat] = array_keys($aAllFormParts); + } + foreach ($aAllFormParts as $sPartId => $oExport) { + $UIContentBlock = UIContentBlockUIBlockFactory::MakeStandard('form_part_'.$sPartId)->AddCSSClass('form_part'); + $oForm->AddSubBlock($UIContentBlock); + $UIContentBlock->AddSubBlock($oExport->GetFormPart($oP, $sPartId)); + } + //end of form + $oBlockExport = UIContentBlockUIBlockFactory::MakeStandard("export-feedback")->SetIsHidden(true); + $oBlockExport->AddSubBlock(new Html('

'.Dict::S('ExcelExport:PreparingExport').'

')); + $oBlockExport->AddSubBlock(new Html('
')); + $oP->AddSubBlock($oBlockExport); + if ($sFormat == null) {//if it's global export + $oP->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction('export', Dict::S('UI:Button:Export'), 'export', false, 'export-btn')); + } + $oBlockResult = UIContentBlockUIBlockFactory::MakeStandard("export_text_result")->SetIsHidden(true); + $oBlockResult->AddSubBlock(new Html(Dict::S('Core:BulkExport:ExportResult'))); + + $oTextArea = new TextArea('export_content', '', 'export_content'); + $oTextArea->AddCSSClass('ibo-input-text--export'); + $oBlockResult->AddSubBlock($oTextArea); + $oP->AddSubBlock($oBlockResult); + + $sJSParts = json_encode($aFormPartsByFormat); + $oP->add_ready_script( + <<add('
'); - $oP->add_ready_script( - <<add('
'); + $oP->add_ready_script( + <<SetBreadCrumbEntry('ui-tool-export', Dict::S('Menu:ExportMenu'), Dict::S('Menu:ExportMenu+'), '', 'fas fa-file-export', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); - } - - if ($sExpression === null) { - // No expression supplied, let's check if phrasebook entry is given - if ($sQueryId !== null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - } else { - ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); - } - } else { - if (utils::IsModeCLI()) { - Usage($oP); - ReportErrorAndExit("No expression or query phrasebook identifier supplied."); - } else { - // form to enter an OQL query or pick a query phrasebook identifier - DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); - $oP->output(); - exit; - } - } - } - - - if ($sFormat !== null) { - $oExporter = BulkExport::FindExporter($sFormat); - if ($oExporter === null) { - $aSupportedFormats = BulkExport::FindSupportedFormats(); - ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); - } else { - DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); - } - } else { - DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); - } - if ($sMode == 'dialog') { - $oP->add('
'); - } - $oP->output(); + ); + } else { + $oP = new iTopWebPage('iTop Export'); + $oP->SetBreadCrumbEntry('ui-tool-export', Dict::S('Menu:ExportMenu'), Dict::S('Menu:ExportMenu+'), '', 'fas fa-file-export', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); + } + + if ($sExpression === null) { + // No expression supplied, let's check if phrasebook entry is given + if ($sQueryId !== null) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + } else { + ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); + } + } else { + if (utils::IsModeCLI()) { + Usage($oP); + ReportErrorAndExit("No expression or query phrasebook identifier supplied."); + } else { + // form to enter an OQL query or pick a query phrasebook identifier + DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); + $oP->output(); + exit; + } + } + } + + if ($sFormat !== null) { + $oExporter = BulkExport::FindExporter($sFormat); + if ($oExporter === null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + } else { + DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); + } + } else { + DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); + } + if ($sMode == 'dialog') { + $oP->add('
'); + } + $oP->output(); } /** @@ -439,117 +422,103 @@ function InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode) */ function CheckParameters($sExpression, $sQueryId, $sFormat) { - $oExporter = null; - $oQuery = null; - - if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { - ReportErrorAndExit("The user account is not authorized to access the archives"); - } - - if (($sExpression === null) && ($sQueryId === null)) { - ReportErrorAndUsage("Missing parameter. The parameter 'expression' or 'query' must be specified."); - } - - // Either $sExpression or $sQueryId must be specified - if ($sExpression === null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - } else { - ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); - } - } - if ($sFormat === null) { - ReportErrorAndUsage("Missing parameter 'format'."); - } - - // Check if the supplied query is valid (and all the parameters are supplied - try { - $oSearch = DBObjectSearch::FromOQL($sExpression); - $oSearch->UpdateContextFromUser(); - $aArgs = array(); - foreach ($oSearch->GetQueryParams() as $sParam => $foo) { - $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) { - $aArgs[$sParam] = $value; - } else { - throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); - } - } - $oSearch->SetInternalParams($aArgs); - - $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); - $oExporter = BulkExport::FindExporter($sFormat, $oSearch); - if ($oExporter == null) - { - $aSupportedFormats = BulkExport::FindSupportedFormats(); - ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); - } - } - catch(MissingQueryArgument $e) - { - $oSearch = null; - ReportErrorAndUsage("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); - } - catch(OQLException $e) - { - $oSearch = null; - ReportErrorAndExit("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); - } - catch(Exception $e) - { - $oSearch = null; - ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); - } - - // update last export information if check parameters ok - if($oQuery != null){ - $oQuery->UpdateLastExportInformation(); - } - - $oExporter->SetFormat($sFormat); - $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); - $oExporter->SetObjectList($oSearch); - $oExporter->ReadParameters(); - - return $oExporter; + $oExporter = null; + $oQuery = null; + + if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { + ReportErrorAndExit("The user account is not authorized to access the archives"); + } + + if (($sExpression === null) && ($sQueryId === null)) { + ReportErrorAndUsage("Missing parameter. The parameter 'expression' or 'query' must be specified."); + } + + // Either $sExpression or $sQueryId must be specified + if ($sExpression === null) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + } else { + ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); + } + } + if ($sFormat === null) { + ReportErrorAndUsage("Missing parameter 'format'."); + } + + // Check if the supplied query is valid (and all the parameters are supplied + try { + $oSearch = DBObjectSearch::FromOQL($sExpression); + $oSearch->UpdateContextFromUser(); + $aArgs = []; + foreach ($oSearch->GetQueryParams() as $sParam => $foo) { + $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); + if (!is_null($value)) { + $aArgs[$sParam] = $value; + } else { + throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); + } + } + $oSearch->SetInternalParams($aArgs); + + $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); + $oExporter = BulkExport::FindExporter($sFormat, $oSearch); + if ($oExporter == null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + } + } catch (MissingQueryArgument $e) { + $oSearch = null; + ReportErrorAndUsage("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); + } catch (OQLException $e) { + $oSearch = null; + ReportErrorAndExit("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); + } catch (Exception $e) { + $oSearch = null; + ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); + } + + // update last export information if check parameters ok + if ($oQuery != null) { + $oQuery->UpdateLastExportInformation(); + } + + $oExporter->SetFormat($sFormat); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + $oExporter->SetObjectList($oSearch); + $oExporter->ReadParameters(); + + return $oExporter; } function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) { - $oExporter->SetHttpHeaders($oP); - $exportResult = $oExporter->GetHeader(); - $aStatus = array(); - do - { - $exportResult .= $oExporter->GetNextChunk($aStatus); - } - while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'error') - { - $oExporter->Cleanup(); - ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); - } - else - { - $exportResult .= $oExporter->GetFooter(); - $sMimeType = $oExporter->GetMimeType(); - if (substr($sMimeType, 0, 5) == 'text/') - { - $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); - } - $oP->SetContentType($sMimeType); - $oP->SetContentDisposition('attachment', $oExporter->GetDownloadFileName()); - $oP->add($exportResult); - $oExporter->Cleanup(); - } + $oExporter->SetHttpHeaders($oP); + $exportResult = $oExporter->GetHeader(); + $aStatus = []; + do { + $exportResult .= $oExporter->GetNextChunk($aStatus); + } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'error') { + $oExporter->Cleanup(); + ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); + } else { + $exportResult .= $oExporter->GetFooter(); + $sMimeType = $oExporter->GetMimeType(); + if (substr($sMimeType, 0, 5) == 'text/') { + $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); + } + $oP->SetContentType($sMimeType); + $oP->SetContentDisposition('attachment', $oExporter->GetDownloadFileName()); + $oP->add($exportResult); + $oExporter->Cleanup(); + } } - ///////////////////////////////////////////////////////////////////////////// // // Command Line mode @@ -562,116 +531,103 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) $oCtx = new ContextTag(ContextTag::TAG_EXPORT); if (utils::IsModeCLI()) { - SetupUtils::CheckPhpAndExtensionsForCli(new CLIPage('iTop - Export')); - - try { - // Do this before loging, in order to allow setting user credentials from within the file - utils::UseParamFile(); - } - catch (Exception $e) { - echo "Error: ".utils::HtmlEntities($e->getMessage())."
\n"; - exit(EXIT_CODE_FATAL); - } - - $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); - $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); - if ($sAuthUser == null) { - ReportErrorAndUsage("Missing parameter '--auth_user'"); - } - if ($sAuthPwd == null) { - ReportErrorAndUsage("Missing parameter '--auth_pwd'"); - } - - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { - UserRights::Login($sAuthUser); // Login & set the user's language - } else { - ReportErrorAndExit("Access restricted or wrong credentials for user '$sAuthUser'"); - } - - $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); - $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); - $bLocalize = (utils::ReadParam('no_localize', 0) != 1); - if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { - ReportErrorAndExit("The user account is not authorized to access the archives"); - } - - if (($sExpression == null) && ($sQueryId == null)) { - ReportErrorAndUsage("Missing parameter. At least one of '--expression' or '--query' must be specified."); - } - - if ($sExpression === null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - } else { - ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); - } - } - try { - $oSearch = DBObjectSearch::FromOQL($sExpression); - $oSearch->UpdateContextFromUser(); - $aArgs = array(); - foreach ($oSearch->GetQueryParams() as $sParam => $foo) { - $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) { - $aArgs[$sParam] = $value; - } else { - throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); - } - } - $oSearch->SetInternalParams($aArgs); - - $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); - $oExporter = BulkExport::FindExporter($sFormat); - if ($oExporter == null) - { - $aSupportedFormats = BulkExport::FindSupportedFormats(); - ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); - } - - $oExporter->SetFormat($sFormat); - $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); - $oExporter->SetObjectList($oSearch); - $oExporter->ReadParameters(); - - $exportResult = $oExporter->GetHeader(); - $aStatus = array(); - - do - { - $exportResult .= $oExporter->GetNextChunk($aStatus); - } - while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'error') - { - ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); - } - else - { - $exportResult .= $oExporter->GetFooter(); - echo $exportResult; - } - $oExporter->Cleanup(); - - } - catch(MissingQueryArgument $e) - { - ReportErrorAndUsage("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); - } - catch(OQLException $e) - { - ReportErrorAndExit("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); - } - catch(Exception $e) - { - ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); - } - - exit; + SetupUtils::CheckPhpAndExtensionsForCli(new CLIPage('iTop - Export')); + + try { + // Do this before loging, in order to allow setting user credentials from within the file + utils::UseParamFile(); + } catch (Exception $e) { + echo "Error: ".utils::HtmlEntities($e->getMessage())."
\n"; + exit(EXIT_CODE_FATAL); + } + + $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); + $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); + if ($sAuthUser == null) { + ReportErrorAndUsage("Missing parameter '--auth_user'"); + } + if ($sAuthPwd == null) { + ReportErrorAndUsage("Missing parameter '--auth_pwd'"); + } + + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + ReportErrorAndExit("Access restricted or wrong credentials for user '$sAuthUser'"); + } + + $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); + $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); + $bLocalize = (utils::ReadParam('no_localize', 0) != 1); + if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { + ReportErrorAndExit("The user account is not authorized to access the archives"); + } + + if (($sExpression == null) && ($sQueryId == null)) { + ReportErrorAndUsage("Missing parameter. At least one of '--expression' or '--query' must be specified."); + } + + if ($sExpression === null) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + } else { + ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); + } + } + try { + $oSearch = DBObjectSearch::FromOQL($sExpression); + $oSearch->UpdateContextFromUser(); + $aArgs = []; + foreach ($oSearch->GetQueryParams() as $sParam => $foo) { + $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); + if (!is_null($value)) { + $aArgs[$sParam] = $value; + } else { + throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); + } + } + $oSearch->SetInternalParams($aArgs); + + $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); + $oExporter = BulkExport::FindExporter($sFormat); + if ($oExporter == null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + } + + $oExporter->SetFormat($sFormat); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + $oExporter->SetObjectList($oSearch); + $oExporter->ReadParameters(); + + $exportResult = $oExporter->GetHeader(); + $aStatus = []; + + do { + $exportResult .= $oExporter->GetNextChunk($aStatus); + } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'error') { + ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); + } else { + $exportResult .= $oExporter->GetFooter(); + echo $exportResult; + } + $oExporter->Cleanup(); + + } catch (MissingQueryArgument $e) { + ReportErrorAndUsage("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); + } catch (OQLException $e) { + ReportErrorAndExit("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); + } catch (Exception $e) { + ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); + } + + exit; } ///////////////////////////////////////////////////////////////////////////// @@ -680,59 +636,56 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) // ///////////////////////////////////////////////////////////////////////////// -try -{ - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - - // Main parameters - $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); - $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); - $sFormat = utils::ReadParam('format', null, true /* Allow CLI */); - $sFileName = utils::ReadParam('filename', '', true, 'string'); - $bInteractive = utils::ReadParam('interactive', false); - $sMode = utils::ReadParam('mode', ''); - - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - - ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); - - if ($bInteractive) { - InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode); - } else { - $oExporter = CheckParameters($sExpression, $sQueryId, $sFormat); - $sMimeType = $oExporter->GetMimeType(); - if ($sMimeType == 'text/html') { - // Note: Using NiceWebPage only for HTML export as it includes JS scripts & files, which makes no sense in other export formats. More over, it breaks Excel spreadsheet import. - if ($oExporter instanceof HTMLBulkExport) { - $oP = new NiceWebPage('iTop export'); - $oP->add_http_headers(); - $oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});"); - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); - } else { - $oP = new WebPage('iTop export'); - $oP->add_http_headers(); - $oP->add_style("table br { mso-data-placement:same-cell; }"); // Trick for Excel: keep line breaks inside the same cell ! - } - $oP->add_style("body { overflow: auto; }"); - } else { - $oP = new DownloadPage('iTop export'); - $oP->SetContentType($oExporter->GetMimeType()); - } - DoExport($oP, $oExporter, false); - $oP->output(); - } -} -catch (BulkExportMissingParameterException $e) { - $oP = new AjaxPage('iTop Export'); - $oP->add(utils::HtmlEntities($e->getMessage())); - Usage($oP); - $oP->output(); -} -catch (Exception $e) { - $oP = new WebPage('iTop Export'); - $oP->add_http_headers(); - $oP->add('Error: '.utils::HtmlEntities($e->getMessage())); - IssueLog::Error(utils::HtmlEntities($e->getMessage())."\n".$e->getTraceAsString()); - $oP->output(); +try { + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + + // Main parameters + $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); + $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); + $sFormat = utils::ReadParam('format', null, true /* Allow CLI */); + $sFileName = utils::ReadParam('filename', '', true, 'string'); + $bInteractive = utils::ReadParam('interactive', false); + $sMode = utils::ReadParam('mode', ''); + + LoginWebPage::DoLogin(); // Check user rights and prompt if needed + + ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); + + if ($bInteractive) { + InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode); + } else { + $oExporter = CheckParameters($sExpression, $sQueryId, $sFormat); + $sMimeType = $oExporter->GetMimeType(); + if ($sMimeType == 'text/html') { + // Note: Using NiceWebPage only for HTML export as it includes JS scripts & files, which makes no sense in other export formats. More over, it breaks Excel spreadsheet import. + if ($oExporter instanceof HTMLBulkExport) { + $oP = new NiceWebPage('iTop export'); + $oP->add_http_headers(); + $oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});"); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); + } else { + $oP = new WebPage('iTop export'); + $oP->add_http_headers(); + $oP->add_style("table br { mso-data-placement:same-cell; }"); // Trick for Excel: keep line breaks inside the same cell ! + } + $oP->add_style("body { overflow: auto; }"); + } else { + $oP = new DownloadPage('iTop export'); + $oP->SetContentType($oExporter->GetMimeType()); + } + DoExport($oP, $oExporter, false); + $oP->output(); + } +} catch (BulkExportMissingParameterException $e) { + $oP = new AjaxPage('iTop Export'); + $oP->add(utils::HtmlEntities($e->getMessage())); + Usage($oP); + $oP->output(); +} catch (Exception $e) { + $oP = new WebPage('iTop Export'); + $oP->add_http_headers(); + $oP->add('Error: '.utils::HtmlEntities($e->getMessage())); + IssueLog::Error(utils::HtmlEntities($e->getMessage())."\n".$e->getTraceAsString()); + $oP->output(); } diff --git a/webservices/export.php b/webservices/export.php index 3a43ce4089..c35d4f1f3c 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -1,4 +1,5 @@ GetMessage()."
\n"; - exit(EXIT_CODE_FATAL); +try { + // Do this before loging, in order to allow setting user credentials from within the file + utils::UseParamFile(); +} catch (Exception $e) { + echo "Error: ".$e->GetMessage()."
\n"; + exit(EXIT_CODE_FATAL); } /** * @since 3.1.0 N°6047 */ $oCtx = new ContextTag(ContextTag::TAG_EXPORT); -if (utils::IsModeCLI()) -{ - $oP = new CLIPage("iTop - Export"); - SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); - - $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); - $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); - - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) - { - UserRights::Login($sAuthUser); // Login & set the user's language - } - else - { - $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); - $oP->output(); - exit(EXIT_CODE_ERROR); - } -} -else -{ - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed +if (utils::IsModeCLI()) { + $oP = new CLIPage("iTop - Export"); + SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); + + $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); + $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); + + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); + $oP->output(); + exit(EXIT_CODE_ERROR); + } +} else { + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed } ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); - $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); -if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) -{ - $oP = new CLIPage("iTop - Export"); - $oP->p("The user account is not authorized to access the archives"); - $oP->output(); - exit(EXIT_CODE_ERROR); +if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { + $oP = new CLIPage("iTop - Export"); + $oP->p("The user account is not authorized to access the archives"); + $oP->output(); + exit(EXIT_CODE_ERROR); } $bLocalize = (utils::ReadParam('no_localize', 0) != 1); @@ -108,287 +95,242 @@ $oQuery = null; -if (strlen($sExpression) == 0) -{ - $sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data')); - if (strlen($sQueryId) > 0) - { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) - { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - if (strlen($sFields) == 0) - { - $sFields = trim($oQuery->Get('fields')); - } - } - } +if (strlen($sExpression) == 0) { + $sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data')); + if (strlen($sQueryId) > 0) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + if (strlen($sFields) == 0) { + $sFields = trim($oQuery->Get('fields')); + } + } + } } $sFormat = strtolower(utils::ReadParam('format', 'html', true /* Allow CLI */)); - $aFields = explode(',', $sFields); // Clean the list of columns (empty it if every string is empty) -foreach($aFields as $index => $sField) -{ - $aFields[$index] = trim($sField); - if(strlen($aFields[$index]) == 0) - { - unset($aFields[$index]); - } +foreach ($aFields as $index => $sField) { + $aFields[$index] = trim($sField); + if (strlen($aFields[$index]) == 0) { + unset($aFields[$index]); + } } $oP = null; -if (!empty($sExpression)) -{ - try - { - $oFilter = DBObjectSearch::FromOQL($sExpression); - - // Check and adjust column names - // - $aAliasToFields = array(); - foreach($aFields as $index => $sField) - { - if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches)) - { - $sClassAlias = $aMatches[1]; - $sAttCode = $aMatches[2]; - } - else - { - $sClassAlias = $oFilter->GetClassAlias(); - $sAttCode = $sField; - // Disambiguate the class alias - $aFields[$index] = $sClassAlias.'.'.$sAttCode; - } - $aAliasToFields[$sClassAlias][] = $sAttCode; - - $sClass = $oFilter->GetClassName($sClassAlias); - if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - throw new CoreException("Invalid field specification $sField: $sAttCode is not a valid attribute for $sClass"); - } - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef instanceof AttributeSubItem) - { - $aAliasToFields[$sClassAlias][] = $oAttDef->GetParentAttCode(); - } - else if($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $aAliasToFields[$sClassAlias][] = $sKeyAttCode; - } - } - - // Read query parameters - // - $aArgs = array(); - foreach($oFilter->GetQueryParams() as $sParam => $foo) - { - $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) - { - $aArgs[$sParam] = $value; - } - } - $oFilter->SetInternalParams($aArgs); - foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass) - { - if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0) - { - throw new Exception("The current user does not have permission for exporting data of class $sClass"); - } - } - - // update last export information if check parameters ok - if($oQuery != null){ - $oQuery->UpdateLastExportInformation(); - } - - if ($oFilter) - { - $oSet = new CMDBObjectSet($oFilter, array(), $aArgs); - $oSet->OptimizeColumnLoad($aAliasToFields); - switch($sFormat) - { - case 'html': - $oP = new NiceWebPage("iTop - Export"); - $oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); - - // Integration within MS-Excel web queries + HTTPS + IIS: - // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS - // Then the fix is to force the reset of header values Pragma and Cache-control - header("Cache-control:", true); - header("Pragma:", true); - - // The HTML output is made for pages located in the /pages/ folder - // since this page is in a different folder, let's adjust the HTML 'base' attribute - // to make the relative hyperlinks in the page work - $sUrl = utils::GetAbsoluteUrlAppRoot(); - $oP->set_base($sUrl.'pages/'); - - if (count($aFields) > 0) { - $iSearch = array_search('id', $aFields); - if ($iSearch !== false) { - $bViewLink = true; - unset($aFields[$iSearch]); - } else { - $bViewLink = false; - } - $sFields = implode(',', $aFields); - $aExtraParams = array( - 'menu' => false, - 'toolkit_menu' => false, - 'display_limit' => false, - 'localize_values' => $bLocalize, - 'zlist' => false, - 'extra_fields' => $sFields, - 'view_link' => $bViewLink, - ); - } else { - $aExtraParams = array( - 'menu' => false, - 'toolkit_menu' => false, - 'display_limit' => false, - 'localize_values' => $bLocalize, - 'zlist' => 'details', - ); - } - - $oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams); - $oResultBlock->Display($oP, 'expresult'); - break; - - case 'csv': - $oP = new CSVPage("iTop - Export"); - $sFields = implode(',', $aFields); - $sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data'); - $sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize), $sCharset); - if ($sCharset == 'UTF-8') - { - $sOutputData = UTF8_BOM.$sCSVData; - } - else - { - $sOutputData = $sCSVData; - } - if ($sFileName == '') - { - // Plain text => Firefox will NOT propose to download the file - $oP->add_header("Content-type: text/plain; charset=$sCharset"); - } - else - { - $oP->add_header("Content-type: text/csv; charset=$sCharset"); - } - $oP->add($sOutputData); - break; - - case 'spreadsheet': - $oP = new WebPage("iTop - Export for spreadsheet"); - - // Integration within MS-Excel web queries + HTTPS + IIS: - // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS - // Then the fix is to force the reset of header values Pragma and Cache-control - header("Pragma:", true); - header("Cache-control:", true); - - $sFields = implode(',', $aFields); - $oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell ! - cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize)); - break; - - case 'xml': - $oP = new XMLPage("iTop - Export", true /* passthrough */); - cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, array('localize_values' => $bLocalize)); - break; - - case 'xlsx': - $oP = new AjaxPage(''); - $oExporter = new ExcelExporter(); - $oExporter->SetObjectList($oFilter); - - // Run the export by chunk of 1000 objects to limit memory usage - $oExporter->SetChunkSize(1000); - do - { - $aStatus = $oExporter->Run(); // process one chunk - } - while( ($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'done') - { - $oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - $oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx'); - $oP->add(file_get_contents($oExporter->GetExcelFilePath())); - $oExporter->Cleanup(); - } - else - { - $oP->add('Error, xlsx export failed: '.$aStatus['message']); - } - break; - - default: - $oP = new WebPage("iTop - Export"); - $oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml."); - } - } - } - catch(Exception $e) - { - $oP = new WebPage("iTop - Export"); - $oP->p("Error the query can not be executed."); - if ($e instanceof CoreException) - { - $oP->p($e->GetHtmlDesc()); - } - else - { - $oP->p($e->getMessage()); - } - } +if (!empty($sExpression)) { + try { + $oFilter = DBObjectSearch::FromOQL($sExpression); + + // Check and adjust column names + // + $aAliasToFields = []; + foreach ($aFields as $index => $sField) { + if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches)) { + $sClassAlias = $aMatches[1]; + $sAttCode = $aMatches[2]; + } else { + $sClassAlias = $oFilter->GetClassAlias(); + $sAttCode = $sField; + // Disambiguate the class alias + $aFields[$index] = $sClassAlias.'.'.$sAttCode; + } + $aAliasToFields[$sClassAlias][] = $sAttCode; + + $sClass = $oFilter->GetClassName($sClassAlias); + if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) { + throw new CoreException("Invalid field specification $sField: $sAttCode is not a valid attribute for $sClass"); + } + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef instanceof AttributeSubItem) { + $aAliasToFields[$sClassAlias][] = $oAttDef->GetParentAttCode(); + } elseif ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $aAliasToFields[$sClassAlias][] = $sKeyAttCode; + } + } + + // Read query parameters + // + $aArgs = []; + foreach ($oFilter->GetQueryParams() as $sParam => $foo) { + $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); + if (!is_null($value)) { + $aArgs[$sParam] = $value; + } + } + $oFilter->SetInternalParams($aArgs); + foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass) { + if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0) { + throw new Exception("The current user does not have permission for exporting data of class $sClass"); + } + } + + // update last export information if check parameters ok + if ($oQuery != null) { + $oQuery->UpdateLastExportInformation(); + } + + if ($oFilter) { + $oSet = new CMDBObjectSet($oFilter, [], $aArgs); + $oSet->OptimizeColumnLoad($aAliasToFields); + switch ($sFormat) { + case 'html': + $oP = new NiceWebPage("iTop - Export"); + $oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); + + // Integration within MS-Excel web queries + HTTPS + IIS: + // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS + // Then the fix is to force the reset of header values Pragma and Cache-control + header("Cache-control:", true); + header("Pragma:", true); + + // The HTML output is made for pages located in the /pages/ folder + // since this page is in a different folder, let's adjust the HTML 'base' attribute + // to make the relative hyperlinks in the page work + $sUrl = utils::GetAbsoluteUrlAppRoot(); + $oP->set_base($sUrl.'pages/'); + + if (count($aFields) > 0) { + $iSearch = array_search('id', $aFields); + if ($iSearch !== false) { + $bViewLink = true; + unset($aFields[$iSearch]); + } else { + $bViewLink = false; + } + $sFields = implode(',', $aFields); + $aExtraParams = [ + 'menu' => false, + 'toolkit_menu' => false, + 'display_limit' => false, + 'localize_values' => $bLocalize, + 'zlist' => false, + 'extra_fields' => $sFields, + 'view_link' => $bViewLink, + ]; + } else { + $aExtraParams = [ + 'menu' => false, + 'toolkit_menu' => false, + 'display_limit' => false, + 'localize_values' => $bLocalize, + 'zlist' => 'details', + ]; + } + + $oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams); + $oResultBlock->Display($oP, 'expresult'); + break; + + case 'csv': + $oP = new CSVPage("iTop - Export"); + $sFields = implode(',', $aFields); + $sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data'); + $sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize], $sCharset); + if ($sCharset == 'UTF-8') { + $sOutputData = UTF8_BOM.$sCSVData; + } else { + $sOutputData = $sCSVData; + } + if ($sFileName == '') { + // Plain text => Firefox will NOT propose to download the file + $oP->add_header("Content-type: text/plain; charset=$sCharset"); + } else { + $oP->add_header("Content-type: text/csv; charset=$sCharset"); + } + $oP->add($sOutputData); + break; + + case 'spreadsheet': + $oP = new WebPage("iTop - Export for spreadsheet"); + + // Integration within MS-Excel web queries + HTTPS + IIS: + // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS + // Then the fix is to force the reset of header values Pragma and Cache-control + header("Pragma:", true); + header("Cache-control:", true); + + $sFields = implode(',', $aFields); + $oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell ! + cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize]); + break; + + case 'xml': + $oP = new XMLPage("iTop - Export", true /* passthrough */); + cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, ['localize_values' => $bLocalize]); + break; + + case 'xlsx': + $oP = new AjaxPage(''); + $oExporter = new ExcelExporter(); + $oExporter->SetObjectList($oFilter); + + // Run the export by chunk of 1000 objects to limit memory usage + $oExporter->SetChunkSize(1000); + do { + $aStatus = $oExporter->Run(); // process one chunk + } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'done') { + $oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx'); + $oP->add(file_get_contents($oExporter->GetExcelFilePath())); + $oExporter->Cleanup(); + } else { + $oP->add('Error, xlsx export failed: '.$aStatus['message']); + } + break; + + default: + $oP = new WebPage("iTop - Export"); + $oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml."); + } + } + } catch (Exception $e) { + $oP = new WebPage("iTop - Export"); + $oP->p("Error the query can not be executed."); + if ($e instanceof CoreException) { + $oP->p($e->GetHtmlDesc()); + } else { + $oP->p($e->getMessage()); + } + } } -if (!$oP) -{ - // Display a short message about how to use this page - $bModeCLI = utils::IsModeCLI(); - if ($bModeCLI) - { - $oP = new CLIPage("iTop - Export"); - } - else - { - $oP = new WebPage("iTop - Export"); - } - $oP->p("General purpose export page."); - $oP->p("Parameters:"); - $oP->p(" * expression: an OQL expression (URL encoded if needed)"); - $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); - if (Utils::IsModeCLI()) { - $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); - } else { - $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); - } - $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); - $oP->p(" * format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv', 'xlsx' or 'xml'"); - $oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a comma"); - $oP->p(" * fields_advanced: (optional, no effect on XML/HTML formats ; ignored is fields is specified) If set to 1, the default list of fields will include the external keys and their reconciliation keys"); - $oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file"); +if (!$oP) { + // Display a short message about how to use this page + $bModeCLI = utils::IsModeCLI(); + if ($bModeCLI) { + $oP = new CLIPage("iTop - Export"); + } else { + $oP = new WebPage("iTop - Export"); + } + $oP->p("General purpose export page."); + $oP->p("Parameters:"); + $oP->p(" * expression: an OQL expression (URL encoded if needed)"); + $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); + if (Utils::IsModeCLI()) { + $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); + } else { + $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); + } + $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); + $oP->p(" * format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv', 'xlsx' or 'xml'"); + $oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a comma"); + $oP->p(" * fields_advanced: (optional, no effect on XML/HTML formats ; ignored is fields is specified) If set to 1, the default list of fields will include the external keys and their reconciliation keys"); + $oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file"); } -if ($sFileName != '') -{ - $oP->add_header('Content-Disposition: attachment; filename="'.$sFileName.'"'); +if ($sFileName != '') { + $oP->add_header('Content-Disposition: attachment; filename="'.$sFileName.'"'); } $oP->TrashUnexpectedOutput(); $oP->output(); -?> diff --git a/webservices/import.php b/webservices/import.php index dc009b8409..2c757fc945 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -1,4 +1,5 @@ array - ( - 'mandatory' => true, - 'modes' => 'cli', - 'default' => null, - 'description' => 'login (must have enough rights to create objects of the given class)', - ), - 'auth_pwd' => array - ( - 'mandatory' => true, - 'modes' => 'cli', - 'default' => null, - 'description' => 'password', - ), - 'class' => array - ( - 'mandatory' => true, - 'modes' => 'http,cli', - 'default' => null, - 'description' => 'class of loaded objects', - ), - 'csvdata' => array - ( - 'mandatory' => true, - 'modes' => 'http', - 'default' => null, - 'description' => 'data', - ), - 'csvfile' => array - ( - 'mandatory' => true, - 'modes' => 'cli', - 'default' => '', - 'description' => 'local data file, replaces csvdata if specified', - ), - 'charset' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'Character set encoding of the CSV data: UTF-8, ISO-8859-1, WINDOWS-1251, WINDOWS-1252, ISO-8859-15, If blank, then the charset is set to config(csv_file_default_charset)', - ), - 'date_format' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)', - ), - 'separator' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => ',', - 'description' => 'column separator in CSV data (1 char, or \'tab\')', - ), - 'qualifier' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '"', - 'description' => 'test qualifier in CSV data', - ), - 'output' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => 'summary', - 'description' => '[retcode] to return the count of lines in error, [summary] to return a concise report, [details] to get a detailed report (each line listed)', - ), +$aPageParams = +[ + 'auth_user' => + [ + 'mandatory' => true, + 'modes' => 'cli', + 'default' => null, + 'description' => 'login (must have enough rights to create objects of the given class)', + ], + 'auth_pwd' => + [ + 'mandatory' => true, + 'modes' => 'cli', + 'default' => null, + 'description' => 'password', + ], + 'class' => + [ + 'mandatory' => true, + 'modes' => 'http,cli', + 'default' => null, + 'description' => 'class of loaded objects', + ], + 'csvdata' => + [ + 'mandatory' => true, + 'modes' => 'http', + 'default' => null, + 'description' => 'data', + ], + 'csvfile' => + [ + 'mandatory' => true, + 'modes' => 'cli', + 'default' => '', + 'description' => 'local data file, replaces csvdata if specified', + ], + 'charset' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'Character set encoding of the CSV data: UTF-8, ISO-8859-1, WINDOWS-1251, WINDOWS-1252, ISO-8859-15, If blank, then the charset is set to config(csv_file_default_charset)', + ], + 'date_format' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)', + ], + 'separator' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => ',', + 'description' => 'column separator in CSV data (1 char, or \'tab\')', + ], + 'qualifier' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '"', + 'description' => 'test qualifier in CSV data', + ], + 'output' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => 'summary', + 'description' => '[retcode] to return the count of lines in error, [summary] to return a concise report, [details] to get a detailed report (each line listed)', + ], /* - 'reportlevel' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => 'errors|warnings|created|changed|unchanged', - 'description' => 'combination of flags to limit the detailed output', - ), + 'reportlevel' => array + ( + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => 'errors|warnings|created|changed|unchanged', + 'description' => 'combination of flags to limit the detailed output', + ), */ - 'reconciliationkeys' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'name of the columns used to identify existing objects and update them, or create a new one', - ), - 'simulate' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '0', - 'description' => 'If set to 1, then the load will not be executed, but the expected report will be produced', - ), - 'comment' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'Comment to be added into the change log', - ), - 'no_localize' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '0', - 'description' => 'If set to 0, then header and values are supposed to be localized in the language of the logged in user. Set to 1 to use internal attribute codes and values (enums)', - ), -); + 'reconciliationkeys' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'name of the columns used to identify existing objects and update them, or create a new one', + ], + 'simulate' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '0', + 'description' => 'If set to 1, then the load will not be executed, but the expected report will be produced', + ], + 'comment' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'Comment to be added into the change log', + ], + 'no_localize' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '0', + 'description' => 'If set to 0, then header and values are supposed to be localized in the language of the logged in user. Set to 1 to use internal attribute codes and values (enums)', + ], +]; function UsageAndExit($oP) { - global $aPageParams; - $bModeCLI = utils::IsModeCLI(); - - $oP->p("USAGE:\n"); - foreach($aPageParams as $sParam => $aParamData) - { - $aModes = explode(',', $aParamData['modes']); - if ($bModeCLI) - { - if (in_array('cli', $aModes)) - { - $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); - $oP->p("$sParam = $sDesc"); - } - } - else - { - if (in_array('http', $aModes)) - { - $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); - $oP->p("$sParam = $sDesc"); - } - } - } - $oP->output(); - exit; + global $aPageParams; + $bModeCLI = utils::IsModeCLI(); + + $oP->p("USAGE:\n"); + foreach ($aPageParams as $sParam => $aParamData) { + $aModes = explode(',', $aParamData['modes']); + if ($bModeCLI) { + if (in_array('cli', $aModes)) { + $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); + $oP->p("$sParam = $sDesc"); + } + } else { + if (in_array('http', $aModes)) { + $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); + $oP->p("$sParam = $sDesc"); + } + } + } + $oP->output(); + exit; } - function ReadParam($oP, $sParam, $sSanitizationFilter = 'parameter') { - global $aPageParams; - assert(isset($aPageParams[$sParam])); - assert(!$aPageParams[$sParam]['mandatory']); - $sValue = utils::ReadParam($sParam, $aPageParams[$sParam]['default'], true /* Allow CLI */, $sSanitizationFilter); - return trim($sValue); + global $aPageParams; + assert(isset($aPageParams[$sParam])); + assert(!$aPageParams[$sParam]['mandatory']); + $sValue = utils::ReadParam($sParam, $aPageParams[$sParam]['default'], true /* Allow CLI */, $sSanitizationFilter); + return trim($sValue); } function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) { - global $aPageParams; - assert(isset($aPageParams[$sParam])); - assert($aPageParams[$sParam]['mandatory']); - - $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); - if (is_null($sValue)) - { - $oP->p("ERROR: Missing argument '$sParam'\n"); - UsageAndExit($oP); - } - return trim($sValue); + global $aPageParams; + assert(isset($aPageParams[$sParam])); + assert($aPageParams[$sParam]['mandatory']); + + $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); + if (is_null($sValue)) { + $oP->p("ERROR: Missing argument '$sParam'\n"); + UsageAndExit($oP); + } + return trim($sValue); } ///////////////////////////////// @@ -213,60 +205,47 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) * @since 3.1.0 N°6047 */ $oCtx = new ContextTag(ContextTag::TAG_IMPORT); -if (utils::IsModeCLI()) -{ - $oP = new CLIPage("iTop - Bulk import"); - SetupUtils::CheckPhpAndExtensionsForCli($oP, -2); -} -else -{ - $oP = new CSVPage("iTop - Bulk import"); +if (utils::IsModeCLI()) { + $oP = new CLIPage("iTop - Bulk import"); + SetupUtils::CheckPhpAndExtensionsForCli($oP, -2); +} else { + $oP = new CSVPage("iTop - Bulk import"); } -try -{ - utils::UseParamFile(); -} -catch(Exception $e) -{ - $oP->p("Error: ".$e->GetMessage()); - $oP->output(); - exit(-2); +try { + utils::UseParamFile(); +} catch (Exception $e) { + $oP->p("Error: ".$e->GetMessage()); + $oP->output(); + exit(-2); } -if (utils::IsModeCLI()) -{ - // Next steps: - // specific arguments: 'csvfile' - // - $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); - $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); - $sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) - { - UserRights::Login($sAuthUser); // Login & set the user's language - } - else - { - $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); - $oP->output(); - exit(-1); - } - - if (!is_readable($sCsvFile)) - { - $oP->p("Input file could not be found or could not be read: '$sCsvFile'"); - $oP->output(); - exit(-1); - } - $sCSVData = file_get_contents($sCsvFile); +if (utils::IsModeCLI()) { + // Next steps: + // specific arguments: 'csvfile' + // + $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); + $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); + $sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data'); + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); + $oP->output(); + exit(-1); + } -} -else -{ - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + if (!is_readable($sCsvFile)) { + $oP->p("Input file could not be found or could not be read: '$sCsvFile'"); + $oP->output(); + exit(-1); + } + $sCSVData = file_get_contents($sCsvFile); + +} else { + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::ResetSession(true); - $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); if ($iRet !== LoginWebPage::EXIT_CODE_OK) { switch ($iRet) { case LoginWebPage::EXIT_CODE_MISSINGLOGIN: @@ -296,637 +275,530 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) exit -1; } - $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); + $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); } +try { + $aWarnings = []; + + ////////////////////////////////////////////////// + // + // Read parameters + // + $sClass = ReadMandatoryParam($oP, 'class', 'raw_data'); // do not filter as a valid class, we want to produce the report "wrong class" ourselves + $sSep = ReadParam($oP, 'separator', 'raw_data'); + $sQualifier = ReadParam($oP, 'qualifier', 'raw_data'); + $sCharSet = ReadParam($oP, 'charset', 'raw_data'); + $sDateFormat = ReadParam($oP, 'date_format', 'raw_data'); + if (strpos($sDateFormat, '%') !== false) { + $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat); + } + $sOutput = ReadParam($oP, 'output', 'string'); + $sReconcKeys = ReadParam($oP, 'reconciliationkeys', 'raw_data'); + $sSimulate = ReadParam($oP, 'simulate'); + $sComment = ReadParam($oP, 'comment', 'raw_data'); + $bLocalize = (ReadParam($oP, 'no_localize') != 1); + + if (strtolower(trim($sSep)) == 'tab') { + $sSep = "\t"; + } -try -{ - $aWarnings = array(); - - ////////////////////////////////////////////////// - // - // Read parameters - // - $sClass = ReadMandatoryParam($oP, 'class', 'raw_data'); // do not filter as a valid class, we want to produce the report "wrong class" ourselves - $sSep = ReadParam($oP, 'separator', 'raw_data'); - $sQualifier = ReadParam($oP, 'qualifier', 'raw_data'); - $sCharSet = ReadParam($oP, 'charset', 'raw_data'); - $sDateFormat = ReadParam($oP, 'date_format', 'raw_data'); - if (strpos($sDateFormat, '%') !== false) - { - $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat); - } - $sOutput = ReadParam($oP, 'output', 'string'); - $sReconcKeys = ReadParam($oP, 'reconciliationkeys', 'raw_data'); - $sSimulate = ReadParam($oP, 'simulate'); - $sComment = ReadParam($oP, 'comment', 'raw_data'); - $bLocalize = (ReadParam($oP, 'no_localize') != 1); - - if (strtolower(trim($sSep)) == 'tab') - { - $sSep = "\t"; - } - - ////////////////////////////////////////////////// - // - // Check parameters format/consistency - // - if (strlen($sCSVData) == 0) - { - throw new BulkLoadException("Missing data - at least one line is expected"); - } - - if (!MetaModel::IsValidClass($sClass)) - { - throw new BulkLoadException("Unknown class: '$sClass'"); - } - - if (strlen($sSep) > 1) - { - throw new BulkLoadException("Separator is limited to one character, found '$sSep'"); - } - - if (strlen($sQualifier) > 1) - { - throw new BulkLoadException("Text qualifier is limited to one character, found '$sQualifier'"); - } - - if (!in_array($sOutput, array('retcode', 'summary', 'details'))) - { - throw new BulkLoadException("Unknown output format: '$sOutput'"); - } - - if (strlen($sDateFormat) == 0) - { - $sDateFormat = null; - } - - if ($sCharSet == '') - { - $sCharSet = MetaModel::GetConfig()->Get('csv_file_default_charset'); - } - - if ($sSimulate == '1') - { - $bSimulate = true; - } - else - { - $bSimulate = false; - } - - if (($sOutput == "summary") || ($sOutput == 'details')) - { - $oP->add_comment("Output format: ".$sOutput); - $oP->add_comment("Class: ".$sClass); - $oP->add_comment("Separator: ".$sSep); - $oP->add_comment("Qualifier: ".$sQualifier); - $oP->add_comment("Charset Encoding:".$sCharSet); - if (($sDateFormat !== null) && (strlen($sDateFormat) > 0)) - { - $oP->add_comment("Date and time format: '$sDateFormat'"); - $oDateTimeFormat = new DateTimeFormat($sDateFormat); - $sDateOnlyFormat = $oDateTimeFormat->ToDateFormat(); - $oP->add_comment("Date format: '$sDateOnlyFormat'"); - } - else - { - $oP->add_comment("Date format: "); - } - $oP->add_comment("Localize: ".($bLocalize?'yes':'no')); - $oP->add_comment("Data Size: ".strlen($sCSVData)); - } - ////////////////////////////////////////////////// - // - // Security - // - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY)) - { - throw new SecurityException(Dict::Format('UI:Error:BulkModifyNotAllowedOn_Class', $sClass)); - } - - ////////////////////////////////////////////////// - // - // Create an index of the known column names (in lower case) - // If data is localized, an array of => array of (several leads to ambiguity) - // Otherwise an array of => array of (1 element by construction) - // - // Examples (localized in french): - // 'lieu' => 'location_id' - // 'lieu->name' => 'location_id->name' - // - // Note: it may happen that an external field has the same label as the external key - // in that case, we consider that the external key has precedence - // - $aKnownColumnNames = ['id'=>['id']]; - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - if ($bLocalize) - { - $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCode)); - } - else - { - $sColName = strtolower($sAttCode); - } - if (!$oAttDef->IsExternalField() || !array_key_exists($sColName, $aKnownColumnNames)) - { - $aKnownColumnNames[$sColName][] = $sAttCode; - } - if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) - { - $sRemoteClass = $oAttDef->GetTargetClass(); - foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) - { - $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; - if ($bLocalize) - { - $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCodeEx)); - } - else - { - $sColName = strtolower($sAttCodeEx); - } - if (!array_key_exists($sColName, $aKnownColumnNames)) - { - $aKnownColumnNames[$sColName][] = $sAttCodeEx; - } - } - } - } - - //print_r($aKnownColumnNames); - //print_r(array_keys($aKnownColumnNames)); - //exit; - - ////////////////////////////////////////////////// - // - // Parse first line, check attributes, analyse the request - // - if ($sCharSet == 'UTF-8') - { - // Remove the BOM if any - if (substr($sCSVData, 0, 3) == UTF8_BOM) - { - $sCSVData = substr($sCSVData, 3); - } - // Clean the input - // Todo: warn the user if some characters are lost/substituted - $sUTF8Data = iconv('UTF-8', 'UTF-8//IGNORE//TRANSLIT', $sCSVData); - } - else - { - $sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); - } - $oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier); - - // Limitation: as the attribute list is in the first line, we can not match external key by a third-party attribute - $aRawFieldList = $oCSVParser->ListFields(); - $iColCount = count($aRawFieldList); - - // Translate into internal names - $aFieldList = []; - foreach($aRawFieldList as $iFieldId => $sFieldName) - { - $sFieldName = trim($sFieldName); - $aMatches = array(); - if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches)) - { - // Ignore any trailing "star" (*) that simply indicates a mandatory field - $sFieldName = $aMatches[1]; - } - else if (preg_match('/^(.+)\*->(.+)$/', $sFieldName, $aMatches)) - { - // Remove any trailing "star" character before the arrow (->) - // A star character at the end can be used to indicate a mandatory field - $sFieldName = $aMatches[1].'->'.$aMatches[2]; - } - if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames)) - { - $aColumns = $aKnownColumnNames[strtolower($sFieldName)]; - if (count($aColumns) > 1) - { - $aCompetitors = array(); - foreach ($aColumns as $sAttCodeEx) - { - $aCompetitors[] = $sAttCodeEx; - } - $aWarnings[] = "Input column '$sFieldName' is ambiguous. Could be related to ".implode (' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; - } - $aFieldList[$iFieldId] = $aColumns[0]; - } - else - { - // Protect against XSS injection - $sSafeName = str_replace(array('"', '<', '>'), '', $sFieldName); - throw new BulkLoadException("Unknown column: '$sSafeName'. Possible columns: ".implode(', ', array_keys($aKnownColumnNames))); - } - } - // Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->') - - $aAttList = array(); - $aExtKeys = array(); - foreach($aFieldList as $iFieldId => $sFieldName) - { - $aMatches = array(); - if (preg_match('/^(.+)->(.+)$/', trim($sFieldName), $aMatches)) - { - // The column has been specified as "extkey->attcode" - // - $sExtKeyAttCode = $aMatches[1]; - $sRemoteAttCode = $aMatches[2]; - if (!MetaModel::IsValidAttCode($sClass, $sExtKeyAttCode)) - { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown attribute '$sExtKeyAttCode' (class: '$sClass')"); - } - $oAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); - if (!$oAtt->IsExternalKey()) - { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Not an external key '$sExtKeyAttCode' (class: '$sClass')"); - } - $sTargetClass = $oAtt->GetTargetClass(); - if (!MetaModel::IsValidAttCode($sTargetClass, $sRemoteAttCode)) - { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown attribute '$sRemoteAttCode' (key: '$sExtKeyAttCode', class: '$sTargetClass')"); - } - $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; - } - elseif ($sFieldName == 'id') - { - $aAttList[$sFieldName] = $iFieldId; - } - else - { - // The column has been specified as "attcode" - // - if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) - { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown attribute '$sFieldName' (class: '$sClass')"); - } - $oAtt = MetaModel::GetAttributeDef($sClass, $sFieldName); - if ($oAtt->IsExternalKey()) - { - $aExtKeys[$sFieldName]['id'] = $iFieldId; - $aAttList[$sFieldName] = $iFieldId; - } - elseif ($oAtt->IsExternalField()) - { - $sExtKeyAttCode = $oAtt->GetKeyAttCode(); - $sRemoteAttCode = $oAtt->GetExtAttCode(); - $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; - } - else - { - $aAttList[$sFieldName] = $iFieldId; - } - } - } - - // Make sure there are some reconciliation keys - // - if (empty($sReconcKeys)) - { - $aReconcSpec = array(); - // Base reconciliation scheme on the default one - // The reconciliation attributes not present in the data will be ignored - foreach(MetaModel::GetReconcKeys($sClass) as $sReconcKeyAttCode) - { - if (in_array($sReconcKeyAttCode, $aFieldList)) - { - if ($bLocalize) - { - $aReconcSpec[] = MetaModel::GetLabel($sClass, $sReconcKeyAttCode); - } - else - { - $aReconcSpec[] = $sReconcKeyAttCode; - } - } - } - if (count($aReconcSpec) == 0) - { - throw new BulkLoadException("No reconciliation scheme could be defined, please add a column corresponding to one defined reconciliation key (class: '$sClass', reconciliation:".implode(',', MetaModel::GetReconcKeys($sClass)).")"); - } - $sReconcKeys = implode(',', $aReconcSpec); - } - - // Interpret the list of reconciliation keys - // - $aFinalReconcilKeys = array(); - $aReconcilKeysReport = array(); - foreach (explode(',', $sReconcKeys) as $sReconcKey) - { - $sReconcKey = trim($sReconcKey); - if (empty($sReconcKey)) continue; // skip empty spec - - if (array_key_exists(strtolower($sReconcKey), $aKnownColumnNames)) - { - // Translate from a translated name to codes - $aColumns = $aKnownColumnNames[strtolower($sReconcKey)]; - if (count($aColumns) > 1) - { - $aCompetitors = array(); - foreach ($aColumns as $sAttCodeEx) - { - $aCompetitors[] = $sAttCodeEx; - } - $aWarnings[] = "Reconciliation key '$sReconcKey' is ambiguous. Could be related to ".implode (' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; - } - $sReconcKey = $aColumns[0]; - } - else - { - // Protect against XSS injection - $sSafeName = str_replace(array('"', '<', '>'), '', $sReconcKey); - throw new BulkLoadException("Unknown reconciliation key: '$sSafeName'"); - } - - // Check that the reconciliation key is either a given column, or an external key - if (!in_array($sReconcKey, $aFieldList)) - { - if (!array_key_exists($sReconcKey, $aExtKeys)) - { - // Protect against XSS injection - $sSafeName = str_replace(array('"', '<', '>'), '', $sReconcKey); - throw new BulkLoadException("Reconciliation key not found in the input columns: '$sSafeName'"); - } - } - - if (preg_match('/^(.+)->(.+)$/', trim($sReconcKey), $aMatches)) - { - // The column has been specified as "extkey->attcode" - // - $sExtKeyAttCode = $aMatches[1]; - $sRemoteAttCode = $aMatches[2]; - - $aFinalReconcilKeys[] = $sExtKeyAttCode; - $aReconcilKeysReport[$sExtKeyAttCode][] = $sRemoteAttCode; - } - else - { - if (!MetaModel::IsValidAttCode($sClass, $sReconcKey) && $sReconcKey != 'id') - { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown reconciliation attribute '$sReconcKey' (class: '$sClass')"); - } - if ($sReconcKey == 'id') { - $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey] = array(); - } else { - $oAtt = MetaModel::GetAttributeDef($sClass, $sReconcKey); - if ($oAtt->IsExternalKey()) { - $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey][] = 'id'; - } elseif ($oAtt->IsExternalField()) { - $sReconcAttCode = $oAtt->GetKeyAttCode(); - $sReconcKeyReport = "$sReconcAttCode ($sReconcKey)"; - - $aFinalReconcilKeys[] = $sReconcAttCode; - $aReconcilKeysReport[$sReconcAttCode][] = $sReconcKeyReport; - } else { - $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey] = array(); - } - } - } - } - - ////////////////////////////////////////////////// - // - // Go for parsing and interpretation - // - - $aData = $oCSVParser->ToArray(); - $iLineCount = count($aData); - - if (($sOutput == "summary") || ($sOutput == 'details')) - { - $oP->add_comment("Data Lines: ".$iLineCount); - $oP->add_comment("Simulate: ".($bSimulate ? '1' : '0')); - $oP->add_comment("Columns: ".implode(', ', $aFieldList)); - - $aReconciliationReport = array(); - foreach($aReconcilKeysReport as $sKey => $aKeyDetails) - { - if (count($aKeyDetails) > 0) - { - $aReconciliationReport[] = $sKey.' ('.implode(',', $aKeyDetails).')'; - } - else - { - $aReconciliationReport[] = $sKey; - } - } - $oP->add_comment("Reconciliation Keys: ".implode(', ', $aReconciliationReport)); - - foreach ($aWarnings as $sWarning) - { - $oP->add_comment("Warning: ".$sWarning); - } - } - - $oBulk = new BulkChange( - $sClass, - $aData, - $aAttList, - $aExtKeys, - $aFinalReconcilKeys, - null, // synchro scope - null, // on delete - $sDateFormat, - $bLocalize - ); - - if ($bSimulate) - { - $oMyChange = null; - } - else - { - if (strlen($sComment) > 0) { - $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; - } else { - $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV)'; - } - CMDBObject::SetCurrentChangeFromParams($sMoreInfo, 'csv-import.php'); - $oMyChange = CMDBObject::GetCurrentChange(); - } - - $aRes = $oBulk->Process($oMyChange); - - ////////////////////////////////////////////////// - // - // Compute statistics - // - $iCountErrors = 0; - $iCountWarnings = 0; - $iCountCreations = 0; - $iCountUpdates = 0; - $iCountUnchanged = 0; - foreach($aRes as $iRow => $aRowData) - { - $bWritten = false; - - $oStatus = $aRowData["__STATUS__"]; - switch(get_class($oStatus)) - { - case 'RowStatus_NoChange': - $iCountUnchanged++; - break; - case 'RowStatus_Modify': - $iCountUpdates++; - $bWritten = true; - break; - case 'RowStatus_NewObj': - $iCountCreations++; - $bWritten = true; - break; - case 'RowStatus_Issue': - $iCountErrors++; - break; - } - - if ($bWritten) - { - // Something has been done, still there may be some issues to report - foreach($aRowData as $key => $value) - { - if (!is_object($value)) continue; - - switch (get_class($value)) - { - case 'CellStatus_Void': - case 'CellStatus_Modify': - break; - case 'CellStatus_Issue': - case 'CellStatus_SearchIssue': - case 'CellStatus_NullIssue': - case 'CellStatus_Ambiguous': - $iCountWarnings++; - break; - } - } - } - } - - ////////////////////////////////////////////////// - // - // Summary of settings and results - // - if ($sOutput == 'retcode') - { - $oP->add($iCountErrors); - } - - if (($sOutput == "summary") || ($sOutput == 'details')) - { - $oP->add_comment("Change tracking comment: ".$sComment); - $oP->add_comment("Issues: ".$iCountErrors); - $oP->add_comment("Warnings: ".$iCountWarnings); - $oP->add_comment("Created: ".$iCountCreations); - $oP->add_comment("Updated: ".$iCountUpdates); - $oP->add_comment("Unchanged: ".$iCountUnchanged); - } - - - if ($sOutput == 'details') - { - // Setup result presentation - // - $aDisplayConfig = array(); - $aDisplayConfig["__LINE__"] = array("label"=>"Line", "description"=>""); - $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); - $aDisplayConfig["__OBJECT_CLASS__"] = array("label"=>"Object Class", "description"=>""); - $aDisplayConfig["__OBJECT_ID__"] = array("label"=>"Object Id", "description"=>""); - foreach($aExtKeys as $sExtKeyAttCode => $aRemoteAtt) - { - $sLabel = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode)->GetLabel(); - $aDisplayConfig["$sExtKeyAttCode"] = array("label"=>$sExtKeyAttCode, "description"=>$sLabel." - ext key"); - } - foreach($aFinalReconcilKeys as $iCol => $sAttCode) - { - // $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - // $aDisplayConfig["$iCol"] = array("label"=>"$sLabel", "description"=>""); - } - foreach ($aAttList as $sAttCode => $iCol) - { - if ($sAttCode == 'id') - { - $sLabel = Dict::S('UI:CSVImport:idField'); - - $aDisplayConfig["$iCol"] = array("label"=>$sAttCode, "description"=>$sLabel); - } - else - { - $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - $aDisplayConfig["$iCol"] = array("label"=>$sAttCode, "description"=>$sLabel); - } - } - - $aResultDisp = array(); // to be displayed - foreach($aRes as $iRow => $aRowData) - { - $aRowDisp = array(); - $aRowDisp["__LINE__"] = $iRow; - if (is_object($aRowData["__STATUS__"])) - { - $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); - } - else - { - $aRowDisp["__STATUS__"] = "*No status available*"; - } - if (isset($aRowData["finalclass"]) && isset($aRowData["id"])) - { - $aRowDisp["__OBJECT_CLASS__"] = $aRowData["finalclass"]; - $aRowDisp["__OBJECT_ID__"] = $aRowData["id"]->GetCLIValue(); - } - else - { - $aRowDisp["__OBJECT_CLASS__"] = "n/a"; - $aRowDisp["__OBJECT_ID__"] = "n/a"; - } - foreach($aRowData as $key => $value) - { - $sKey = (string) $key; - - if ($sKey == '__STATUS__') continue; - //__ERRORS__ used by tests only - if ($sKey == '__ERRORS__') continue; - if ($sKey == 'finalclass') continue; - if ($sKey == 'id') continue; - - if (is_object($value)) - { - $aRowDisp["$sKey"] = $value->GetCLIValueAndDescription(); - } - else - { - $aRowDisp["$sKey"] = $value; - } - } - $aResultDisp[$iRow] = $aRowDisp; - } - $oP->table($aDisplayConfig, $aResultDisp); - } -} -catch(BulkLoadException $e) -{ - $oP->add_comment($e->getMessage()); -} -catch(SecurityException $e) -{ - $oP->add_comment($e->getMessage()); -} -catch(Exception $e) -{ - $oP->add_comment((string)$e); + ////////////////////////////////////////////////// + // + // Check parameters format/consistency + // + if (strlen($sCSVData) == 0) { + throw new BulkLoadException("Missing data - at least one line is expected"); + } + + if (!MetaModel::IsValidClass($sClass)) { + throw new BulkLoadException("Unknown class: '$sClass'"); + } + + if (strlen($sSep) > 1) { + throw new BulkLoadException("Separator is limited to one character, found '$sSep'"); + } + + if (strlen($sQualifier) > 1) { + throw new BulkLoadException("Text qualifier is limited to one character, found '$sQualifier'"); + } + + if (!in_array($sOutput, ['retcode', 'summary', 'details'])) { + throw new BulkLoadException("Unknown output format: '$sOutput'"); + } + + if (strlen($sDateFormat) == 0) { + $sDateFormat = null; + } + + if ($sCharSet == '') { + $sCharSet = MetaModel::GetConfig()->Get('csv_file_default_charset'); + } + + if ($sSimulate == '1') { + $bSimulate = true; + } else { + $bSimulate = false; + } + + if (($sOutput == "summary") || ($sOutput == 'details')) { + $oP->add_comment("Output format: ".$sOutput); + $oP->add_comment("Class: ".$sClass); + $oP->add_comment("Separator: ".$sSep); + $oP->add_comment("Qualifier: ".$sQualifier); + $oP->add_comment("Charset Encoding:".$sCharSet); + if (($sDateFormat !== null) && (strlen($sDateFormat) > 0)) { + $oP->add_comment("Date and time format: '$sDateFormat'"); + $oDateTimeFormat = new DateTimeFormat($sDateFormat); + $sDateOnlyFormat = $oDateTimeFormat->ToDateFormat(); + $oP->add_comment("Date format: '$sDateOnlyFormat'"); + } else { + $oP->add_comment("Date format: "); + } + $oP->add_comment("Localize: ".($bLocalize ? 'yes' : 'no')); + $oP->add_comment("Data Size: ".strlen($sCSVData)); + } + ////////////////////////////////////////////////// + // + // Security + // + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY)) { + throw new SecurityException(Dict::Format('UI:Error:BulkModifyNotAllowedOn_Class', $sClass)); + } + + ////////////////////////////////////////////////// + // + // Create an index of the known column names (in lower case) + // If data is localized, an array of => array of (several leads to ambiguity) + // Otherwise an array of => array of (1 element by construction) + // + // Examples (localized in french): + // 'lieu' => 'location_id' + // 'lieu->name' => 'location_id->name' + // + // Note: it may happen that an external field has the same label as the external key + // in that case, we consider that the external key has precedence + // + $aKnownColumnNames = ['id' => ['id']]; + foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { + if ($bLocalize) { + $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCode)); + } else { + $sColName = strtolower($sAttCode); + } + if (!$oAttDef->IsExternalField() || !array_key_exists($sColName, $aKnownColumnNames)) { + $aKnownColumnNames[$sColName][] = $sAttCode; + } + if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { + $sRemoteClass = $oAttDef->GetTargetClass(); + foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) { + $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; + if ($bLocalize) { + $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCodeEx)); + } else { + $sColName = strtolower($sAttCodeEx); + } + if (!array_key_exists($sColName, $aKnownColumnNames)) { + $aKnownColumnNames[$sColName][] = $sAttCodeEx; + } + } + } + } + + //print_r($aKnownColumnNames); + //print_r(array_keys($aKnownColumnNames)); + //exit; + + ////////////////////////////////////////////////// + // + // Parse first line, check attributes, analyse the request + // + if ($sCharSet == 'UTF-8') { + // Remove the BOM if any + if (substr($sCSVData, 0, 3) == UTF8_BOM) { + $sCSVData = substr($sCSVData, 3); + } + // Clean the input + // Todo: warn the user if some characters are lost/substituted + $sUTF8Data = iconv('UTF-8', 'UTF-8//IGNORE//TRANSLIT', $sCSVData); + } else { + $sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); + } + $oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier); + + // Limitation: as the attribute list is in the first line, we can not match external key by a third-party attribute + $aRawFieldList = $oCSVParser->ListFields(); + $iColCount = count($aRawFieldList); + + // Translate into internal names + $aFieldList = []; + foreach ($aRawFieldList as $iFieldId => $sFieldName) { + $sFieldName = trim($sFieldName); + $aMatches = []; + if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches)) { + // Ignore any trailing "star" (*) that simply indicates a mandatory field + $sFieldName = $aMatches[1]; + } elseif (preg_match('/^(.+)\*->(.+)$/', $sFieldName, $aMatches)) { + // Remove any trailing "star" character before the arrow (->) + // A star character at the end can be used to indicate a mandatory field + $sFieldName = $aMatches[1].'->'.$aMatches[2]; + } + if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames)) { + $aColumns = $aKnownColumnNames[strtolower($sFieldName)]; + if (count($aColumns) > 1) { + $aCompetitors = []; + foreach ($aColumns as $sAttCodeEx) { + $aCompetitors[] = $sAttCodeEx; + } + $aWarnings[] = "Input column '$sFieldName' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; + } + $aFieldList[$iFieldId] = $aColumns[0]; + } else { + // Protect against XSS injection + $sSafeName = str_replace(['"', '<', '>'], '', $sFieldName); + throw new BulkLoadException("Unknown column: '$sSafeName'. Possible columns: ".implode(', ', array_keys($aKnownColumnNames))); + } + } + // Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->') + + $aAttList = []; + $aExtKeys = []; + foreach ($aFieldList as $iFieldId => $sFieldName) { + $aMatches = []; + if (preg_match('/^(.+)->(.+)$/', trim($sFieldName), $aMatches)) { + // The column has been specified as "extkey->attcode" + // + $sExtKeyAttCode = $aMatches[1]; + $sRemoteAttCode = $aMatches[2]; + if (!MetaModel::IsValidAttCode($sClass, $sExtKeyAttCode)) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown attribute '$sExtKeyAttCode' (class: '$sClass')"); + } + $oAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); + if (!$oAtt->IsExternalKey()) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Not an external key '$sExtKeyAttCode' (class: '$sClass')"); + } + $sTargetClass = $oAtt->GetTargetClass(); + if (!MetaModel::IsValidAttCode($sTargetClass, $sRemoteAttCode)) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown attribute '$sRemoteAttCode' (key: '$sExtKeyAttCode', class: '$sTargetClass')"); + } + $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; + } elseif ($sFieldName == 'id') { + $aAttList[$sFieldName] = $iFieldId; + } else { + // The column has been specified as "attcode" + // + if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown attribute '$sFieldName' (class: '$sClass')"); + } + $oAtt = MetaModel::GetAttributeDef($sClass, $sFieldName); + if ($oAtt->IsExternalKey()) { + $aExtKeys[$sFieldName]['id'] = $iFieldId; + $aAttList[$sFieldName] = $iFieldId; + } elseif ($oAtt->IsExternalField()) { + $sExtKeyAttCode = $oAtt->GetKeyAttCode(); + $sRemoteAttCode = $oAtt->GetExtAttCode(); + $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; + } else { + $aAttList[$sFieldName] = $iFieldId; + } + } + } + + // Make sure there are some reconciliation keys + // + if (empty($sReconcKeys)) { + $aReconcSpec = []; + // Base reconciliation scheme on the default one + // The reconciliation attributes not present in the data will be ignored + foreach (MetaModel::GetReconcKeys($sClass) as $sReconcKeyAttCode) { + if (in_array($sReconcKeyAttCode, $aFieldList)) { + if ($bLocalize) { + $aReconcSpec[] = MetaModel::GetLabel($sClass, $sReconcKeyAttCode); + } else { + $aReconcSpec[] = $sReconcKeyAttCode; + } + } + } + if (count($aReconcSpec) == 0) { + throw new BulkLoadException("No reconciliation scheme could be defined, please add a column corresponding to one defined reconciliation key (class: '$sClass', reconciliation:".implode(',', MetaModel::GetReconcKeys($sClass)).")"); + } + $sReconcKeys = implode(',', $aReconcSpec); + } + + // Interpret the list of reconciliation keys + // + $aFinalReconcilKeys = []; + $aReconcilKeysReport = []; + foreach (explode(',', $sReconcKeys) as $sReconcKey) { + $sReconcKey = trim($sReconcKey); + if (empty($sReconcKey)) { + continue; + } // skip empty spec + + if (array_key_exists(strtolower($sReconcKey), $aKnownColumnNames)) { + // Translate from a translated name to codes + $aColumns = $aKnownColumnNames[strtolower($sReconcKey)]; + if (count($aColumns) > 1) { + $aCompetitors = []; + foreach ($aColumns as $sAttCodeEx) { + $aCompetitors[] = $sAttCodeEx; + } + $aWarnings[] = "Reconciliation key '$sReconcKey' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; + } + $sReconcKey = $aColumns[0]; + } else { + // Protect against XSS injection + $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); + throw new BulkLoadException("Unknown reconciliation key: '$sSafeName'"); + } + + // Check that the reconciliation key is either a given column, or an external key + if (!in_array($sReconcKey, $aFieldList)) { + if (!array_key_exists($sReconcKey, $aExtKeys)) { + // Protect against XSS injection + $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); + throw new BulkLoadException("Reconciliation key not found in the input columns: '$sSafeName'"); + } + } + + if (preg_match('/^(.+)->(.+)$/', trim($sReconcKey), $aMatches)) { + // The column has been specified as "extkey->attcode" + // + $sExtKeyAttCode = $aMatches[1]; + $sRemoteAttCode = $aMatches[2]; + + $aFinalReconcilKeys[] = $sExtKeyAttCode; + $aReconcilKeysReport[$sExtKeyAttCode][] = $sRemoteAttCode; + } else { + if (!MetaModel::IsValidAttCode($sClass, $sReconcKey) && $sReconcKey != 'id') { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown reconciliation attribute '$sReconcKey' (class: '$sClass')"); + } + if ($sReconcKey == 'id') { + $aFinalReconcilKeys[] = $sReconcKey; + $aReconcilKeysReport[$sReconcKey] = []; + } else { + $oAtt = MetaModel::GetAttributeDef($sClass, $sReconcKey); + if ($oAtt->IsExternalKey()) { + $aFinalReconcilKeys[] = $sReconcKey; + $aReconcilKeysReport[$sReconcKey][] = 'id'; + } elseif ($oAtt->IsExternalField()) { + $sReconcAttCode = $oAtt->GetKeyAttCode(); + $sReconcKeyReport = "$sReconcAttCode ($sReconcKey)"; + + $aFinalReconcilKeys[] = $sReconcAttCode; + $aReconcilKeysReport[$sReconcAttCode][] = $sReconcKeyReport; + } else { + $aFinalReconcilKeys[] = $sReconcKey; + $aReconcilKeysReport[$sReconcKey] = []; + } + } + } + } + + ////////////////////////////////////////////////// + // + // Go for parsing and interpretation + // + + $aData = $oCSVParser->ToArray(); + $iLineCount = count($aData); + + if (($sOutput == "summary") || ($sOutput == 'details')) { + $oP->add_comment("Data Lines: ".$iLineCount); + $oP->add_comment("Simulate: ".($bSimulate ? '1' : '0')); + $oP->add_comment("Columns: ".implode(', ', $aFieldList)); + + $aReconciliationReport = []; + foreach ($aReconcilKeysReport as $sKey => $aKeyDetails) { + if (count($aKeyDetails) > 0) { + $aReconciliationReport[] = $sKey.' ('.implode(',', $aKeyDetails).')'; + } else { + $aReconciliationReport[] = $sKey; + } + } + $oP->add_comment("Reconciliation Keys: ".implode(', ', $aReconciliationReport)); + + foreach ($aWarnings as $sWarning) { + $oP->add_comment("Warning: ".$sWarning); + } + } + + $oBulk = new BulkChange( + $sClass, + $aData, + $aAttList, + $aExtKeys, + $aFinalReconcilKeys, + null, // synchro scope + null, // on delete + $sDateFormat, + $bLocalize + ); + + if ($bSimulate) { + $oMyChange = null; + } else { + if (strlen($sComment) > 0) { + $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; + } else { + $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV)'; + } + CMDBObject::SetCurrentChangeFromParams($sMoreInfo, 'csv-import.php'); + $oMyChange = CMDBObject::GetCurrentChange(); + } + + $aRes = $oBulk->Process($oMyChange); + + ////////////////////////////////////////////////// + // + // Compute statistics + // + $iCountErrors = 0; + $iCountWarnings = 0; + $iCountCreations = 0; + $iCountUpdates = 0; + $iCountUnchanged = 0; + foreach ($aRes as $iRow => $aRowData) { + $bWritten = false; + + $oStatus = $aRowData["__STATUS__"]; + switch (get_class($oStatus)) { + case 'RowStatus_NoChange': + $iCountUnchanged++; + break; + case 'RowStatus_Modify': + $iCountUpdates++; + $bWritten = true; + break; + case 'RowStatus_NewObj': + $iCountCreations++; + $bWritten = true; + break; + case 'RowStatus_Issue': + $iCountErrors++; + break; + } + + if ($bWritten) { + // Something has been done, still there may be some issues to report + foreach ($aRowData as $key => $value) { + if (!is_object($value)) { + continue; + } + + switch (get_class($value)) { + case 'CellStatus_Void': + case 'CellStatus_Modify': + break; + case 'CellStatus_Issue': + case 'CellStatus_SearchIssue': + case 'CellStatus_NullIssue': + case 'CellStatus_Ambiguous': + $iCountWarnings++; + break; + } + } + } + } + + ////////////////////////////////////////////////// + // + // Summary of settings and results + // + if ($sOutput == 'retcode') { + $oP->add($iCountErrors); + } + + if (($sOutput == "summary") || ($sOutput == 'details')) { + $oP->add_comment("Change tracking comment: ".$sComment); + $oP->add_comment("Issues: ".$iCountErrors); + $oP->add_comment("Warnings: ".$iCountWarnings); + $oP->add_comment("Created: ".$iCountCreations); + $oP->add_comment("Updated: ".$iCountUpdates); + $oP->add_comment("Unchanged: ".$iCountUnchanged); + } + + if ($sOutput == 'details') { + // Setup result presentation + // + $aDisplayConfig = []; + $aDisplayConfig["__LINE__"] = ["label" => "Line", "description" => ""]; + $aDisplayConfig["__STATUS__"] = ["label" => "Status", "description" => ""]; + $aDisplayConfig["__OBJECT_CLASS__"] = ["label" => "Object Class", "description" => ""]; + $aDisplayConfig["__OBJECT_ID__"] = ["label" => "Object Id", "description" => ""]; + foreach ($aExtKeys as $sExtKeyAttCode => $aRemoteAtt) { + $sLabel = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode)->GetLabel(); + $aDisplayConfig["$sExtKeyAttCode"] = ["label" => $sExtKeyAttCode, "description" => $sLabel." - ext key"]; + } + foreach ($aFinalReconcilKeys as $iCol => $sAttCode) { + // $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + // $aDisplayConfig["$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + foreach ($aAttList as $sAttCode => $iCol) { + if ($sAttCode == 'id') { + $sLabel = Dict::S('UI:CSVImport:idField'); + + $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; + } else { + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; + } + } + + $aResultDisp = []; // to be displayed + foreach ($aRes as $iRow => $aRowData) { + $aRowDisp = []; + $aRowDisp["__LINE__"] = $iRow; + if (is_object($aRowData["__STATUS__"])) { + $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); + } else { + $aRowDisp["__STATUS__"] = "*No status available*"; + } + if (isset($aRowData["finalclass"]) && isset($aRowData["id"])) { + $aRowDisp["__OBJECT_CLASS__"] = $aRowData["finalclass"]; + $aRowDisp["__OBJECT_ID__"] = $aRowData["id"]->GetCLIValue(); + } else { + $aRowDisp["__OBJECT_CLASS__"] = "n/a"; + $aRowDisp["__OBJECT_ID__"] = "n/a"; + } + foreach ($aRowData as $key => $value) { + $sKey = (string) $key; + + if ($sKey == '__STATUS__') { + continue; + } + //__ERRORS__ used by tests only + if ($sKey == '__ERRORS__') { + continue; + } + if ($sKey == 'finalclass') { + continue; + } + if ($sKey == 'id') { + continue; + } + + if (is_object($value)) { + $aRowDisp["$sKey"] = $value->GetCLIValueAndDescription(); + } else { + $aRowDisp["$sKey"] = $value; + } + } + $aResultDisp[$iRow] = $aRowDisp; + } + $oP->table($aDisplayConfig, $aResultDisp); + } +} catch (BulkLoadException $e) { + $oP->add_comment($e->getMessage()); +} catch (SecurityException $e) { + $oP->add_comment($e->getMessage()); +} catch (Exception $e) { + $oP->add_comment((string)$e); } $oP->output(); -?> diff --git a/webservices/itop.wsdl.php b/webservices/itop.wsdl.php index 5b9b37b1f9..d818859889 100644 --- a/webservices/itop.wsdl.php +++ b/webservices/itop.wsdl.php @@ -1,4 +1,5 @@ diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index e14988c0c3..58d02c63d1 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -1,9 +1,10 @@ - /** - * Shows a usage of the SOAP queries + * Shows a usage of the SOAP queries * * @copyright Copyright (C) 2010-2024 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 @@ -36,120 +36,101 @@ * @return bool|false|string * @throws \Exception */ -function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array()) +function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = []) { - // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. + // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. - if (function_exists('curl_init')) - { - // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options - // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 - // by setting the SSLVERSION to 3 as done below. - $aHTTPHeaders = array(); - if ($sOptionnalHeaders !== null) - { - $aHeaders = explode("\n", $sOptionnalHeaders); - // N°3267 - Webservices: Fix optional headers not being taken into account - // See https://www.php.net/curl_setopt CURLOPT_HTTPHEADER - $aHTTPHeaders = array(); - foreach($aHeaders as $sHeaderString) - { - $aHTTPHeaders[] = trim($sHeaderString); - } - } - // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions - $aOptions = array( - CURLOPT_RETURNTRANSFER => true, // return the content of the request - CURLOPT_HEADER => false, // don't return the headers in the output - CURLOPT_FOLLOWLOCATION => true, // follow redirects - CURLOPT_ENCODING => "", // handle all encodings - CURLOPT_USERAGENT => "spider", // who am i - CURLOPT_AUTOREFERER => true, // set referer on redirect - CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect - CURLOPT_TIMEOUT => 120, // timeout on response - CURLOPT_MAXREDIRS => 10, // stop after 10 redirects - CURLOPT_SSL_VERIFYHOST => 0, // Disabled SSL Cert checks - CURLOPT_SSL_VERIFYPEER => 0, // Disabled SSL Cert checks - // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why - // but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 - // CURLOPT_SSLVERSION => 3, - CURLOPT_POST => count($aData), - CURLOPT_POSTFIELDS => http_build_query($aData), - CURLOPT_HTTPHEADER => $aHTTPHeaders, - ); - $aAllOptions = $aCurlOptions + $aOptions; - $ch = curl_init($sUrl); - curl_setopt_array($ch, $aAllOptions); - $response = curl_exec($ch); - $iErr = curl_errno($ch); - $sErrMsg = curl_error( $ch ); - if ($iErr !== 0) - { - throw new Exception("Problem opening URL: $sUrl, $sErrMsg"); - } - if (is_array($aResponseHeaders)) - { - $aHeaders = curl_getinfo($ch); - foreach($aHeaders as $sCode => $sValue) - { - $sName = str_replace(' ' , '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" - $aResponseHeaders[$sName] = $sValue; - } - } - curl_close( $ch ); - } - else - { - // cURL is not available let's try with streams and fopen... + if (function_exists('curl_init')) { + // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options + // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 + // by setting the SSLVERSION to 3 as done below. + $aHTTPHeaders = []; + if ($sOptionnalHeaders !== null) { + $aHeaders = explode("\n", $sOptionnalHeaders); + // N°3267 - Webservices: Fix optional headers not being taken into account + // See https://www.php.net/curl_setopt CURLOPT_HTTPHEADER + $aHTTPHeaders = []; + foreach ($aHeaders as $sHeaderString) { + $aHTTPHeaders[] = trim($sHeaderString); + } + } + // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions + $aOptions = [ + CURLOPT_RETURNTRANSFER => true, // return the content of the request + CURLOPT_HEADER => false, // don't return the headers in the output + CURLOPT_FOLLOWLOCATION => true, // follow redirects + CURLOPT_ENCODING => "", // handle all encodings + CURLOPT_USERAGENT => "spider", // who am i + CURLOPT_AUTOREFERER => true, // set referer on redirect + CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect + CURLOPT_TIMEOUT => 120, // timeout on response + CURLOPT_MAXREDIRS => 10, // stop after 10 redirects + CURLOPT_SSL_VERIFYHOST => 0, // Disabled SSL Cert checks + CURLOPT_SSL_VERIFYPEER => 0, // Disabled SSL Cert checks + // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why + // but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 + // CURLOPT_SSLVERSION => 3, + CURLOPT_POST => count($aData), + CURLOPT_POSTFIELDS => http_build_query($aData), + CURLOPT_HTTPHEADER => $aHTTPHeaders, + ]; + $aAllOptions = $aCurlOptions + $aOptions; + $ch = curl_init($sUrl); + curl_setopt_array($ch, $aAllOptions); + $response = curl_exec($ch); + $iErr = curl_errno($ch); + $sErrMsg = curl_error($ch); + if ($iErr !== 0) { + throw new Exception("Problem opening URL: $sUrl, $sErrMsg"); + } + if (is_array($aResponseHeaders)) { + $aHeaders = curl_getinfo($ch); + foreach ($aHeaders as $sCode => $sValue) { + $sName = str_replace(' ', '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" + $aResponseHeaders[$sName] = $sValue; + } + } + curl_close($ch); + } else { + // cURL is not available let's try with streams and fopen... - $sData = http_build_query($aData); - $aParams = array('http' => array( - 'method' => 'POST', - 'content' => $sData, - 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", - )); - if ($sOptionnalHeaders !== null) - { - $aParams['http']['header'] .= $sOptionnalHeaders; - } - $ctx = stream_context_create($aParams); + $sData = http_build_query($aData); + $aParams = ['http' => [ + 'method' => 'POST', + 'content' => $sData, + 'header' => "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", + ]]; + if ($sOptionnalHeaders !== null) { + $aParams['http']['header'] .= $sOptionnalHeaders; + } + $ctx = stream_context_create($aParams); - $fp = @fopen($sUrl, 'rb', false, $ctx); - if (!$fp) - { - global $php_errormsg; - if (isset($php_errormsg)) - { - throw new Exception("Wrong URL: $sUrl, $php_errormsg"); - } - elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) - { - throw new Exception("Cannot connect to $sUrl: missing module 'openssl'"); - } - else - { - throw new Exception("Wrong URL: $sUrl"); - } - } - $response = @stream_get_contents($fp); - if ($response === false) - { - throw new Exception("Problem reading data from $sUrl, $php_errormsg"); - } - if (is_array($aResponseHeaders)) - { - $aMeta = stream_get_meta_data($fp); - $aHeaders = $aMeta['wrapper_data']; - foreach($aHeaders as $sHeaderString) - { - if(preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) - { - $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]); - } - } - } - } - return $response; + $fp = @fopen($sUrl, 'rb', false, $ctx); + if (!$fp) { + global $php_errormsg; + if (isset($php_errormsg)) { + throw new Exception("Wrong URL: $sUrl, $php_errormsg"); + } elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) { + throw new Exception("Cannot connect to $sUrl: missing module 'openssl'"); + } else { + throw new Exception("Wrong URL: $sUrl"); + } + } + $response = @stream_get_contents($fp); + if ($response === false) { + throw new Exception("Problem reading data from $sUrl, $php_errormsg"); + } + if (is_array($aResponseHeaders)) { + $aMeta = stream_get_meta_data($fp); + $aHeaders = $aMeta['wrapper_data']; + foreach ($aHeaders as $sHeaderString) { + if (preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) { + $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]); + } + } + } + } + return $response; } //////////////////////////////////////////////////////////////////////////////// @@ -161,235 +142,223 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead // Define the operations to perform (one operation per call the rest service) // -$aOperations = array( - array( - 'operation' => 'list_operations', // operation code - ), - array( - 'operation' => 'core/create', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => array( - 'org_id' => "SELECT Organization WHERE name = 'Demo'", - 'caller_id' => array('name' => 'monet', 'first_name' => 'claude'), - 'title' => 'issue blah', - 'description' => 'something happened' - ), - ), - array( - 'operation' => 'core/update', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest WHERE id=1', - 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => array( - 'title' => 'Issue #'.rand(0, 100), - 'contacts_list' => array( - array( - 'role' => 'fireman #'.rand(0, 100), - 'contact_id' => array('finalclass' => 'Person', 'name' => 'monet', 'first_name' => 'claude'), - ), - ), - ), - ), - // Rewrite the full CaseLog on an existing UserRequest with id=1, setting date and user (optional) - array( - 'operation' => 'core/update', - 'comment' => 'Synchronization from Client A', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest WHERE id=1', - 'output_fields' => 'id, friendlyname, title', - 'fields' => array( - 'public_log' => array( - 'items' => array( - 0 => array( - 'date' => '2001-02-01 23:59:59', //Allow to set the date of a true event, an alarm for eg. - 'user_login' => 'Alarm monitoring', //Free text - 'user_id' => 0, //0 is required for the user_login to be taken into account - 'message' => 'This is 1st entry as an HTML formatted
text', - ), - 1 => array( - 'date' => '2001-02-02 00:00:00', //If ommitted set automatically. - 'user_login' => 'Alarm monitoring', //user=id=0 is missing so will be ignored - 'message' => 'Second entry in text format: +$aOperations = [ + [ + 'operation' => 'list_operations', // operation code + ], + [ + 'operation' => 'core/create', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'org_id' => "SELECT Organization WHERE name = 'Demo'", + 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], + 'title' => 'issue blah', + 'description' => 'something happened' + ], + ], + [ + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE id=1', + 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'title' => 'Issue #'.rand(0, 100), + 'contacts_list' => [ + [ + 'role' => 'fireman #'.rand(0, 100), + 'contact_id' => ['finalclass' => 'Person', 'name' => 'monet', 'first_name' => 'claude'], + ], + ], + ], + ], + // Rewrite the full CaseLog on an existing UserRequest with id=1, setting date and user (optional) + [ + 'operation' => 'core/update', + 'comment' => 'Synchronization from Client A', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE id=1', + 'output_fields' => 'id, friendlyname, title', + 'fields' => [ + 'public_log' => [ + 'items' => [ + 0 => [ + 'date' => '2001-02-01 23:59:59', //Allow to set the date of a true event, an alarm for eg. + 'user_login' => 'Alarm monitoring', //Free text + 'user_id' => 0, //0 is required for the user_login to be taken into account + 'message' => 'This is 1st entry as an HTML formatted
text', + ], + 1 => [ + 'date' => '2001-02-02 00:00:00', //If ommitted set automatically. + 'user_login' => 'Alarm monitoring', //user=id=0 is missing so will be ignored + 'message' => 'Second entry in text format: with new line, but format not specified, so treated as HTML!, user_id=0 missing, so user_login ignored', - ), - ), - ), - ), - ), - // Add a Text entry in the HTML CaseLog of the UserRequest with id=1, setting date and user (optional) - array( - 'operation' => 'core/update', // operation code - 'comment' => 'Synchronization from Alarm Monitoring', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 1, // object id or OQL - 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) - // Example of adding an entry into a CaseLog on an existing UserRequest - 'fields' => array( - 'public_log' => array( - 'add_item' => array( - 'user_login' => 'New Entry', //Free text - 'user_id' => 0, //0 is required for the user_login to be taken into account - 'format' => 'text', //If ommitted, source is expected to be HTML - 'message' => 'This text is not HTML formatted with 3 lines: + ], + ], + ], + ], + ], + // Add a Text entry in the HTML CaseLog of the UserRequest with id=1, setting date and user (optional) + [ + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from Alarm Monitoring', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 1, // object id or OQL + 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) + // Example of adding an entry into a CaseLog on an existing UserRequest + 'fields' => [ + 'public_log' => [ + 'add_item' => [ + 'user_login' => 'New Entry', //Free text + 'user_id' => 0, //0 is required for the user_login to be taken into account + 'format' => 'text', //If ommitted, source is expected to be HTML + 'message' => 'This text is not HTML formatted with 3 lines: new line 3rd and last line', - ), - ), - ), - ), - array( - 'operation' => 'core/get', // operation code - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest', - 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) - ), - array( - 'operation' => 'core/delete', // operation code - 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest WHERE org_id = 2', - 'simulate' => true, - ), - array( - 'operation' => 'core/apply_stimulus', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 1, - 'stimulus' => 'ev_assign', - // Values to set - 'fields' => array( - 'team_id' => 15, // Helpdesk - 'agent_id' => 9 // Jules Verne - ), - 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) - ), - array( - 'operation' => 'core/get_related', // operation code - 'class' => 'Server', - 'key' => 'SELECT Server', - 'relation' => 'impacts', // relation code - 'depth' => 4, // max recursion depth - ), -); -$aOperations = array( - array( - 'operation' => 'core/create', // operation code - 'comment' => 'Automatic creation of attachment blah blah...', // comment recorded in the change tracking log - 'class' => 'Attachment', - 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => array( - 'item_class' => 'UserRequest', - 'item_id' => 1, - 'item_org_id' => 3, - 'contents' => array( - 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACmSURBVChTfZHRDYMwDESzQ2fqhHx3C3ao+MkW/WlnaFxfzk7sEnE6JHJ+NgaKZN2zLHVN2ssfkae0Da7FQ5PRk/ve4Hcx19Ie6CEGuh/6vMgNhwanHVUNbt73lUDbYJ+6pg8b3+m2RehsVPdMXyvQY+OVkB+Rrv64lUjb3nq+aCA6v4leRqtfaIgimr53atBy9PlfUhoh3fFCNDmErv9FWR6ylBL5AREbmHBnFj5lAAAAAElFTkSuQmCC', - 'filename' => 'myself.png', - 'mimetype' => 'image/png' - ), - ), - ), - array( - 'operation' => 'core/get', // operation code - 'class' => 'Attachment', - 'key' => 'SELECT Attachment', - 'output_fields' => '*', - ) -); -$aOperations = array( - array( - 'operation' => 'core/update', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'Server', - 'key' => 'SELECT Server WHERE name="Server1"', - 'output_fields' => 'id, friendlyname, description', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => array( - 'description' => 'Issue #'.time(), - ), - ), -); -$aOperations = array( - array( - 'operation' => 'core/create', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => array( - 'org_id' => "SELECT Organization WHERE name = 'Demo'", - 'caller_id' => array('name' => 'monet', 'first_name' => 'claude'), - 'title' => 'issue blah', - 'description' => 'something happened' - ), - ), -); -$aXXXOperations = array( - array( - 'operation' => 'core/check_credentials', // operation code - 'user' => 'admin', - 'password' => 'admin', - ), -); -$aDeleteOperations = array( - array( - 'operation' => 'core/delete', // operation code - 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log - 'class' => 'Server', - 'key' => 'SELECT Server', - 'simulate' => false, - ), -); + ], + ], + ], + ], + [ + 'operation' => 'core/get', // operation code + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest', + 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) + ], + [ + 'operation' => 'core/delete', // operation code + 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE org_id = 2', + 'simulate' => true, + ], + [ + 'operation' => 'core/apply_stimulus', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 1, + 'stimulus' => 'ev_assign', + // Values to set + 'fields' => [ + 'team_id' => 15, // Helpdesk + 'agent_id' => 9 // Jules Verne + ], + 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) + ], + [ + 'operation' => 'core/get_related', // operation code + 'class' => 'Server', + 'key' => 'SELECT Server', + 'relation' => 'impacts', // relation code + 'depth' => 4, // max recursion depth + ], +]; +$aOperations = [ + [ + 'operation' => 'core/create', // operation code + 'comment' => 'Automatic creation of attachment blah blah...', // comment recorded in the change tracking log + 'class' => 'Attachment', + 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'item_class' => 'UserRequest', + 'item_id' => 1, + 'item_org_id' => 3, + 'contents' => [ + 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACmSURBVChTfZHRDYMwDESzQ2fqhHx3C3ao+MkW/WlnaFxfzk7sEnE6JHJ+NgaKZN2zLHVN2ssfkae0Da7FQ5PRk/ve4Hcx19Ie6CEGuh/6vMgNhwanHVUNbt73lUDbYJ+6pg8b3+m2RehsVPdMXyvQY+OVkB+Rrv64lUjb3nq+aCA6v4leRqtfaIgimr53atBy9PlfUhoh3fFCNDmErv9FWR6ylBL5AREbmHBnFj5lAAAAAElFTkSuQmCC', + 'filename' => 'myself.png', + 'mimetype' => 'image/png' + ], + ], + ], + [ + 'operation' => 'core/get', // operation code + 'class' => 'Attachment', + 'key' => 'SELECT Attachment', + 'output_fields' => '*', + ] +]; +$aOperations = [ + [ + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'Server', + 'key' => 'SELECT Server WHERE name="Server1"', + 'output_fields' => 'id, friendlyname, description', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'description' => 'Issue #'.time(), + ], + ], +]; +$aOperations = [ + [ + 'operation' => 'core/create', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'org_id' => "SELECT Organization WHERE name = 'Demo'", + 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], + 'title' => 'issue blah', + 'description' => 'something happened' + ], + ], +]; +$aXXXOperations = [ + [ + 'operation' => 'core/check_credentials', // operation code + 'user' => 'admin', + 'password' => 'admin', + ], +]; +$aDeleteOperations = [ + [ + 'operation' => 'core/delete', // operation code + 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log + 'class' => 'Server', + 'key' => 'SELECT Server', + 'simulate' => false, + ], +]; -if (false) -{ - echo "Please edit the sample script and configure the server URL"; - exit; -} -else -{ - $sUrl = "https://localhost/itop/webservices/rest.php?version=1.3"; +if (false) { + echo "Please edit the sample script and configure the server URL"; + exit; +} else { + $sUrl = "https://localhost/itop/webservices/rest.php?version=1.3"; } -$aData = array(); +$aData = []; $aData['auth_user'] = 'rest'; $aData['auth_pwd'] = 'rest'; +foreach ($aOperations as $iOp => $aOperation) { + echo "======================================\n"; + echo "Operation #$iOp: ".$aOperation['operation']."\n"; + $aData['json_data'] = json_encode($aOperation); -foreach ($aOperations as $iOp => $aOperation) -{ - echo "======================================\n"; - echo "Operation #$iOp: ".$aOperation['operation']."\n"; - $aData['json_data'] = json_encode($aOperation); - - echo "--------------------------------------\n"; - echo "Input:\n"; - print_r($aOperation); - $aResults = null; - try - { - $response = DoPostRequest($sUrl, $aData); - $aResults = json_decode($response); - } - catch (Exception $e) - { - $response = $e->getMessage(); - } - if ($aResults) - { - echo "--------------------------------------\n"; - echo "Reply:\n"; - print_r($aResults); - } - else - { - echo "ERROR rest.php replied:\n"; - echo $response; - } + echo "--------------------------------------\n"; + echo "Input:\n"; + print_r($aOperation); + $aResults = null; + try { + $response = DoPostRequest($sUrl, $aData); + $aResults = json_decode($response); + } catch (Exception $e) { + $response = $e->getMessage(); + } + if ($aResults) { + echo "--------------------------------------\n"; + echo "Reply:\n"; + print_r($aResults); + } else { + echo "ERROR rest.php replied:\n"; + echo $response; + } } - diff --git a/webservices/itopsoap.examples.php b/webservices/itopsoap.examples.php index 5ed1bdc24c..b869dde628 100644 --- a/webservices/itopsoap.examples.php +++ b/webservices/itopsoap.examples.php @@ -1,9 +1,10 @@ - /** - * Shows a usage of the SOAP queries + * Shows a usage of the SOAP queries * * @copyright Copyright (C) 2010-2024 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 @@ -31,115 +31,102 @@ $aSOAPMapping = SOAPMapping::GetMapping(); -ini_set("soap.wsdl_cache_enabled","0"); +ini_set("soap.wsdl_cache_enabled", "0"); $oSoapClient = new SoapClient( - $sWsdlUri, - array( - 'trace' => 1, - 'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php - ) + $sWsdlUri, + [ + 'trace' => 1, + 'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php + ] ); -try -{ - // The most simple service, returning a string - // - $sServerVersion = $oSoapClient->GetVersion(); - echo "

GetVersion() returned $sServerVersion

"; +try { + // The most simple service, returning a string + // + $sServerVersion = $oSoapClient->GetVersion(); + echo "

GetVersion() returned $sServerVersion

"; - // More complex ones, returning a SOAPResult structure - // (run the page to know more about the returned data) - // - $oRes = $oSoapClient->CreateIncidentTicket - ( - 'admin', /* login */ - 'admin', /* password */ - 'Email server down', /* title */ - 'HW found shutdown', /* description */ - null, /* caller */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* customer */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'NW Management'))), /* service */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Troubleshooting'))), /* service subcategory */ - '', /* product */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'NW support'))), /* workgroup */ - array( - new SOAPLinkCreationSpec( - 'Device', - array(new SOAPSearchCondition('name', 'switch01')), - array() - ), - new SOAPLinkCreationSpec( - 'Server', - array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), - array() - ), - ), /* impacted cis */ - '1', /* impact */ - '1' /* urgency */ - ); + // More complex ones, returning a SOAPResult structure + // (run the page to know more about the returned data) + // + $oRes = $oSoapClient->CreateIncidentTicket( + 'admin', /* login */ + 'admin', /* password */ + 'Email server down', /* title */ + 'HW found shutdown', /* description */ + null, /* caller */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* customer */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW Management')]), /* service */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Troubleshooting')]), /* service subcategory */ + '', /* product */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW support')]), /* workgroup */ + [ + new SOAPLinkCreationSpec( + 'Device', + [new SOAPSearchCondition('name', 'switch01')], + [] + ), + new SOAPLinkCreationSpec( + 'Server', + [new SOAPSearchCondition('name', 'dbserver1.demo.com')], + [] + ), + ], /* impacted cis */ + '1', /* impact */ + '1' /* urgency */ + ); - echo "

CreateIncidentTicket() returned:\n"; - echo "

\n";
-	print_r($oRes);
-	echo "
\n"; - echo "

\n"; + echo "

CreateIncidentTicket() returned:\n"; + echo "

\n";
+    print_r($oRes);
+    echo "
\n"; + echo "

\n"; - $oRes = $oSoapClient->SearchObjects - ( - 'admin', /* login */ - 'admin', /* password */ - 'SELECT URP_Profiles' /* oql */ - ); + $oRes = $oSoapClient->SearchObjects( + 'admin', /* login */ + 'admin', /* password */ + 'SELECT URP_Profiles' /* oql */ + ); - echo "

SearchObjects() returned:\n"; - if ($oRes->status) - { - $aResults = $oRes->result; + echo "

SearchObjects() returned:\n"; + if ($oRes->status) { + $aResults = $oRes->result; - echo "\n"; + echo "
\n"; - // Header made after the first line - echo "\n"; - foreach ($aResults[0]->values as $aKeyValuePair) - { - echo " \n"; - } - echo "\n"; + // Header made after the first line + echo "\n"; + foreach ($aResults[0]->values as $aKeyValuePair) { + echo " \n"; + } + echo "\n"; - foreach ($aResults as $iRow => $aData) - { - echo "\n"; - foreach ($aData->values as $aKeyValuePair) - { - echo " \n"; - } - echo "\n"; - } - echo "
".$aKeyValuePair->key."
".$aKeyValuePair->key."
".$aKeyValuePair->value."
\n"; - } - else - { - $aErrors = array(); - foreach ($oRes->errors->messages as $oMessage) - { - $aErrors[] = $oMessage->text; - } - $sErrorMsg = implode(', ', $aErrors); - echo "

SearchObjects() failed with message: $sErrorMsg

\n"; - //echo "
\n";
-		//print_r($oRes);
-		//echo "
\n"; - } - echo "

\n"; -} -catch(SoapFault $e) -{ - echo "

SoapFault Exception: {$e->getMessage()}

\n"; - echo "

Request

\n"; - echo "
\n"; 
-	echo htmlspecialchars($oSoapClient->__getLastRequest())."\n"; 
-	echo "
"; - echo "

Response

"; - echo $oSoapClient->__getLastResponse()."\n"; + foreach ($aResults as $iRow => $aData) { + echo "\n"; + foreach ($aData->values as $aKeyValuePair) { + echo " ".$aKeyValuePair->value."\n"; + } + echo "\n"; + } + echo "\n"; + } else { + $aErrors = []; + foreach ($oRes->errors->messages as $oMessage) { + $aErrors[] = $oMessage->text; + } + $sErrorMsg = implode(', ', $aErrors); + echo "

SearchObjects() failed with message: $sErrorMsg

\n"; + //echo "
\n";
+        //print_r($oRes);
+        //echo "
\n"; + } + echo "

\n"; +} catch (SoapFault $e) { + echo "

SoapFault Exception: {$e->getMessage()}

\n"; + echo "

Request

\n"; + echo "
\n";
+    echo htmlspecialchars($oSoapClient->__getLastRequest())."\n";
+    echo "
"; + echo "

Response

"; + echo $oSoapClient->__getLastResponse()."\n"; } -?> diff --git a/webservices/itopsoaptypes.class.inc.php b/webservices/itopsoaptypes.class.inc.php index 6145d85fae..ce1ca86a59 100644 --- a/webservices/itopsoaptypes.class.inc.php +++ b/webservices/itopsoaptypes.class.inc.php @@ -1,9 +1,10 @@ - /** * Declarations required for the WSDL * @@ -24,165 +24,158 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ - // Note: the attributes must have the same names (case sensitive) as in the WSDL specification // class SOAPSearchCondition { - public $attcode; // string - public $value; // mixed - - public function __construct($sAttCode, $value) - { - $this->attcode = $sAttCode; - $this->value = $value; - } + public $attcode; // string + public $value; // mixed + + public function __construct($sAttCode, $value) + { + $this->attcode = $sAttCode; + $this->value = $value; + } } - class SOAPExternalKeySearch { - public $conditions; // array of SOAPSearchCondition - - public function __construct($aConditions = null) - { - $this->conditions = $aConditions; - } - - public function IsVoid() - { - if (is_null($this->conditions)) return true; - if (count($this->conditions) == 0) return true; - } + public $conditions; // array of SOAPSearchCondition + + public function __construct($aConditions = null) + { + $this->conditions = $aConditions; + } + + public function IsVoid() + { + if (is_null($this->conditions)) { + return true; + } + if (count($this->conditions) == 0) { + return true; + } + } } - class SOAPAttributeValue { - public $attcode; // string - public $value; // mixed - - public function __construct($sAttCode, $value) - { - $this->attcode = $sAttCode; - $this->value = $value; - } + public $attcode; // string + public $value; // mixed + + public function __construct($sAttCode, $value) + { + $this->attcode = $sAttCode; + $this->value = $value; + } } - class SOAPLinkCreationSpec { - public $class; - public $conditions; // array of SOAPSearchCondition - public $attributes; // array of SOAPAttributeValue - - public function __construct($sClass, $aConditions, $aAttributes) - { - $this->class = $sClass; - $this->conditions = $aConditions; - $this->attributes = $aAttributes; - } + public $class; + public $conditions; // array of SOAPSearchCondition + public $attributes; // array of SOAPAttributeValue + + public function __construct($sClass, $aConditions, $aAttributes) + { + $this->class = $sClass; + $this->conditions = $aConditions; + $this->attributes = $aAttributes; + } } - class SOAPLogMessage { - public $text; // string + public $text; // string - public function __construct($sText) - { - $this->text = $sText; - } + public function __construct($sText) + { + $this->text = $sText; + } } - class SOAPResultLog { - public $messages; // array of SOAPLogMessage + public $messages; // array of SOAPLogMessage - public function __construct($aMessages) - { - $this->messages = $aMessages; - } + public function __construct($aMessages) + { + $this->messages = $aMessages; + } } - class SOAPKeyValue { - public $key; // string - public $value; // string - - public function __construct($sKey, $sValue) - { - $this->key = $sKey; - $this->value = $sValue; - } + public $key; // string + public $value; // string + + public function __construct($sKey, $sValue) + { + $this->key = $sKey; + $this->value = $sValue; + } } class SOAPResultMessage { - public $label; // string - public $values; // array of SOAPKeyValue - - public function __construct($sLabel, $aValues) - { - $this->label = $sLabel; - $this->values = $aValues; - } + public $label; // string + public $values; // array of SOAPKeyValue + + public function __construct($sLabel, $aValues) + { + $this->label = $sLabel; + $this->values = $aValues; + } } - class SOAPResult { - public $status; // boolean - public $result; // array of SOAPResultMessage - public $errors; // array of SOAPResultLog - public $warnings; // array of SOAPResultLog - public $infos; // array of SOAPResultLog - - public function __construct($bStatus, $aResult, $aErrors, $aWarnings, $aInfos) - { - $this->status = $bStatus; - $this->result = $aResult; - $this->errors = $aErrors; - $this->warnings = $aWarnings; - $this->infos = $aInfos; - } + public $status; // boolean + public $result; // array of SOAPResultMessage + public $errors; // array of SOAPResultLog + public $warnings; // array of SOAPResultLog + public $infos; // array of SOAPResultLog + + public function __construct($bStatus, $aResult, $aErrors, $aWarnings, $aInfos) + { + $this->status = $bStatus; + $this->result = $aResult; + $this->errors = $aErrors; + $this->warnings = $aWarnings; + $this->infos = $aInfos; + } } class SOAPSimpleResult { - public $status; // boolean - public $message; // string - - public function __construct($bStatus, $sMessage) - { - $this->status = $bStatus; - $this->message = $sMessage; - } + public $status; // boolean + public $message; // string + + public function __construct($bStatus, $sMessage) + { + $this->status = $bStatus; + $this->message = $sMessage; + } } - class SOAPMapping { - static function GetMapping() - { - $aSOAPMapping = array( - 'SearchCondition' => 'SOAPSearchCondition', - 'ExternalKeySearch' => 'SOAPExternalKeySearch', - 'AttributeValue' => 'SOAPAttributeValue', - 'LinkCreationSpec' => 'SOAPLinkCreationSpec', - 'KeyValue' => 'SOAPKeyValue', - 'LogMessage' => 'SOAPLogMessage', - 'ResultLog' => 'SOAPResultLog', - 'ResultData' => 'SOAPKeyValue', - 'ResultMessage' => 'SOAPResultMessage', - 'Result' => 'SOAPResult', - 'SimpleResult' => 'SOAPSimpleResult', - ); - return $aSOAPMapping; - } + public static function GetMapping() + { + $aSOAPMapping = [ + 'SearchCondition' => 'SOAPSearchCondition', + 'ExternalKeySearch' => 'SOAPExternalKeySearch', + 'AttributeValue' => 'SOAPAttributeValue', + 'LinkCreationSpec' => 'SOAPLinkCreationSpec', + 'KeyValue' => 'SOAPKeyValue', + 'LogMessage' => 'SOAPLogMessage', + 'ResultLog' => 'SOAPResultLog', + 'ResultData' => 'SOAPKeyValue', + 'ResultMessage' => 'SOAPResultMessage', + 'Result' => 'SOAPResult', + 'SimpleResult' => 'SOAPSimpleResult', + ]; + return $aSOAPMapping; + } } - -?> diff --git a/webservices/rest.php b/webservices/rest.php index 70f08cb0e2..c16b8df703 100644 --- a/webservices/rest.php +++ b/webservices/rest.php @@ -1,4 +1,5 @@ operations[] = array( - 'verb' => $sVerb, - 'description' => $sDescription, - 'extension' => $sServiceProviderClass, - ); - } + public $version; + public $operations; + + public function AddOperation($sVerb, $sDescription, $sServiceProviderClass) + { + $this->operations[] = [ + 'verb' => $sVerb, + 'description' => $sDescription, + 'extension' => $sServiceProviderClass, + ]; + } } if (!function_exists('json_last_error_msg')) { - function json_last_error_msg() { - static $ERRORS = array( - JSON_ERROR_NONE => 'No error', - JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', - JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', - JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', - JSON_ERROR_SYNTAX => 'Syntax error', - JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', - ); - - $error = json_last_error(); - return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; - } + function json_last_error_msg() + { + static $ERRORS = [ + JSON_ERROR_NONE => 'No error', + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', + JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', + JSON_ERROR_SYNTAX => 'Syntax error', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + ]; + + $error = json_last_error(); + return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; + } } //////////////////////////////////////////////////////////////////////////////// @@ -74,206 +75,169 @@ function json_last_error_msg() { //read json_data parameter via as a string (standard behaviour) $sJsonString = utils::ReadParam('json_data', null, false, 'raw_data'); -if (empty($sJsonString)){ - //N °3455: read json_data parameter via a file passed by http protocol - if(isset($_FILES['json_data']['tmp_name'])) - { - $sTmpFilePath = $_FILES['json_data']['tmp_name']; - if (is_file($sTmpFilePath)){ - $sValue = file_get_contents($sTmpFilePath); - unlink($sTmpFilePath); - if (! empty($sValue)){ - $sJsonString = utils::Sanitize($sValue, null, 'raw_data'); - } - } - } +if (empty($sJsonString)) { + //N °3455: read json_data parameter via a file passed by http protocol + if (isset($_FILES['json_data']['tmp_name'])) { + $sTmpFilePath = $_FILES['json_data']['tmp_name']; + if (is_file($sTmpFilePath)) { + $sValue = file_get_contents($sTmpFilePath); + unlink($sTmpFilePath); + if (! empty($sValue)) { + $sJsonString = utils::Sanitize($sValue, null, 'raw_data'); + } + } + } } $sProvider = ''; $oKPI = new ExecutionKPI(); -try -{ - utils::UseParamFile(); - - $oKPI->ComputeAndReport('Data model loaded'); +try { + utils::UseParamFile(); + + $oKPI->ComputeAndReport('Data model loaded'); // N°6358 - force credentials for REST calls LoginWebPage::ResetSession(true); - $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); $oKPI->ComputeAndReport('User login'); - if ($iRet == LoginWebPage::EXIT_CODE_OK) - { - // Extra validation of the profile - if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) - { - // Web services access is limited to the users with the profile REST Web Services - $iRet = LoginWebPage::EXIT_CODE_NOTAUTHORIZED; - } - } - if ($iRet != LoginWebPage::EXIT_CODE_OK) - { - switch($iRet) - { - case LoginWebPage::EXIT_CODE_MISSINGLOGIN: - throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER); - break; - - case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: - throw new Exception("Missing parameter 'auth_pwd'", RestResult::MISSING_AUTH_PWD); - break; - - case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: - throw new Exception("Invalid login", RestResult::UNAUTHORIZED); - break; - - case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: - throw new Exception("Portal user is not allowed", RestResult::UNAUTHORIZED); - break; - - case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: - throw new Exception("This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)", RestResult::UNAUTHORIZED); - break; - - default: - throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED); - } - } - - if ($sVersion == null) - { - throw new Exception("Missing parameter 'version' (e.g. '1.0')", RestResult::MISSING_VERSION); - } - - if ($sJsonString == null) - { - throw new Exception("Missing parameter 'json_data'", RestResult::MISSING_JSON); - } - - if (is_string($sJsonString)) - { - $aJsonData = @json_decode($sJsonString); + if ($iRet == LoginWebPage::EXIT_CODE_OK) { + // Extra validation of the profile + if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) { + // Web services access is limited to the users with the profile REST Web Services + $iRet = LoginWebPage::EXIT_CODE_NOTAUTHORIZED; + } } - elseif(is_array($sJsonString)) - { + if ($iRet != LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + throw new Exception("Missing parameter 'auth_pwd'", RestResult::MISSING_AUTH_PWD); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + throw new Exception("Invalid login", RestResult::UNAUTHORIZED); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + throw new Exception("Portal user is not allowed", RestResult::UNAUTHORIZED); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + throw new Exception("This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)", RestResult::UNAUTHORIZED); + break; + + default: + throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED); + } + } + + if ($sVersion == null) { + throw new Exception("Missing parameter 'version' (e.g. '1.0')", RestResult::MISSING_VERSION); + } + + if ($sJsonString == null) { + throw new Exception("Missing parameter 'json_data'", RestResult::MISSING_JSON); + } + + if (is_string($sJsonString)) { + $aJsonData = @json_decode($sJsonString); + } elseif (is_array($sJsonString)) { $aJsonData = (object) $sJsonString; $sJsonString = json_encode($aJsonData); - } - else - { + } else { $aJsonData = null; } - if ($aJsonData == null) - { + if ($aJsonData == null) { throw new Exception('Parameter json_data is not a valid JSON structure', RestResult::INVALID_JSON); } - $oKPI->ComputeAndReport('Parameters validated'); - - - /** @var iRestServiceProvider[] $aProviders */ - $oKPI = new ExecutionKPI(); - $aProviders = array(); - foreach(get_declared_classes() as $sPHPClass) - { - $oRefClass = new ReflectionClass($sPHPClass); - if ($oRefClass->implementsInterface('iRestServiceProvider')) - { - $aProviders[] = new $sPHPClass; - } - } - - $aOpToRestService = array(); // verb => $oRestServiceProvider - /** @var iRestServiceProvider $oRestSP */ - foreach ($aProviders as $oRestSP) - { - $aOperations = $oRestSP->ListOperations($sVersion); - foreach ($aOperations as $aOpData) - { - $aOpToRestService[$aOpData['verb']] = array - ( - 'service_provider' => $oRestSP, - 'description' => $aOpData['description'], - ); - } - } - $oKPI->ComputeAndReport('iRestServiceProvider loaded with operations'); - - if (count($aOpToRestService) == 0) - { - throw new Exception("There is no service available for version '$sVersion'", RestResult::UNSUPPORTED_VERSION); - } - - - $sOperation = RestUtils::GetMandatoryParam($aJsonData, 'operation'); - if ($sOperation == 'list_operations') - { - $oResult = new RestResultListOperations(); - $oResult->message = "Operations: ".count($aOpToRestService); - $oResult->version = $sVersion; - foreach ($aOpToRestService as $sVerb => $aOpData) - { - $oResult->AddOperation($sVerb, $aOpData['description'], get_class($aOpData['service_provider'])); - } - } - else - { - if (!array_key_exists($sOperation, $aOpToRestService)) - { - throw new Exception("Unknown verb '$sOperation' in version '$sVersion'", RestResult::UNKNOWN_OPERATION); - } - /** @var iRestServiceProvider $oRS */ - $oRS = $aOpToRestService[$sOperation]['service_provider']; - $sProvider = get_class($oRS); - - if ($oRS instanceof iRestInputSanitizer) { - $sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString); - } - - CMDBObject::SetTrackOrigin('webservice-rest'); - $oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData); - } - $oKPI->ComputeAndReport('Operation finished'); -} -catch(Exception $e) -{ - $oResult = new RestResult(); - if ($e->GetCode() == 0) - { - $oResult->code = RestResult::INTERNAL_ERROR; - } - else - { - $oResult->code = $e->GetCode(); - } - $oResult->message = "Error: ".$e->GetMessage(); - $oKPI->ComputeAndReport('Exception catched'); + $oKPI->ComputeAndReport('Parameters validated'); + + /** @var iRestServiceProvider[] $aProviders */ + $oKPI = new ExecutionKPI(); + $aProviders = []; + foreach (get_declared_classes() as $sPHPClass) { + $oRefClass = new ReflectionClass($sPHPClass); + if ($oRefClass->implementsInterface('iRestServiceProvider')) { + $aProviders[] = new $sPHPClass(); + } + } + + $aOpToRestService = []; // verb => $oRestServiceProvider + /** @var iRestServiceProvider $oRestSP */ + foreach ($aProviders as $oRestSP) { + $aOperations = $oRestSP->ListOperations($sVersion); + foreach ($aOperations as $aOpData) { + $aOpToRestService[$aOpData['verb']] = + [ + 'service_provider' => $oRestSP, + 'description' => $aOpData['description'], + ]; + } + } + $oKPI->ComputeAndReport('iRestServiceProvider loaded with operations'); + + if (count($aOpToRestService) == 0) { + throw new Exception("There is no service available for version '$sVersion'", RestResult::UNSUPPORTED_VERSION); + } + + $sOperation = RestUtils::GetMandatoryParam($aJsonData, 'operation'); + if ($sOperation == 'list_operations') { + $oResult = new RestResultListOperations(); + $oResult->message = "Operations: ".count($aOpToRestService); + $oResult->version = $sVersion; + foreach ($aOpToRestService as $sVerb => $aOpData) { + $oResult->AddOperation($sVerb, $aOpData['description'], get_class($aOpData['service_provider'])); + } + } else { + if (!array_key_exists($sOperation, $aOpToRestService)) { + throw new Exception("Unknown verb '$sOperation' in version '$sVersion'", RestResult::UNKNOWN_OPERATION); + } + /** @var iRestServiceProvider $oRS */ + $oRS = $aOpToRestService[$sOperation]['service_provider']; + $sProvider = get_class($oRS); + + if ($oRS instanceof iRestInputSanitizer) { + $sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString); + } + + CMDBObject::SetTrackOrigin('webservice-rest'); + $oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData); + } + $oKPI->ComputeAndReport('Operation finished'); +} catch (Exception $e) { + $oResult = new RestResult(); + if ($e->GetCode() == 0) { + $oResult->code = RestResult::INTERNAL_ERROR; + } else { + $oResult->code = $e->GetCode(); + } + $oResult->message = "Error: ".$e->GetMessage(); + $oKPI->ComputeAndReport('Exception catched'); } // Output the results // $sResponse = json_encode($oResult); - -if ($sResponse === false) -{ - $oJsonIssue = new RestResult(); - $oJsonIssue->code = RestResult::INTERNAL_ERROR; - $oJsonIssue->message = 'json encoding failed with message: '.json_last_error_msg().'. Full response structure for debugging purposes (print_r+bin2hex): '.bin2hex(print_r($oResult, true)); - $sResponse = json_encode($oJsonIssue); +if ($sResponse === false) { + $oJsonIssue = new RestResult(); + $oJsonIssue->code = RestResult::INTERNAL_ERROR; + $oJsonIssue->message = 'json encoding failed with message: '.json_last_error_msg().'. Full response structure for debugging purposes (print_r+bin2hex): '.bin2hex(print_r($oResult, true)); + $sResponse = json_encode($oJsonIssue); } - $sCallback = utils::ReadParam('callback', null); -if ($sCallback == null) -{ - $oP = new JsonPage(); -} -else -{ - $oP = new JsonPPage($sCallback); +if ($sCallback == null) { + $oP = new JsonPage(); +} else { + $oP = new JsonPPage($sCallback); } $oP->add_header('Access-Control-Allow-Origin: *'); $oP->SetData(json_decode($sResponse, true)); @@ -283,31 +247,29 @@ function json_last_error_msg() { // Log usage // -if (MetaModel::GetConfig()->Get('log_rest_service')) -{ - $oLog = new EventRestService(); - $oLog->SetTrim('userinfo', UserRights::GetUser()); - $oLog->Set('version', $sVersion); - $oLog->Set('operation', $sOperation); +if (MetaModel::GetConfig()->Get('log_rest_service')) { + $oLog = new EventRestService(); + $oLog->SetTrim('userinfo', UserRights::GetUser()); + $oLog->Set('version', $sVersion); + $oLog->Set('operation', $sOperation); $oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString); - $oLog->Set('provider', $sProvider); - $sMessage = $oResult->message; - if (empty($oResult->message)) - { - $sMessage = 'Ok'; - } - $oLog->SetTrim('message', $sMessage); - $oLog->Set('code', $oResult->code); - $oResult->SanitizeContent(); - $iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; - $sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT); - $sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode); - !StringFitsInLogField( $sJsonOuputWithPrettyPrinting) ? - $oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty - $oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting); - - $oLog->DBInsertNoReload(); + $oLog->Set('provider', $sProvider); + $sMessage = $oResult->message; + if (empty($oResult->message)) { + $sMessage = 'Ok'; + } + $oLog->SetTrim('message', $sMessage); + $oLog->Set('code', $oResult->code); + $oResult->SanitizeContent(); + $iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + $sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT); + $sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode); + !StringFitsInLogField($sJsonOuputWithPrettyPrinting) ? + $oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty + $oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting); + + $oLog->DBInsertNoReload(); } /** @@ -315,5 +277,5 @@ function json_last_error_msg() { */ function StringFitsInLogField(string $sLog): bool { - return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260 -} \ No newline at end of file + return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260 +} diff --git a/webservices/soapserver.php b/webservices/soapserver.php index 2d9b49ec46..e0b01ac4ab 100644 --- a/webservices/soapserver.php +++ b/webservices/soapserver.php @@ -1,4 +1,5 @@ $aSOAPMapping - ) +$oSoapServer = new SoapServer( + $sWsdlUri, + [ + 'classmap' => $aSOAPMapping + ] ); // $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION); -if (!empty($sServiceCategory)) -{ - $sServiceClass = $sServiceCategory; - if (!class_exists($sServiceClass)) - { - // not a valid class name (not a PHP class at all) - throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not a PHP class"); - } - elseif (!is_subclass_of($sServiceClass, 'WebServicesBase')) - { - // not a valid class name (not deriving from WebServicesBase) - throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not derived from WebServicesBase"); - } - else - { - $oSoapServer->setClass($sServiceClass, null); - } -} -else -{ - $oSoapServer->setClass('BasicServices', null); +if (!empty($sServiceCategory)) { + $sServiceClass = $sServiceCategory; + if (!class_exists($sServiceClass)) { + // not a valid class name (not a PHP class at all) + throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not a PHP class"); + } elseif (!is_subclass_of($sServiceClass, 'WebServicesBase')) { + // not a valid class name (not deriving from WebServicesBase) + throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not derived from WebServicesBase"); + } else { + $oSoapServer->setClass($sServiceClass, null); + } +} else { + $oSoapServer->setClass('BasicServices', null); } -if ($_SERVER["REQUEST_METHOD"] == "POST") -{ - CMDBObject::SetTrackOrigin('webservice-soap'); - $oSoapServer->handle(); -} -else -{ - echo "This SOAP server can handle the following functions: "; - $aFunctions = $oSoapServer->getFunctions(); - echo "
    \n"; - foreach($aFunctions as $sFunc) - { - if ($sFunc == 'GetWSDLContents') continue; +if ($_SERVER["REQUEST_METHOD"] == "POST") { + CMDBObject::SetTrackOrigin('webservice-soap'); + $oSoapServer->handle(); +} else { + echo "This SOAP server can handle the following functions: "; + $aFunctions = $oSoapServer->getFunctions(); + echo "
      \n"; + foreach ($aFunctions as $sFunc) { + if ($sFunc == 'GetWSDLContents') { + continue; + } - echo "
    • $sFunc
    • \n"; - } - echo "
    \n"; - echo "

    Here the WSDL file

    "; + echo "

  • $sFunc
  • \n"; + } + echo "
\n"; + echo "

Here the WSDL file

"; - echo "You may also want to try the following service categories: "; - echo "

    \n"; - foreach(get_declared_classes() as $sPHPClass) - { - if (is_subclass_of($sPHPClass, 'WebServicesBase')) - { - $sServiceCategory = $sPHPClass; - $sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php'; - $sSoapServerUri .= "?service_category=$sServiceCategory"; - echo "
  • $sServiceCategory
  • \n"; - } - } - echo "
\n"; + echo "You may also want to try the following service categories: "; + echo "
    \n"; + foreach (get_declared_classes() as $sPHPClass) { + if (is_subclass_of($sPHPClass, 'WebServicesBase')) { + $sServiceCategory = $sPHPClass; + $sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php'; + $sSoapServerUri .= "?service_category=$sServiceCategory"; + echo "
  • $sServiceCategory
  • \n"; + } + } + echo "
\n"; } -?> diff --git a/webservices/status.php b/webservices/status.php index 636ab4ec64..55f297519d 100644 --- a/webservices/status.php +++ b/webservices/status.php @@ -6,16 +6,13 @@ use Combodo\iTop\Application\Status\Status; //Do check Status -try -{ - new Status(); - $aResult = ['status' => STATUS_RUNNING, 'code' => RestResult::OK, 'message' => '']; -} -catch (Exception $e) -{ - $iCode = (defined('\RestResult::INTERNAL_ERROR')) ? RestResult::INTERNAL_ERROR : 100; - $aResult = ['status' => STATUS_ERROR, 'code' => $iCode, 'message' => $e->getMessage()]; - http_response_code(500); +try { + new Status(); + $aResult = ['status' => STATUS_RUNNING, 'code' => RestResult::OK, 'message' => '']; +} catch (Exception $e) { + $iCode = (defined('\RestResult::INTERNAL_ERROR')) ? RestResult::INTERNAL_ERROR : 100; + $aResult = ['status' => STATUS_ERROR, 'code' => $iCode, 'message' => $e->getMessage()]; + http_response_code(500); } //Set headers, based on webservices/rest.php diff --git a/webservices/webservices.basic.php b/webservices/webservices.basic.php index bf8c364474..336ace0713 100644 --- a/webservices/webservices.basic.php +++ b/webservices/webservices.basic.php @@ -1,9 +1,10 @@ - /** * Implementation of iTop SOAP services * @@ -26,267 +26,234 @@ require_once(APPROOT.'/webservices/webservices.class.inc.php'); - class BasicServices extends WebServicesBase { - static protected function GetWSDLFilePath() - { - return APPROOT.'/webservices/itop.wsdl.tpl'; - } - - /** - * Get the server version (TODO: get it dynamically, where ?) - * - * @return string WebServiceResult - */ - static public function GetVersion() - { - if (ITOP_REVISION == 'svn') - { - $sVersionString = ITOP_VERSION.' [dev]'; - } - else - { - // This is a build made from SVN, let display the full information - $sVersionString = ITOP_VERSION_FULL." ".ITOP_BUILD_DATE; - } - - return $sVersionString; - } - - public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) - { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) - { - $oRes = new WebServiceResultFailedLogin($sLogin); - $this->LogUsage(__FUNCTION__, $oRes); - - return $oRes->ToSoapStructure(); - } - UserRights::Login($sLogin); - - $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); - $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); - $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); - $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); - $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); - - $aImpactedCIs = array(); - if (is_null($aSOAPImpactedCIs)) $aSOAPImpactedCIs = array(); - foreach($aSOAPImpactedCIs as $oImpactedCIs) - { - $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); - } - - $oRes = $this->_CreateResponseTicket - ( - 'UserRequest', - $sTitle, - $sDescription, - $aCallerDesc, - $aCustomerDesc, - $aServiceDesc, - $aServiceSubcategoryDesc, - $sProduct, - $aWorkgroupDesc, - $aImpactedCIs, - $sImpact, - $sUrgency - ); - return $oRes->ToSoapStructure(); - } - - public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) - { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) - { - $oRes = new WebServiceResultFailedLogin($sLogin); - $this->LogUsage(__FUNCTION__, $oRes); - - return $oRes->ToSoapStructure(); - } - UserRights::Login($sLogin); - - - if (!class_exists('Incident')) - { - $oRes = new WebServiceResult(); - $oRes->LogError("The class Incident does not exist. Did you install the Incident Management (ITIL) module ?"); - return $oRes->ToSoapStructure(); - } - - $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); - $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); - $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); - $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); - $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); - - $aImpactedCIs = array(); - if (is_null($aSOAPImpactedCIs)) $aSOAPImpactedCIs = array(); - foreach($aSOAPImpactedCIs as $oImpactedCIs) - { - $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); - } - - $oRes = $this->_CreateResponseTicket - ( - 'Incident', - $sTitle, - $sDescription, - $aCallerDesc, - $aCustomerDesc, - $aServiceDesc, - $aServiceSubcategoryDesc, - $sProduct, - $aWorkgroupDesc, - $aImpactedCIs, - $sImpact, - $sUrgency - ); - return $oRes->ToSoapStructure(); - } - - /** - * Create an ResponseTicket (Incident or UserRequest) from an external system - * Some CIs might be specified (by their name/IP) - * - * @param string sClass The class of the ticket: Incident or UserRequest - * @param string sTitle - * @param string sDescription - * @param array aCallerDesc - * @param array aCustomerDesc - * @param array aServiceDesc - * @param array aServiceSubcategoryDesc - * @param string sProduct - * @param array aWorkgroupDesc - * @param array aImpactedCIs - * @param string sImpact - * @param string sUrgency - * - * @return WebServiceResult - */ - protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCallerDesc, $aCustomerDesc, $aServiceDesc, $aServiceSubcategoryDesc, $sProduct, $aWorkgroupDesc, $aImpactedCIs, $sImpact, $sUrgency) - { - - $oRes = new WebServiceResult(); - - try - { - CMDBObject::SetTrackInfo('Administrator'); - - $oNewTicket = MetaModel::NewObject($sClass); - $this->MyObjectSetScalar('title', 'title', $sTitle, $oNewTicket, $oRes); - $this->MyObjectSetScalar('description', 'description', $sDescription, $oNewTicket, $oRes); - - $this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes); - $this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes); - - $this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes); - if (!array_key_exists('service_id', $aServiceSubcategoryDesc)) - { - $aServiceSubcategoryDesc['service_id'] = $oNewTicket->Get('service_id'); - } - $this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes); - if (MetaModel::IsValidAttCode($sClass, 'product')) - { - // 1.x data models - $this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes); - } - - if (MetaModel::IsValidAttCode($sClass, 'workgroup_id')) - { - // 1.x data models - $this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); - } - else if (MetaModel::IsValidAttCode($sClass, 'team_id')) - { - // 2.x data models - $this->MyObjectSetExternalKey('team_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); - } - - - if (MetaModel::IsValidAttCode($sClass, 'ci_list')) - { - // 1.x data models - $aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); - } - else if (MetaModel::IsValidAttCode($sClass, 'functionalcis_list')) - { - // 2.x data models - $aDevicesNotFound = $this->AddLinkedObjects('functionalcis_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); - } - - if (count($aDevicesNotFound) > 0) - { - $this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes); - } - else - { - $this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes); - } - - $this->MyObjectSetScalar('impact', 'impact', $sImpact, $oNewTicket, $oRes); - $this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes); - - $this->MyObjectInsert($oNewTicket, 'created', $oRes); - } - catch (CoreException $e) - { - $oRes->LogError($e->getMessage()); - } - catch (Exception $e) - { - $oRes->LogError($e->getMessage()); - } - - $this->LogUsage(__FUNCTION__, $oRes); - return $oRes; - } - - /** - * Given an OQL, returns a set of objects (several objects could be on the same row) - * - * @param string sOQL - */ - public function SearchObjects($sLogin, $sPassword, $sOQL) - { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) - { - $oRes = new WebServiceResultFailedLogin($sLogin); - $this->LogUsage(__FUNCTION__, $oRes); - - return $oRes->ToSoapStructure(); - } - UserRights::Login($sLogin); - - $oRes = $this->_SearchObjects($sOQL); - return $oRes->ToSoapStructure(); - } - - protected function _SearchObjects($sOQL) - { - $oRes = new WebServiceResult(); - try - { - $oSearch = DBObjectSearch::FromOQL($sOQL); - $oSet = new DBObjectSet($oSearch); - $aData = $oSet->ToArrayOfValues(); - foreach($aData as $iRow => $aRow) - { - $oRes->AddResultRow("row_$iRow", $aRow); - } - } - catch (CoreException $e) - { - $oRes->LogError($e->getMessage()); - } - catch (Exception $e) - { - $oRes->LogError($e->getMessage()); - } - - $this->LogUsage(__FUNCTION__, $oRes); - return $oRes; - } + protected static function GetWSDLFilePath() + { + return APPROOT.'/webservices/itop.wsdl.tpl'; + } + + /** + * Get the server version (TODO: get it dynamically, where ?) + * + * @return string WebServiceResult + */ + public static function GetVersion() + { + if (ITOP_REVISION == 'svn') { + $sVersionString = ITOP_VERSION.' [dev]'; + } else { + // This is a build made from SVN, let display the full information + $sVersionString = ITOP_VERSION_FULL." ".ITOP_BUILD_DATE; + } + + return $sVersionString; + } + + public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) + { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + $oRes = new WebServiceResultFailedLogin($sLogin); + $this->LogUsage(__FUNCTION__, $oRes); + + return $oRes->ToSoapStructure(); + } + UserRights::Login($sLogin); + + $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); + $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); + $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); + $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); + $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); + + $aImpactedCIs = []; + if (is_null($aSOAPImpactedCIs)) { + $aSOAPImpactedCIs = []; + } + foreach ($aSOAPImpactedCIs as $oImpactedCIs) { + $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); + } + + $oRes = $this->_CreateResponseTicket( + 'UserRequest', + $sTitle, + $sDescription, + $aCallerDesc, + $aCustomerDesc, + $aServiceDesc, + $aServiceSubcategoryDesc, + $sProduct, + $aWorkgroupDesc, + $aImpactedCIs, + $sImpact, + $sUrgency + ); + return $oRes->ToSoapStructure(); + } + + public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) + { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + $oRes = new WebServiceResultFailedLogin($sLogin); + $this->LogUsage(__FUNCTION__, $oRes); + + return $oRes->ToSoapStructure(); + } + UserRights::Login($sLogin); + + if (!class_exists('Incident')) { + $oRes = new WebServiceResult(); + $oRes->LogError("The class Incident does not exist. Did you install the Incident Management (ITIL) module ?"); + return $oRes->ToSoapStructure(); + } + + $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); + $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); + $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); + $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); + $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); + + $aImpactedCIs = []; + if (is_null($aSOAPImpactedCIs)) { + $aSOAPImpactedCIs = []; + } + foreach ($aSOAPImpactedCIs as $oImpactedCIs) { + $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); + } + + $oRes = $this->_CreateResponseTicket( + 'Incident', + $sTitle, + $sDescription, + $aCallerDesc, + $aCustomerDesc, + $aServiceDesc, + $aServiceSubcategoryDesc, + $sProduct, + $aWorkgroupDesc, + $aImpactedCIs, + $sImpact, + $sUrgency + ); + return $oRes->ToSoapStructure(); + } + + /** + * Create an ResponseTicket (Incident or UserRequest) from an external system + * Some CIs might be specified (by their name/IP) + * + * @param string sClass The class of the ticket: Incident or UserRequest + * @param string sTitle + * @param string sDescription + * @param array aCallerDesc + * @param array aCustomerDesc + * @param array aServiceDesc + * @param array aServiceSubcategoryDesc + * @param string sProduct + * @param array aWorkgroupDesc + * @param array aImpactedCIs + * @param string sImpact + * @param string sUrgency + * + * @return WebServiceResult + */ + protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCallerDesc, $aCustomerDesc, $aServiceDesc, $aServiceSubcategoryDesc, $sProduct, $aWorkgroupDesc, $aImpactedCIs, $sImpact, $sUrgency) + { + + $oRes = new WebServiceResult(); + + try { + CMDBObject::SetTrackInfo('Administrator'); + + $oNewTicket = MetaModel::NewObject($sClass); + $this->MyObjectSetScalar('title', 'title', $sTitle, $oNewTicket, $oRes); + $this->MyObjectSetScalar('description', 'description', $sDescription, $oNewTicket, $oRes); + + $this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes); + $this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes); + + $this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes); + if (!array_key_exists('service_id', $aServiceSubcategoryDesc)) { + $aServiceSubcategoryDesc['service_id'] = $oNewTicket->Get('service_id'); + } + $this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes); + if (MetaModel::IsValidAttCode($sClass, 'product')) { + // 1.x data models + $this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes); + } + + if (MetaModel::IsValidAttCode($sClass, 'workgroup_id')) { + // 1.x data models + $this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); + } elseif (MetaModel::IsValidAttCode($sClass, 'team_id')) { + // 2.x data models + $this->MyObjectSetExternalKey('team_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); + } + + if (MetaModel::IsValidAttCode($sClass, 'ci_list')) { + // 1.x data models + $aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); + } elseif (MetaModel::IsValidAttCode($sClass, 'functionalcis_list')) { + // 2.x data models + $aDevicesNotFound = $this->AddLinkedObjects('functionalcis_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); + } + + if (count($aDevicesNotFound) > 0) { + $this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes); + } else { + $this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes); + } + + $this->MyObjectSetScalar('impact', 'impact', $sImpact, $oNewTicket, $oRes); + $this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes); + + $this->MyObjectInsert($oNewTicket, 'created', $oRes); + } catch (CoreException $e) { + $oRes->LogError($e->getMessage()); + } catch (Exception $e) { + $oRes->LogError($e->getMessage()); + } + + $this->LogUsage(__FUNCTION__, $oRes); + return $oRes; + } + + /** + * Given an OQL, returns a set of objects (several objects could be on the same row) + * + * @param string sOQL + */ + public function SearchObjects($sLogin, $sPassword, $sOQL) + { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + $oRes = new WebServiceResultFailedLogin($sLogin); + $this->LogUsage(__FUNCTION__, $oRes); + + return $oRes->ToSoapStructure(); + } + UserRights::Login($sLogin); + + $oRes = $this->_SearchObjects($sOQL); + return $oRes->ToSoapStructure(); + } + + protected function _SearchObjects($sOQL) + { + $oRes = new WebServiceResult(); + try { + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new DBObjectSet($oSearch); + $aData = $oSet->ToArrayOfValues(); + foreach ($aData as $iRow => $aRow) { + $oRes->AddResultRow("row_$iRow", $aRow); + } + } catch (CoreException $e) { + $oRes->LogError($e->getMessage()); + } catch (Exception $e) { + $oRes->LogError($e->getMessage()); + } + + $this->LogUsage(__FUNCTION__, $oRes); + return $oRes; + } } -?> diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index 5df50d4270..f411b49d8d 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -1,9 +1,10 @@ - /** * Implementation of iTop SOAP services * @@ -24,7 +24,6 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ - require_once(APPROOT.'/webservices/itopsoaptypes.class.inc.php'); /** @@ -34,202 +33,197 @@ */ class WebServiceResult { - - /** - * Overall status - * - * @var m_bStatus - */ - public $m_bStatus; - - /** - * Error log - * - * @var m_aErrors - */ - public $m_aErrors; - - /** - * Warning log - * - * @var m_aWarnings - */ - public $m_aWarnings; - - /** - * Information log - * - * @var m_aInfos - */ - public $m_aInfos; - - /** - * Constructor - * - * @param status $bStatus - */ - public function __construct() - { - $this->m_bStatus = true; - $this->m_aResult = array(); - $this->m_aErrors = array(); - $this->m_aWarnings = array(); - $this->m_aInfos = array(); - } - - public function ToSoapStructure() - { - $aResults = array(); - foreach($this->m_aResult as $sLabel => $aData) - { - $aValues = array(); - foreach($aData as $sKey => $value) - { - $aValues[] = new SOAPKeyValue($sKey, $value); - } - $aResults[] = new SoapResultMessage($sLabel, $aValues); - } - $aInfos = array(); - foreach($this->m_aInfos as $sMessage) - { - $aInfos[] = new SoapLogMessage($sMessage); - } - $aWarnings = array(); - foreach($this->m_aWarnings as $sMessage) - { - $aWarnings[] = new SoapLogMessage($sMessage); - } - $aErrors = array(); - foreach($this->m_aErrors as $sMessage) - { - $aErrors[] = new SoapLogMessage($sMessage); - } - - $oRet = new SOAPResult( - $this->m_bStatus, - $aResults, - new SOAPResultLog($aErrors), - new SOAPResultLog($aWarnings), - new SOAPResultLog($aInfos) - ); - - return $oRet; - } - - /** - * Did the current processing encounter a stopper issue ? - * - * @return bool - */ - public function IsOk() - { - return $this->m_bStatus; - } - - /** - * Add result details - object reference - * - * @param string sLabel - * @param object oObject - */ - public function AddResultObject($sLabel, $oObject) - { - $oAppContext = new ApplicationContext(); - $this->m_aResult[$sLabel] = array( - 'id' => $oObject->GetKey(), - 'name' => $oObject->GetRawName(), - 'url' => $oAppContext->MakeObjectUrl(get_class($oObject), $oObject->GetKey(), null, false), // Raw URL without HTML tags - ); - } - - /** - * Add result details - a table row - * - * @param string sLabel - * @param object oObject - */ - public function AddResultRow($sLabel, $aRow) - { - $this->m_aResult[$sLabel] = $aRow; - } - - /** - * Log an error - * - * @param string sDescription - */ - public function LogError($sDescription) - { - $this->m_aErrors[] = $sDescription; - // Note: SOAP do transform false into null - $this->m_bStatus = 0; - } - - /** - * Log a warning - * - * @param string sDescription - */ - public function LogWarning($sDescription) - { - $this->m_aWarnings[] = $sDescription; - } - - /** - * Log an error or a warning - * - * @param string sDescription - * @param boolean bIsStopper - */ - public function LogIssue($sDescription, $bIsStopper = true) - { - if ($bIsStopper) $this->LogError($sDescription); - else $this->LogWarning($sDescription); - } - - /** - * Log operation details - * - * @param description $sDescription - */ - public function LogInfo($sDescription) - { - $this->m_aInfos[] = $sDescription; - } - - protected static function LogToText($aLog) - { - return implode("\n", $aLog); - } - - public function GetInfoAsText() - { - return self::LogToText($this->m_aInfos); - } - - public function GetWarningsAsText() - { - return self::LogToText($this->m_aWarnings); - } - - public function GetErrorsAsText() - { - return self::LogToText($this->m_aErrors); - } - - public function GetReturnedDataAsText() - { - $sRet = ''; - foreach ($this->m_aResult as $sKey => $value) - { - $sRet .= "===== $sKey =====\n"; - $sRet .= print_r($value, true); - } - return $sRet; - } + /** + * Overall status + * + * @var m_bStatus + */ + public $m_bStatus; + + /** + * Error log + * + * @var m_aErrors + */ + public $m_aErrors; + + /** + * Warning log + * + * @var m_aWarnings + */ + public $m_aWarnings; + + /** + * Information log + * + * @var m_aInfos + */ + public $m_aInfos; + + /** + * Constructor + * + * @param status $bStatus + */ + public function __construct() + { + $this->m_bStatus = true; + $this->m_aResult = []; + $this->m_aErrors = []; + $this->m_aWarnings = []; + $this->m_aInfos = []; + } + + public function ToSoapStructure() + { + $aResults = []; + foreach ($this->m_aResult as $sLabel => $aData) { + $aValues = []; + foreach ($aData as $sKey => $value) { + $aValues[] = new SOAPKeyValue($sKey, $value); + } + $aResults[] = new SoapResultMessage($sLabel, $aValues); + } + $aInfos = []; + foreach ($this->m_aInfos as $sMessage) { + $aInfos[] = new SoapLogMessage($sMessage); + } + $aWarnings = []; + foreach ($this->m_aWarnings as $sMessage) { + $aWarnings[] = new SoapLogMessage($sMessage); + } + $aErrors = []; + foreach ($this->m_aErrors as $sMessage) { + $aErrors[] = new SoapLogMessage($sMessage); + } + + $oRet = new SOAPResult( + $this->m_bStatus, + $aResults, + new SOAPResultLog($aErrors), + new SOAPResultLog($aWarnings), + new SOAPResultLog($aInfos) + ); + + return $oRet; + } + + /** + * Did the current processing encounter a stopper issue ? + * + * @return bool + */ + public function IsOk() + { + return $this->m_bStatus; + } + + /** + * Add result details - object reference + * + * @param string sLabel + * @param object oObject + */ + public function AddResultObject($sLabel, $oObject) + { + $oAppContext = new ApplicationContext(); + $this->m_aResult[$sLabel] = [ + 'id' => $oObject->GetKey(), + 'name' => $oObject->GetRawName(), + 'url' => $oAppContext->MakeObjectUrl(get_class($oObject), $oObject->GetKey(), null, false), // Raw URL without HTML tags + ]; + } + + /** + * Add result details - a table row + * + * @param string sLabel + * @param object oObject + */ + public function AddResultRow($sLabel, $aRow) + { + $this->m_aResult[$sLabel] = $aRow; + } + + /** + * Log an error + * + * @param string sDescription + */ + public function LogError($sDescription) + { + $this->m_aErrors[] = $sDescription; + // Note: SOAP do transform false into null + $this->m_bStatus = 0; + } + + /** + * Log a warning + * + * @param string sDescription + */ + public function LogWarning($sDescription) + { + $this->m_aWarnings[] = $sDescription; + } + + /** + * Log an error or a warning + * + * @param string sDescription + * @param boolean bIsStopper + */ + public function LogIssue($sDescription, $bIsStopper = true) + { + if ($bIsStopper) { + $this->LogError($sDescription); + } else { + $this->LogWarning($sDescription); + } + } + + /** + * Log operation details + * + * @param description $sDescription + */ + public function LogInfo($sDescription) + { + $this->m_aInfos[] = $sDescription; + } + + protected static function LogToText($aLog) + { + return implode("\n", $aLog); + } + + public function GetInfoAsText() + { + return self::LogToText($this->m_aInfos); + } + + public function GetWarningsAsText() + { + return self::LogToText($this->m_aWarnings); + } + + public function GetErrorsAsText() + { + return self::LogToText($this->m_aErrors); + } + + public function GetReturnedDataAsText() + { + $sRet = ''; + foreach ($this->m_aResult as $sKey => $value) { + $sRet .= "===== $sKey =====\n"; + $sRet .= print_r($value, true); + } + return $sRet; + } } - /** * Generic response of iTop SOAP services - failed login * @@ -237,11 +231,11 @@ public function GetReturnedDataAsText() */ class WebServiceResultFailedLogin extends WebServiceResult { - public function __construct($sLogin) - { - parent::__construct(); - $this->LogError("Wrong credentials: '$sLogin'"); - } + public function __construct($sLogin) + { + parent::__construct(); + $this->LogError("Wrong credentials: '$sLogin'"); + } } /** @@ -251,355 +245,319 @@ public function __construct($sLogin) */ abstract class WebServicesBase { - static public function GetWSDLContents($sServiceCategory = '') - { - if ($sServiceCategory == '') - { - $sServiceCategory = 'BasicServices'; - } - $sWsdlFilePath = call_user_func(array($sServiceCategory, 'GetWSDLFilePath')); - return file_get_contents($sWsdlFilePath); - } - - /** - * Helper to log a service delivery - * - * @param string sVerb - * @param array aArgs - * @param WebServiceResult oRes - * - */ - protected function LogUsage($sVerb, $oRes) - { - if (!MetaModel::IsLogEnabledWebService()) return; - - $oLog = new EventWebService(); - if ($oRes->IsOk()) - { - $oLog->Set('message', $sVerb.' was successfully invoked'); - } - else - { - $oLog->Set('message', $sVerb.' returned errors'); - } - $oLog->Set('userinfo', UserRights::GetUser()); - $oLog->Set('verb', $sVerb); - $oLog->Set('result', $oRes->IsOk()); - $this->TrimAndSetValue($oLog, 'log_info', (string)$oRes->GetInfoAsText()); - $this->TrimAndSetValue($oLog, 'log_warning', (string)$oRes->GetWarningsAsText()); - $this->TrimAndSetValue($oLog, 'log_error', (string)$oRes->GetErrorsAsText()); - $this->TrimAndSetValue($oLog, 'data', (string)$oRes->GetReturnedDataAsText()); - $oLog->DBInsertNoReload(); - } - - protected function TrimAndSetValue($oLog, $sAttCode, $sValue) - { - $oAttDef = MetaModel::GetAttributeDef(get_class($oLog), $sAttCode); - if (is_object($oAttDef)) - { - $iMaxSize = $oAttDef->GetMaxSize(); - if ($iMaxSize && (mb_strlen($sValue) > $iMaxSize)) { - $sValue = mb_substr($sValue, 0, $iMaxSize); - } - $oLog->Set($sAttCode, $sValue); - } - } - - /** - * Helper to set a scalar attribute - * - * @param string sAttCode - * @param scalar value - * @param DBObject oTargetObj - * @param WebServiceResult oRes - * - */ - protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) - { - $res = $oTargetObj->CheckValue($sAttCode, $value); - if ($res === true) - { - $oTargetObj->Set($sAttCode, $value); - } - else - { - // $res contains the error description - $oRes->LogError("Unexpected value for parameter $sParamName: $res"); - } - } - - /** - * Helper to set an external key - * - * @param string sAttCode - * @param array aExtKeyDesc - * @param DBObject oTargetObj - * @param WebServiceResult oRes - * - */ - protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, &$oTargetObj, &$oRes) - { - $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); - - $bIsMandatory = !$oExtKey->IsNullAllowed(); - - if (is_null($aExtKeyDesc)) - { - if ($bIsMandatory) - { - $oRes->LogError("Parameter $sParamName: found null for a mandatory key"); - } - else - { - // skip silently - return; - } - } - - if (count($aExtKeyDesc) == 0) - { - $oRes->LogIssue("Parameter $sParamName: no search condition has been specified", $bIsMandatory); - return; - } - - $sKeyClass = $oExtKey->GetTargetClass(); - $oReconFilter = new DBObjectSearch($sKeyClass); - foreach ($aExtKeyDesc as $sForeignAttCode => $value) - { - if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) - { - $aCodes = MetaModel::GetFiltersList($sKeyClass); - $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; - $oRes->LogIssue($sMsg, $bIsMandatory); - } - // The foreign attribute is one of our reconciliation key - $oReconFilter->AddCondition($sForeignAttCode, $value, '='); - } - $oExtObjects = new CMDBObjectSet($oReconFilter); - switch($oExtObjects->Count()) - { - case 0: - $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL(true)."')"; - $oRes->LogIssue($sMsg, $bIsMandatory); - break; - case 1: - // Do change the external key attribute - $oForeignObj = $oExtObjects->Fetch(); - $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); - - // Report it (no need to report if the object already had this value - if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) - { - $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); - } - break; - default: - $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL(true)."')"; - $oRes->LogIssue($sMsg, $bIsMandatory); - } - } - - /** - * Helper to link objects - * - * @param string sLinkAttCode - * @param string sLinkedClass - * @param array $aLinkList - * @param DBObject oTargetObj - * @param WebServiceResult oRes - * - * @return array List of objects that could not be found - */ - protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) - { - $oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode); - $sLinkClass = $oLinkAtt->GetLinkedClass(); - $sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote(); - - $aItemsFound = array(); - $aItemsNotFound = array(); - - if (is_null($aLinkList)) - { - return $aItemsNotFound; - } - - foreach ($aLinkList as $aItemData) - { - if (!array_key_exists('class', $aItemData)) - { - $oRes->LogWarning("Parameter $sParamName: missing 'class' specification"); - continue; // skip - } - $sTargetClass = $aItemData['class']; - if (!MetaModel::IsValidClass($sTargetClass)) - { - $oRes->LogError("Parameter $sParamName: invalid class '$sTargetClass'"); - continue; // skip - } - if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) - { - $oRes->LogError("Parameter $sParamName: '$sTargetClass' is not a child class of '$sLinkedClass'"); - continue; // skip - } - $oReconFilter = new DBObjectSearch($sTargetClass); - $aCIStringDesc = array(); - foreach ($aItemData['search'] as $sAttCode => $value) { - if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { - $aCodes = MetaModel::GetFiltersList($sTargetClass); - $oRes->LogError("Parameter $sParamName: '$sAttCode' is not a valid filter code for class '$sTargetClass', expecting a value in {".implode(', ', $aCodes)."}"); - continue 2; // skip the entire item - } - $aCIStringDesc[] = "$sAttCode: $value"; - - // The attribute is one of our reconciliation key - $oReconFilter->AddCondition($sAttCode, $value, '='); - } - if (count($aCIStringDesc) == 1) - { - // take the last and unique value to describe the object - $sItemDesc = $value; - } - else - { - // describe the object by the given keys - $sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')'; - } - - $oExtObjects = new CMDBObjectSet($oReconFilter); - switch($oExtObjects->Count()) - { - case 0: - $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL(true)."')"); - $aItemsNotFound[] = $sItemDesc; - break; - case 1: - $aItemsFound[] = array ( - 'object' => $oExtObjects->Fetch(), - 'link_values' => @$aItemData['link_values'], - 'desc' => $sItemDesc, - ); - break; - default: - $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL(true)."')"); - $aItemsNotFound[] = $sItemDesc; - } - } - - if (count($aItemsFound) > 0) - { - $aLinks = array(); - foreach($aItemsFound as $aItemData) - { - $oLink = MetaModel::NewObject($sLinkClass); - $oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey()); - foreach($aItemData['link_values'] as $sKey => $value) - { - if(!MetaModel::IsValidAttCode($sLinkClass, $sKey)) - { - $oRes->LogWarning("Parameter $sParamName: Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); - } - else - { - $oLink->Set($sKey, $value); - } - } - $aLinks[] = $oLink; - } - $oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks); - $oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet); - } - - return $aItemsNotFound; - } - - /** - * @param \CMDBObject $oTargetObj - * @param string $sResultLabel - * @param \WebServiceResult $oRes - * - * @throws \ArchivedObjectException - * @throws \CoreCannotSaveObjectException - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \CoreWarning - * @throws \MySQLException - * @throws \OQLException - * @throws \SecurityException - */ - protected function MyObjectInsert($oTargetObj, $sResultLabel, &$oRes) - { - if ($oRes->IsOk()) - { - list($bRes, $aIssues) = $oTargetObj->CheckToWrite(); - if ($bRes) - { - $iId = $oTargetObj->DBInsertNoReload(); - $oRes->LogInfo("Created object ".get_class($oTargetObj)."::$iId"); - $oRes->AddResultObject($sResultLabel, $oTargetObj); - } - else - { - $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); - foreach($aIssues as $iIssue => $sIssue) - { - $oRes->LogError("Issue #$iIssue: $sIssue"); - } - } - } - } - - - static protected function SoapStructToExternalKeySearch($oExternalKeySearch) - { - if (is_null($oExternalKeySearch)) return null; - if ($oExternalKeySearch->IsVoid()) return null; - - $aRes = array(); - foreach($oExternalKeySearch->conditions as $oSearchCondition) - { - $aRes[$oSearchCondition->attcode] = $oSearchCondition->value; - } - return $aRes; - } - - static protected function SoapStructToLinkCreationSpec(SoapLinkCreationSpec $oLinkCreationSpec) - { - $aRes = array - ( - 'class' => $oLinkCreationSpec->class, - 'search' => array(), - 'link_values' => array(), - ); - - foreach($oLinkCreationSpec->conditions as $oSearchCondition) - { - $aRes['search'][$oSearchCondition->attcode] = $oSearchCondition->value; - } - - foreach($oLinkCreationSpec->attributes as $oAttributeValue) - { - $aRes['link_values'][$oAttributeValue->attcode] = $oAttributeValue->value; - } - - return $aRes; - } - - static protected function SoapStructToAssociativeArray($aArrayOfAssocArray) - { - if (is_null($aArrayOfAssocArray)) return array(); - - $aRes = array(); - foreach($aArrayOfAssocArray as $aAssocArray) - { - $aRow = array(); - foreach ($aAssocArray as $oKeyValuePair) - { - $aRow[$oKeyValuePair->key] = $oKeyValuePair->value; - } - $aRes[] = $aRow; - } - return $aRes; - } + public static function GetWSDLContents($sServiceCategory = '') + { + if ($sServiceCategory == '') { + $sServiceCategory = 'BasicServices'; + } + $sWsdlFilePath = call_user_func([$sServiceCategory, 'GetWSDLFilePath']); + return file_get_contents($sWsdlFilePath); + } + + /** + * Helper to log a service delivery + * + * @param string sVerb + * @param array aArgs + * @param WebServiceResult oRes + * + */ + protected function LogUsage($sVerb, $oRes) + { + if (!MetaModel::IsLogEnabledWebService()) { + return; + } + + $oLog = new EventWebService(); + if ($oRes->IsOk()) { + $oLog->Set('message', $sVerb.' was successfully invoked'); + } else { + $oLog->Set('message', $sVerb.' returned errors'); + } + $oLog->Set('userinfo', UserRights::GetUser()); + $oLog->Set('verb', $sVerb); + $oLog->Set('result', $oRes->IsOk()); + $this->TrimAndSetValue($oLog, 'log_info', (string)$oRes->GetInfoAsText()); + $this->TrimAndSetValue($oLog, 'log_warning', (string)$oRes->GetWarningsAsText()); + $this->TrimAndSetValue($oLog, 'log_error', (string)$oRes->GetErrorsAsText()); + $this->TrimAndSetValue($oLog, 'data', (string)$oRes->GetReturnedDataAsText()); + $oLog->DBInsertNoReload(); + } + + protected function TrimAndSetValue($oLog, $sAttCode, $sValue) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oLog), $sAttCode); + if (is_object($oAttDef)) { + $iMaxSize = $oAttDef->GetMaxSize(); + if ($iMaxSize && (mb_strlen($sValue) > $iMaxSize)) { + $sValue = mb_substr($sValue, 0, $iMaxSize); + } + $oLog->Set($sAttCode, $sValue); + } + } + + /** + * Helper to set a scalar attribute + * + * @param string sAttCode + * @param scalar value + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + */ + protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) + { + $res = $oTargetObj->CheckValue($sAttCode, $value); + if ($res === true) { + $oTargetObj->Set($sAttCode, $value); + } else { + // $res contains the error description + $oRes->LogError("Unexpected value for parameter $sParamName: $res"); + } + } + + /** + * Helper to set an external key + * + * @param string sAttCode + * @param array aExtKeyDesc + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + */ + protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, &$oTargetObj, &$oRes) + { + $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); + + $bIsMandatory = !$oExtKey->IsNullAllowed(); + + if (is_null($aExtKeyDesc)) { + if ($bIsMandatory) { + $oRes->LogError("Parameter $sParamName: found null for a mandatory key"); + } else { + // skip silently + return; + } + } + + if (count($aExtKeyDesc) == 0) { + $oRes->LogIssue("Parameter $sParamName: no search condition has been specified", $bIsMandatory); + return; + } + + $sKeyClass = $oExtKey->GetTargetClass(); + $oReconFilter = new DBObjectSearch($sKeyClass); + foreach ($aExtKeyDesc as $sForeignAttCode => $value) { + if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) { + $aCodes = MetaModel::GetFiltersList($sKeyClass); + $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; + $oRes->LogIssue($sMsg, $bIsMandatory); + } + // The foreign attribute is one of our reconciliation key + $oReconFilter->AddCondition($sForeignAttCode, $value, '='); + } + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch ($oExtObjects->Count()) { + case 0: + $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL(true)."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + break; + case 1: + // Do change the external key attribute + $oForeignObj = $oExtObjects->Fetch(); + $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); + + // Report it (no need to report if the object already had this value + if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { + $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); + } + break; + default: + $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL(true)."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + } + } + + /** + * Helper to link objects + * + * @param string sLinkAttCode + * @param string sLinkedClass + * @param array $aLinkList + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + * @return array List of objects that could not be found + */ + protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) + { + $oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode); + $sLinkClass = $oLinkAtt->GetLinkedClass(); + $sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote(); + + $aItemsFound = []; + $aItemsNotFound = []; + + if (is_null($aLinkList)) { + return $aItemsNotFound; + } + + foreach ($aLinkList as $aItemData) { + if (!array_key_exists('class', $aItemData)) { + $oRes->LogWarning("Parameter $sParamName: missing 'class' specification"); + continue; // skip + } + $sTargetClass = $aItemData['class']; + if (!MetaModel::IsValidClass($sTargetClass)) { + $oRes->LogError("Parameter $sParamName: invalid class '$sTargetClass'"); + continue; // skip + } + if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) { + $oRes->LogError("Parameter $sParamName: '$sTargetClass' is not a child class of '$sLinkedClass'"); + continue; // skip + } + $oReconFilter = new DBObjectSearch($sTargetClass); + $aCIStringDesc = []; + foreach ($aItemData['search'] as $sAttCode => $value) { + if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { + $aCodes = MetaModel::GetFiltersList($sTargetClass); + $oRes->LogError("Parameter $sParamName: '$sAttCode' is not a valid filter code for class '$sTargetClass', expecting a value in {".implode(', ', $aCodes)."}"); + continue 2; // skip the entire item + } + $aCIStringDesc[] = "$sAttCode: $value"; + + // The attribute is one of our reconciliation key + $oReconFilter->AddCondition($sAttCode, $value, '='); + } + if (count($aCIStringDesc) == 1) { + // take the last and unique value to describe the object + $sItemDesc = $value; + } else { + // describe the object by the given keys + $sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')'; + } + + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch ($oExtObjects->Count()) { + case 0: + $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL(true)."')"); + $aItemsNotFound[] = $sItemDesc; + break; + case 1: + $aItemsFound[] = [ + 'object' => $oExtObjects->Fetch(), + 'link_values' => @$aItemData['link_values'], + 'desc' => $sItemDesc, + ]; + break; + default: + $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL(true)."')"); + $aItemsNotFound[] = $sItemDesc; + } + } + + if (count($aItemsFound) > 0) { + $aLinks = []; + foreach ($aItemsFound as $aItemData) { + $oLink = MetaModel::NewObject($sLinkClass); + $oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey()); + foreach ($aItemData['link_values'] as $sKey => $value) { + if (!MetaModel::IsValidAttCode($sLinkClass, $sKey)) { + $oRes->LogWarning("Parameter $sParamName: Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); + } else { + $oLink->Set($sKey, $value); + } + } + $aLinks[] = $oLink; + } + $oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks); + $oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet); + } + + return $aItemsNotFound; + } + + /** + * @param \CMDBObject $oTargetObj + * @param string $sResultLabel + * @param \WebServiceResult $oRes + * + * @throws \ArchivedObjectException + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \CoreWarning + * @throws \MySQLException + * @throws \OQLException + * @throws \SecurityException + */ + protected function MyObjectInsert($oTargetObj, $sResultLabel, &$oRes) + { + if ($oRes->IsOk()) { + list($bRes, $aIssues) = $oTargetObj->CheckToWrite(); + if ($bRes) { + $iId = $oTargetObj->DBInsertNoReload(); + $oRes->LogInfo("Created object ".get_class($oTargetObj)."::$iId"); + $oRes->AddResultObject($sResultLabel, $oTargetObj); + } else { + $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); + foreach ($aIssues as $iIssue => $sIssue) { + $oRes->LogError("Issue #$iIssue: $sIssue"); + } + } + } + } + + protected static function SoapStructToExternalKeySearch($oExternalKeySearch) + { + if (is_null($oExternalKeySearch)) { + return null; + } + if ($oExternalKeySearch->IsVoid()) { + return null; + } + + $aRes = []; + foreach ($oExternalKeySearch->conditions as $oSearchCondition) { + $aRes[$oSearchCondition->attcode] = $oSearchCondition->value; + } + return $aRes; + } + + protected static function SoapStructToLinkCreationSpec(SoapLinkCreationSpec $oLinkCreationSpec) + { + $aRes = + [ + 'class' => $oLinkCreationSpec->class, + 'search' => [], + 'link_values' => [], + ]; + + foreach ($oLinkCreationSpec->conditions as $oSearchCondition) { + $aRes['search'][$oSearchCondition->attcode] = $oSearchCondition->value; + } + + foreach ($oLinkCreationSpec->attributes as $oAttributeValue) { + $aRes['link_values'][$oAttributeValue->attcode] = $oAttributeValue->value; + } + + return $aRes; + } + + protected static function SoapStructToAssociativeArray($aArrayOfAssocArray) + { + if (is_null($aArrayOfAssocArray)) { + return []; + } + + $aRes = []; + foreach ($aArrayOfAssocArray as $aAssocArray) { + $aRow = []; + foreach ($aAssocArray as $oKeyValuePair) { + $aRow[$oKeyValuePair->key] = $oKeyValuePair->value; + } + $aRes[] = $aRow; + } + return $aRes; + } } -?> From 0ca81145d9c52334687ff7a5c2fd0a19d53832b0 Mon Sep 17 00:00:00 2001 From: odain Date: Wed, 15 Oct 2025 16:09:14 +0200 Subject: [PATCH 08/15] phpstan: change composer php requirements --- tests/php-code-style/composer.json | 7 +- tests/php-code-style/composer.lock | 560 ++++++++++++++++++----------- 2 files changed, 353 insertions(+), 214 deletions(-) diff --git a/tests/php-code-style/composer.json b/tests/php-code-style/composer.json index b990d13f5b..1d7e9ec0c7 100644 --- a/tests/php-code-style/composer.json +++ b/tests/php-code-style/composer.json @@ -1,6 +1,7 @@ { - "require": { - "friendsofphp/php-cs-fixer": "^3.10", - "phpstan/phpstan": "^2.0" + "require-dev": { + "php": "^7.4 || ^8.0", + "friendsofphp/php-cs-fixer": "^3.88", + "phpstan/phpstan": "^2.1" } } diff --git a/tests/php-code-style/composer.lock b/tests/php-code-style/composer.lock index aeb87a424c..0b38438dd8 100644 --- a/tests/php-code-style/composer.lock +++ b/tests/php-code-style/composer.lock @@ -4,8 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d76c2ddfcd673d9164eff7c412640c4e", - "packages": [ + "content-hash": "afbcf4a8cd7e954326e354a54bd2a1dc", + "packages": [], + "packages-dev": [ { "name": "clue/ndjson-react", "version": "v1.3.0", @@ -402,16 +403,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.87.2", + "version": "v3.88.2", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992" + "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992", - "reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99", + "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99", "shasum": "" }, "require": { @@ -438,12 +439,13 @@ "symfony/polyfill-mbstring": "^1.33", "symfony/polyfill-php80": "^1.33", "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2", "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0" }, "require-dev": { "facile-it/paraunit": "^1.3.1 || ^2.7", - "infection/infection": "^0.29.14", + "infection/infection": "^0.31.0", "justinrainbow/json-schema": "^6.5", "keradus/cli-executor": "^2.2", "mikey179/vfsstream": "^1.6.12", @@ -451,7 +453,6 @@ "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", - "symfony/polyfill-php84": "^1.33", "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2", "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2" }, @@ -494,7 +495,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.87.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2" }, "funding": [ { @@ -502,20 +503,15 @@ "type": "github" } ], - "time": "2025-09-10T09:51:40+00:00" + "time": "2025-09-27T00:24:15+00:00" }, { "name": "phpstan/phpstan", - "version": "2.1.27", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "25da374959afa391992792691093550b3098ef1e" - }, + "version": "2.1.31", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/25da374959afa391992792691093550b3098ef1e", - "reference": "25da374959afa391992792691093550b3098ef1e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", "shasum": "" }, "require": { @@ -560,31 +556,26 @@ "type": "github" } ], - "time": "2025-09-17T09:55:13+00:00" + "time": "2025-10-10T14:14:11+00:00" }, { "name": "psr/container", - "version": "2.0.2", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -611,9 +602,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/event-dispatcher", @@ -667,30 +658,30 @@ }, { "name": "psr/log", - "version": "3.0.2", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -711,9 +702,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2024-09-11T13:17:53+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "react/cache", @@ -1243,29 +1234,29 @@ }, { "name": "sebastian/diff", - "version": "6.0.2", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^9.3", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1297,8 +1288,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -1306,51 +1296,56 @@ "type": "github" } ], - "time": "2024-07-03T04:53:05+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "symfony/console", - "version": "v7.3.3", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2" + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "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|3.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "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": { @@ -1384,7 +1379,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.3" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -1395,33 +1390,29 @@ "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": "2025-08-25T06:35:40+00:00" + "time": "2024-11-06T11:30:55+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.1" }, "type": "library", "extra": { @@ -1430,7 +1421,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -1455,7 +1446,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" }, "funding": [ { @@ -1471,43 +1462,48 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.3", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", - "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" + "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": "<6.4", - "symfony/service-contracts": "<2.5" + "symfony/dependency-injection": "<4.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" + "symfony/event-dispatcher-implementation": "2.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" + "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": { @@ -1535,7 +1531,7 @@ "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/v7.3.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45" }, "funding": [ { @@ -1546,35 +1542,34 @@ "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": "2025-08-13T11:49:31+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=7.2.5", "psr/event-dispatcher": "^1" }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, "type": "library", "extra": { "thanks": { @@ -1582,7 +1577,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -1615,7 +1610,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" }, "funding": [ { @@ -1631,29 +1626,30 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.2", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -1681,7 +1677,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.2" + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { @@ -1692,36 +1688,31 @@ "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": "2025-07-07T08:17:47+00:00" + "time": "2024-10-22T13:05:35+00:00" }, { "name": "symfony/finder", - "version": "v7.3.2", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "63741784cd7b9967975eec610b256eed3ede022b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -1749,7 +1740,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v5.4.45" }, "funding": [ { @@ -1760,34 +1751,32 @@ "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": "2025-07-15T13:41:35+00:00" + "time": "2024-09-28T13:32:08+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.3", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", + "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -1820,7 +1809,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" + "source": "https://github.com/symfony/options-resolver/tree/v5.4.45" }, "funding": [ { @@ -1831,16 +1820,12 @@ "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": "2025-08-05T10:16:07+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2177,6 +2162,86 @@ ], "time": "2024-12-23T08:48:59+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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 backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-php80", "version": "v1.33.0", @@ -2341,22 +2406,103 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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 backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, { "name": "symfony/process", - "version": "v7.3.3", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -2384,7 +2530,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.3" + "source": "https://github.com/symfony/process/tree/v5.4.47" }, "funding": [ { @@ -2395,39 +2541,38 @@ "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": "2025-08-18T09:42:54+00:00" + "time": "2024-11-06T11:36:42+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, + "suggest": { + "symfony/service-implementation": "" + }, "type": "library", "extra": { "thanks": { @@ -2435,16 +2580,13 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2471,7 +2613,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { @@ -2487,25 +2629,25 @@ "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.3.0", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", + "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" }, "type": "library", "autoload": { @@ -2533,7 +2675,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" }, "funding": [ { @@ -2549,39 +2691,38 @@ "type": "tidelift" } ], - "time": "2025-02-24T10:49:57+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/string", - "version": "v7.3.3", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "symfony/translation-contracts": ">=3.0" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "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" }, "type": "library", "autoload": { @@ -2620,7 +2761,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v5.4.47" }, "funding": [ { @@ -2631,25 +2772,22 @@ "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": "2025-08-25T06:35:40+00:00" + "time": "2024-11-10T20:33:58+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": {}, - "platform-dev": {}, + "platform-dev": { + "php": "^7.4 || ^8.0" + }, "plugin-api-version": "2.6.0" } From fc6c535dda21c5fc5b0e6aa23d10d37d62751300 Mon Sep 17 00:00:00 2001 From: odain Date: Fri, 17 Oct 2025 11:30:51 +0200 Subject: [PATCH 09/15] indent with tabs + inception style applied everywhere even php-cs-fixer itself --- tests/php-code-style/.php-cs-fixer.dist.php | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/php-code-style/.php-cs-fixer.dist.php b/tests/php-code-style/.php-cs-fixer.dist.php index 601ff41cdb..7ff4a436e0 100644 --- a/tests/php-code-style/.php-cs-fixer.dist.php +++ b/tests/php-code-style/.php-cs-fixer.dist.php @@ -1,10 +1,10 @@ exclude('oql') + ->exclude('oql') ->in($APPROOT.'/addons') ->in($APPROOT.'/application') ->in($APPROOT.'/core') @@ -16,15 +16,18 @@ ->in($APPROOT.'/sources') ->in($APPROOT.'/synchro') ->in($APPROOT.'/tests') - ->in($APPROOT . '/webservices') - ; + ->in($APPROOT . '/webservices') +; $config = new PhpCsFixer\Config(); return $config->setRiskyAllowed(true) - ->setRules([ - '@PSR12' => true, - 'no_extra_blank_lines' => true, - 'array_syntax' => ['syntax' => 'short'], - ]) - ->setFinder($finder) -; \ No newline at end of file + ->setRules([ + '@PSR12' => true, + 'indentation_type' => true, + 'no_extra_blank_lines' => true, + 'array_syntax' => ['syntax' => 'short'], + ]) + ->setIndent("\t") + ->setLineEnding("\n") + ->setFinder($finder) +; From 34ffb9ea66d12e31baed9ccdd1e936ac9f54bba9 Mon Sep 17 00:00:00 2001 From: odain Date: Fri, 17 Oct 2025 11:32:04 +0200 Subject: [PATCH 10/15] use tabs for code style indentation --- webservices/backoffice.dataloader.php | 132 +-- webservices/createfrommail.php | 182 +-- webservices/cron.php | 758 ++++++------ webservices/export-v2.php | 998 ++++++++-------- webservices/export.php | 480 ++++---- webservices/import.php | 1434 +++++++++++------------ webservices/itop.wsdl.php | 32 +- webservices/itoprest.examples.php | 566 ++++----- webservices/itopsoap.examples.php | 170 +-- webservices/itopsoaptypes.class.inc.php | 214 ++-- webservices/rest.php | 382 +++--- webservices/soapserver.php | 80 +- webservices/status.php | 10 +- webservices/webservices.basic.php | 456 +++---- webservices/webservices.class.inc.php | 1018 ++++++++-------- 15 files changed, 3456 insertions(+), 3456 deletions(-) diff --git a/webservices/backoffice.dataloader.php b/webservices/backoffice.dataloader.php index effadf4344..b4479974c9 100644 --- a/webservices/backoffice.dataloader.php +++ b/webservices/backoffice.dataloader.php @@ -42,24 +42,24 @@ function SetMemoryLimit($oP) { - $sMemoryLimit = trim(ini_get('memory_limit')); - if (empty($sMemoryLimit)) { - // On some PHP installations, memory_limit does not exist as a PHP setting! - // (encountered on a 5.2.0 under Windows) - // In that case, ini_set will not work, let's keep track of this and proceed with the data load - $oP->p("No memory limit has been defined in this instance of PHP"); - } else { - // Check that the limit will allow us to load the data - // - $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); - if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) { - if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === false) { - $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); - } else { - $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); - } - } - } + $sMemoryLimit = trim(ini_get('memory_limit')); + if (empty($sMemoryLimit)) { + // On some PHP installations, memory_limit does not exist as a PHP setting! + // (encountered on a 5.2.0 under Windows) + // In that case, ini_set will not work, let's keep track of this and proceed with the data load + $oP->p("No memory limit has been defined in this instance of PHP"); + } else { + // Check that the limit will allow us to load the data + // + $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); + if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) { + if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === false) { + $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); + } else { + $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); + } + } + } } //////////////////////////////////////////////////////////////////////////////// @@ -80,59 +80,59 @@ function SetMemoryLimit($oP) $oP = new WebPage("iTop - Backoffice data loader"); try { - // Note: the data model must be loaded first - $oDataLoader = new XMLDataLoader(); - - if (empty($sFileName)) { - throw(new Exception("Missing argument 'file'")); - } - if (!file_exists($sFileName)) { - throw(new Exception("File $sFileName does not exist")); - } - - SetMemoryLimit($oP); - - // The XMLDataLoader constructor has initialized the DB, let's start a transaction - CMDBSource::Query('START TRANSACTION'); - - $oP->p("Starting data load."); - CMDBObject::SetCurrentChangeFromParams('Initialization WS'); - $oDataLoader->StartSession(CMDBObject::GetCurrentChange()); - $oDataLoader->LoadFile($sFileName); - - $oP->p("Ending data load session"); - if ($oDataLoader->EndSession(true /* strict */)) { - $iCountCreated = $oDataLoader->GetCountCreated(); - CMDBSource::Query('COMMIT'); - - $oP->p("Data successfully written into the DB: $iCountCreated objects created"); - } else { - CMDBSource::Query('ROLLBACK'); - $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data"); - $aErrors = $oDataLoader->GetErrors(); - if (count($aErrors) > 0) { - $oP->p('Errors ('.count($aErrors).')'); - foreach ($aErrors as $sMsg) { - $oP->p(' * '.$sMsg); - } - } - $aWarnings = $oDataLoader->GetWarnings(); - if (count($aWarnings) > 0) { - $oP->p('Warnings ('.count($aWarnings).')'); - foreach ($aWarnings as $sMsg) { - $oP->p(' * '.$sMsg); - } - } - } + // Note: the data model must be loaded first + $oDataLoader = new XMLDataLoader(); + + if (empty($sFileName)) { + throw(new Exception("Missing argument 'file'")); + } + if (!file_exists($sFileName)) { + throw(new Exception("File $sFileName does not exist")); + } + + SetMemoryLimit($oP); + + // The XMLDataLoader constructor has initialized the DB, let's start a transaction + CMDBSource::Query('START TRANSACTION'); + + $oP->p("Starting data load."); + CMDBObject::SetCurrentChangeFromParams('Initialization WS'); + $oDataLoader->StartSession(CMDBObject::GetCurrentChange()); + $oDataLoader->LoadFile($sFileName); + + $oP->p("Ending data load session"); + if ($oDataLoader->EndSession(true /* strict */)) { + $iCountCreated = $oDataLoader->GetCountCreated(); + CMDBSource::Query('COMMIT'); + + $oP->p("Data successfully written into the DB: $iCountCreated objects created"); + } else { + CMDBSource::Query('ROLLBACK'); + $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data"); + $aErrors = $oDataLoader->GetErrors(); + if (count($aErrors) > 0) { + $oP->p('Errors ('.count($aErrors).')'); + foreach ($aErrors as $sMsg) { + $oP->p(' * '.$sMsg); + } + } + $aWarnings = $oDataLoader->GetWarnings(); + if (count($aWarnings) > 0) { + $oP->p('Warnings ('.count($aWarnings).')'); + foreach ($aWarnings as $sMsg) { + $oP->p(' * '.$sMsg); + } + } + } } catch (Exception $e) { - $oP->p("An error happened while loading the data: ".$e->getMessage()); - $oP->p("Aborting (no data written)..."); - CMDBSource::Query('ROLLBACK'); + $oP->p("An error happened while loading the data: ".$e->getMessage()); + $oP->p("Aborting (no data written)..."); + CMDBSource::Query('ROLLBACK'); } if (function_exists('memory_get_peak_usage')) { - $oP->p("Information: memory peak usage: ".memory_get_peak_usage()); + $oP->p("Information: memory peak usage: ".memory_get_peak_usage()); } $oP->Output(); diff --git a/webservices/createfrommail.php b/webservices/createfrommail.php index 35edac0f50..4ed93177c0 100644 --- a/webservices/createfrommail.php +++ b/webservices/createfrommail.php @@ -41,13 +41,13 @@ function GetSender($aHeaders) { - $aResult = ['name' => '', 'email' => '']; - $aResult['name'] = $aHeaders['From']; - $aMatches = []; - if (preg_match('/\(([0-9a-zA-Z\._]+)@(.+)@(.+)\)/U', array_pop($aHeaders['Received']), $aMatches)) { - $aResult['email'] = $aMatches[1].'@'.$aMatches[2]; - } - return $aResult; + $aResult = ['name' => '', 'email' => '']; + $aResult['name'] = $aHeaders['From']; + $aMatches = []; + if (preg_match('/\(([0-9a-zA-Z\._]+)@(.+)@(.+)\)/U', array_pop($aHeaders['Received']), $aMatches)) { + $aResult['email'] = $aMatches[1].'@'.$aMatches[2]; + } + return $aResult; } /** @@ -59,38 +59,38 @@ function GetSender($aHeaders) */ function CreateTicket($sSenderEmail, $sSubject, $sBody) { - $oTicket = null; - try { - $oContactSearch = new DBObjectSearch('Contact'); // Can be either a Person or a Team, but must be a valid Contact - $oContactSearch->AddCondition('email', $sSenderEmail, '='); - $oSet = new DBObjectSet($oContactSearch); - if ($oSet->Count() == 1) { - $oContact = $oSet->Fetch(); - $oOrganization = MetaModel::GetObject('Organization', $oContact->Get('org_id')); - $oTicket = new UserRequest(); - $oTicket->Set('title', $sSubject); - $oTicket->Set('description', $sBody); - $oTicket->Set('org_id', $oOrganization->GetKey()); - $oTicket->Set('caller_id', $oContact->GetKey()); - $oTicket->Set('impact', DEFAULT_IMPACT); - $oTicket->Set('urgency', DEFAULT_URGENCY); - $oTicket->Set('product', DEFAULT_PRODUCT); - $oTicket->Set('service_id', DEFAULT_SERVICE_ID); // Can be replaced by a search for a valid service for this 'org_id' - $oTicket->Set('servicesubcategory_id', DEFAULT_SUBSERVICE_ID); // Same as above... - $oTicket->Set('workgroup_id', DEFAULT_WORKGROUP_ID); // Same as above... + $oTicket = null; + try { + $oContactSearch = new DBObjectSearch('Contact'); // Can be either a Person or a Team, but must be a valid Contact + $oContactSearch->AddCondition('email', $sSenderEmail, '='); + $oSet = new DBObjectSet($oContactSearch); + if ($oSet->Count() == 1) { + $oContact = $oSet->Fetch(); + $oOrganization = MetaModel::GetObject('Organization', $oContact->Get('org_id')); + $oTicket = new UserRequest(); + $oTicket->Set('title', $sSubject); + $oTicket->Set('description', $sBody); + $oTicket->Set('org_id', $oOrganization->GetKey()); + $oTicket->Set('caller_id', $oContact->GetKey()); + $oTicket->Set('impact', DEFAULT_IMPACT); + $oTicket->Set('urgency', DEFAULT_URGENCY); + $oTicket->Set('product', DEFAULT_PRODUCT); + $oTicket->Set('service_id', DEFAULT_SERVICE_ID); // Can be replaced by a search for a valid service for this 'org_id' + $oTicket->Set('servicesubcategory_id', DEFAULT_SUBSERVICE_ID); // Same as above... + $oTicket->Set('workgroup_id', DEFAULT_WORKGROUP_ID); // Same as above... - // Record the change information about the object - $sUserString = $oContact->GetName().', submitted by email'; - CMDBObject::SetTrackInfo($sUserString); - $oTicket->DBInsert(); - } else { - echo "No contact found in iTop having the email: $sSenderEmail, email message ignored.\n"; - } - } catch (Exception $e) { - echo "Error: exception ".$e->getMessage(); - $oTicket = null; - } - return $oTicket; + // Record the change information about the object + $sUserString = $oContact->GetName().', submitted by email'; + CMDBObject::SetTrackInfo($sUserString); + $oTicket->DBInsert(); + } else { + echo "No contact found in iTop having the email: $sSenderEmail, email message ignored.\n"; + } + } catch (Exception $e) { + echo "Error: exception ".$e->getMessage(); + $oTicket = null; + } + return $oTicket; } /** * Main program @@ -105,61 +105,61 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) // in iTop (identified by her/his email address), otherwise the ticket creation will fail $iNbMessages = $oPop3->numMsg(); for ($index = 1; $index <= $iNbMessages; $index++) { - $params['include_bodies'] = true; - $params['decode_bodies'] = true; - $params['decode_headers'] = true; - $params['crlf'] = "\r\n"; - $aHeaders = $oPop3->getParsedHeaders($index); - $aSender = GetSender($aHeaders); - $oDecoder = new Mail_mimeDecode($oPop3->getRawHeaders($index).$params['crlf'].$oPop3->getBody($index)); - $oStructure = $oDecoder->decode($params); - $sSubject = $aHeaders['Subject']; - // Search for the text/plain body part - $iPartIndex = 0; - $bFound = false; - $sTextBody = ''; - //echo "
\n";
-    //print_r($oStructure);
-    //echo "
\n"; - if (!isset($oStructure->parts) || count($oStructure->parts) == 0) { - $sTextBody = $oStructure->body; - } else { - // Find the first "part" of the body which is in text/plain - while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { - //echo "

Reading part $iPartIndex

\n"; - if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && - ($oStructure->parts[$iPartIndex]->ctype_secondary == 'plain')) { - $sTextBody = $oStructure->parts[$iPartIndex]->body; - $bFound = true; - //echo "

Plain text found ! ($sTextBody)

\n"; - } - $iPartIndex++; - } - // Try again but this time look for an HTML part - if (!$bFound) { - while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { - //echo "

Reading part $iPartIndex

\n"; - if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && - ($oStructure->parts[$iPartIndex]->ctype_secondary == 'html')) { - $sTextBody = $oStructure->parts[$iPartIndex]->body; - $bFound = true; - //echo "

HTML text found ! (".htmlentities($sTextBody, ENT_QUOTES, 'UTF-8').")

\n"; - } - $iPartIndex++; - } - } - } + $params['include_bodies'] = true; + $params['decode_bodies'] = true; + $params['decode_headers'] = true; + $params['crlf'] = "\r\n"; + $aHeaders = $oPop3->getParsedHeaders($index); + $aSender = GetSender($aHeaders); + $oDecoder = new Mail_mimeDecode($oPop3->getRawHeaders($index).$params['crlf'].$oPop3->getBody($index)); + $oStructure = $oDecoder->decode($params); + $sSubject = $aHeaders['Subject']; + // Search for the text/plain body part + $iPartIndex = 0; + $bFound = false; + $sTextBody = ''; + //echo "
\n";
+	//print_r($oStructure);
+	//echo "
\n"; + if (!isset($oStructure->parts) || count($oStructure->parts) == 0) { + $sTextBody = $oStructure->body; + } else { + // Find the first "part" of the body which is in text/plain + while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { + //echo "

Reading part $iPartIndex

\n"; + if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && + ($oStructure->parts[$iPartIndex]->ctype_secondary == 'plain')) { + $sTextBody = $oStructure->parts[$iPartIndex]->body; + $bFound = true; + //echo "

Plain text found ! ($sTextBody)

\n"; + } + $iPartIndex++; + } + // Try again but this time look for an HTML part + if (!$bFound) { + while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { + //echo "

Reading part $iPartIndex

\n"; + if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && + ($oStructure->parts[$iPartIndex]->ctype_secondary == 'html')) { + $sTextBody = $oStructure->parts[$iPartIndex]->body; + $bFound = true; + //echo "

HTML text found ! (".htmlentities($sTextBody, ENT_QUOTES, 'UTF-8').")

\n"; + } + $iPartIndex++; + } + } + } - // Bug: depending on the email, the email address could be found in : - // email => 'john.foo@combodo.com' - // name => 'john foo + // Bug: depending on the email, the email address could be found in : + // email => 'john.foo@combodo.com' + // name => 'john foo - $oTicket = CreateTicket($aSender['email'], $sSubject, $sTextBody); - if ($oTicket != null) { - // Ticket created, delete the email - $oPop3->deleteMsg($index); - echo "Ticket: ".$oTicket->GetName()." created.\n"; - } + $oTicket = CreateTicket($aSender['email'], $sSubject, $sTextBody); + if ($oTicket != null) { + // Ticket created, delete the email + $oPop3->deleteMsg($index); + echo "Ticket: ".$oTicket->GetName()." created.\n"; + } } $oPop3->disconnect(); ?> diff --git a/webservices/cron.php b/webservices/cron.php index 56cd8d066d..66549e7d15 100644 --- a/webservices/cron.php +++ b/webservices/cron.php @@ -29,8 +29,8 @@ const EXIT_CODE_FATAL = -2; // early exit if (file_exists(READONLY_MODE_FILE)) { - echo "iTop is read-only. Exiting...\n"; - exit(EXIT_CODE_ERROR); + echo "iTop is read-only. Exiting...\n"; + exit(EXIT_CODE_ERROR); } require_once(APPROOT.'/application/application.inc.php'); @@ -38,8 +38,8 @@ $sConfigFile = APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE; if (!file_exists($sConfigFile)) { - echo "iTop is not yet installed. Exiting...\n"; - exit(EXIT_CODE_ERROR); + echo "iTop is not yet installed. Exiting...\n"; + exit(EXIT_CODE_ERROR); } require_once(APPROOT.'/application/startup.inc.php'); @@ -48,27 +48,27 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter') { - $sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter); - if (is_null($sValue)) { - $oP->p("ERROR: Missing argument '$sParam'\n"); - UsageAndExit($oP); - } + $sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter); + if (is_null($sValue)) { + $oP->p("ERROR: Missing argument '$sParam'\n"); + UsageAndExit($oP); + } - return trim($sValue); + return trim($sValue); } function UsageAndExit($oP) { - $bModeCLI = ($oP instanceof CLIPage); - - if ($bModeCLI) { - $oP->p("USAGE:\n"); - $oP->p("php cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1] [--debug=1] [--status_only=1]\n"); - } else { - $oP->p("Optional parameters: verbose, param_file, status_only\n"); - } - $oP->output(); - exit(EXIT_CODE_FATAL); + $bModeCLI = ($oP instanceof CLIPage); + + if ($bModeCLI) { + $oP->p("USAGE:\n"); + $oP->p("php cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1] [--debug=1] [--status_only=1]\n"); + } else { + $oP->p("Optional parameters: verbose, param_file, status_only\n"); + } + $oP->output(); + exit(EXIT_CODE_FATAL); } /** @@ -87,84 +87,84 @@ function UsageAndExit($oP) */ function RunTask(BackgroundTask $oTask, $iTimeLimit) { - $TaskClass = $oTask->Get('class_name'); - $oProcess = new $TaskClass(); - $oRefClass = new ReflectionClass(get_class($oProcess)); - $oDateStarted = new DateTime(); - $oDatePlanned = new DateTime($oTask->Get('next_run_date')); - $fStart = microtime(true); - $oCtx = new ContextTag('CRON:Task:'.$TaskClass); - - $sMessage = ''; - $oExceptionToThrow = null; - try { - // Record (when starting) that this task was started, just in case it crashes during the execution - $oTask->Set('latest_run_date', $oDateStarted->format('Y-m-d H:i:s')); - // Record the current user running the cron - $oTask->Set('system_user', utils::GetCurrentUserName()); - $oTask->Set('running', 1); - $oTask->DBUpdate(); - // Time in seconds allowed to the task - $iCurrTimeLimit = $iTimeLimit; - // Compute allowed time - if ($oRefClass->implementsInterface('iScheduledProcess') === false) { - // Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity()) - $iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time'); - $iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime; - // If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0 - if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) { - $iCurrTimeLimit = $iTaskLimit; - } - } - $sMessage = $oProcess->Process($iCurrTimeLimit); - $oTask->Set('running', 0); - } catch (MySQLHasGoneAwayException $e) { - throw $e; - } catch (ProcessFatalException $e) { - $oExceptionToThrow = $e; - } catch (Exception $e) { // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running - if ($oTask->IsDebug()) { - $sMessage = 'Processing failed with message: '. $e->getMessage() . '. ' . $e->getTraceAsString(); - } else { - $sMessage = 'Processing failed with message: '. $e->getMessage(); - } - } - $fDuration = microtime(true) - $fStart; - if ($oTask->Get('total_exec_count') == 0) { - // First execution - $oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s')); - } - $oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics - - // Update the timestamp since we want to be able to re-order the tasks based on the time they finished - $oDateEnded = new DateTime(); - $oTask->Set('latest_run_date', $oDateEnded->format('Y-m-d H:i:s')); - - if ($oRefClass->implementsInterface('iScheduledProcess')) { - // Schedules process do repeat at specific moments - $oPlannedStart = $oProcess->GetNextOccurrence(); - } else { - // Background processes do repeat periodically - $oPlannedStart = clone $oDatePlanned; - // Let's schedule from the previous planned date of execution to avoid shift - $oPlannedStart->modify($oProcess->GetPeriodicity().' seconds'); - $oEnd = new DateTime(); - while ($oPlannedStart->format('U') < $oEnd->format('U')) { - // Next planned start is already in the past, increase it again by a period - $oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds'); - } - } - - $oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s')); - $oTask->DBUpdate(); - - if ($oExceptionToThrow) { - throw $oExceptionToThrow; - } - - unset($oCtx); - - return $sMessage; + $TaskClass = $oTask->Get('class_name'); + $oProcess = new $TaskClass(); + $oRefClass = new ReflectionClass(get_class($oProcess)); + $oDateStarted = new DateTime(); + $oDatePlanned = new DateTime($oTask->Get('next_run_date')); + $fStart = microtime(true); + $oCtx = new ContextTag('CRON:Task:'.$TaskClass); + + $sMessage = ''; + $oExceptionToThrow = null; + try { + // Record (when starting) that this task was started, just in case it crashes during the execution + $oTask->Set('latest_run_date', $oDateStarted->format('Y-m-d H:i:s')); + // Record the current user running the cron + $oTask->Set('system_user', utils::GetCurrentUserName()); + $oTask->Set('running', 1); + $oTask->DBUpdate(); + // Time in seconds allowed to the task + $iCurrTimeLimit = $iTimeLimit; + // Compute allowed time + if ($oRefClass->implementsInterface('iScheduledProcess') === false) { + // Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity()) + $iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time'); + $iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime; + // If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0 + if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) { + $iCurrTimeLimit = $iTaskLimit; + } + } + $sMessage = $oProcess->Process($iCurrTimeLimit); + $oTask->Set('running', 0); + } catch (MySQLHasGoneAwayException $e) { + throw $e; + } catch (ProcessFatalException $e) { + $oExceptionToThrow = $e; + } catch (Exception $e) { // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running + if ($oTask->IsDebug()) { + $sMessage = 'Processing failed with message: '. $e->getMessage() . '. ' . $e->getTraceAsString(); + } else { + $sMessage = 'Processing failed with message: '. $e->getMessage(); + } + } + $fDuration = microtime(true) - $fStart; + if ($oTask->Get('total_exec_count') == 0) { + // First execution + $oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s')); + } + $oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics + + // Update the timestamp since we want to be able to re-order the tasks based on the time they finished + $oDateEnded = new DateTime(); + $oTask->Set('latest_run_date', $oDateEnded->format('Y-m-d H:i:s')); + + if ($oRefClass->implementsInterface('iScheduledProcess')) { + // Schedules process do repeat at specific moments + $oPlannedStart = $oProcess->GetNextOccurrence(); + } else { + // Background processes do repeat periodically + $oPlannedStart = clone $oDatePlanned; + // Let's schedule from the previous planned date of execution to avoid shift + $oPlannedStart->modify($oProcess->GetPeriodicity().' seconds'); + $oEnd = new DateTime(); + while ($oPlannedStart->format('U') < $oEnd->format('U')) { + // Next planned start is already in the past, increase it again by a period + $oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds'); + } + } + + $oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s')); + $oTask->DBUpdate(); + + if ($oExceptionToThrow) { + throw $oExceptionToThrow; + } + + unset($oCtx); + + return $sMessage; } /** @@ -186,114 +186,114 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) */ function CronExec($oP, $bVerbose, $bDebug = false) { - $iStarted = time(); - $iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time'); - $iTimeLimit = $iStarted + $iMaxDuration; - $iCronSleep = MetaModel::GetConfig()->Get('cron_sleep'); - - if ($bVerbose) { - $oP->p("Planned duration = $iMaxDuration seconds"); - $oP->p("Loop pause = $iCronSleep seconds"); - } - - ReSyncProcesses($oP, $bVerbose, $bDebug); - - while (time() < $iTimeLimit) { - CheckMaintenanceMode($oP); - - $oNow = new DateTime(); - $sNow = $oNow->format('Y-m-d H:i:s'); - $oSearch = new DBObjectSearch('BackgroundTask'); - $oSearch->AddCondition('next_run_date', $sNow, '<='); - $oSearch->AddCondition('status', 'active'); - $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); - $bWorkDone = false; - - if ($oTasks->CountExceeds(0)) { - $bWorkDone = true; - $aTasks = []; - if ($bVerbose) { - $sCount = $oTasks->Count(); - $oP->p("$sCount Tasks planned to run now ($sNow):"); - $oP->p('+---------------------------+---------+---------------------+---------------------+'); - $oP->p('| Task Class | Status | Last Run | Next Run |'); - $oP->p('+---------------------------+---------+---------------------+---------------------+'); - } - while ($oTask = $oTasks->Fetch()) { - $aTasks[$oTask->Get('class_name')] = $oTask; - if ($bVerbose) { - $sTaskName = $oTask->Get('class_name'); - $sStatus = $oTask->Get('status'); - $sLastRunDate = $oTask->Get('latest_run_date'); - $sNextRunDate = $oTask->Get('next_run_date'); - $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate)); - } - } - if ($bVerbose) { - $oP->p('+---------------------------+---------+---------------------+---------------------+'); - } - $aRunTasks = []; - foreach ($aTasks as $oTask) { - $sTaskClass = $oTask->Get('class_name'); - $aRunTasks[] = $sTaskClass; - - // N°3219 for each process will use a specific CMDBChange object with a specific track info - // Any BackgroundProcess can overrides this as needed - CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)"); - - // Run the task and record its next run time - if ($bVerbose) { - $oNow = new DateTime(); - $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=49s", ' '.$sTaskClass.' ')); - } - try { - $sMessage = RunTask($aTasks[$sTaskClass], $iTimeLimit); - } catch (MySQLHasGoneAwayException $e) { - $oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")"); - exit(EXIT_CODE_FATAL); - } catch (ProcessFatalException $e) { - $oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")"); - IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')'); - } - if ($bVerbose) { - if (!empty($sMessage)) { - $oP->p("$sTaskClass: $sMessage"); - } - $oEnd = new DateTime(); - $sNextRunDate = $oTask->Get('next_run_date'); - $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=42s", ' '.$sTaskClass.' ')." Next: $sNextRunDate"); - } - if (time() > $iTimeLimit) { - break 2; - } - CheckMaintenanceMode($oP); - } - - // Tasks to run later - if ($bVerbose) { - $oP->p('--'); - $oSearch = new DBObjectSearch('BackgroundTask'); - $oSearch->AddCondition('next_run_date', $sNow, '>'); - $oSearch->AddCondition('status', 'active'); - $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); - while ($oTask = $oTasks->Fetch()) { - if (!in_array($oTask->Get('class_name'), $aRunTasks)) { - $oP->p(sprintf("-- Skipping task: %-'-40s", $oTask->Get('class_name').' ')." until: ".$oTask->Get('next_run_date')); - } - } - } - } - - if ($bVerbose && $bWorkDone) { - $oP->p("Sleeping...\n"); - } - sleep($iCronSleep); - } - if ($bVerbose) { - $oP->p(''); - DisplayStatus($oP, ['next_run_date' => true]); - $oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)"); - } + $iStarted = time(); + $iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time'); + $iTimeLimit = $iStarted + $iMaxDuration; + $iCronSleep = MetaModel::GetConfig()->Get('cron_sleep'); + + if ($bVerbose) { + $oP->p("Planned duration = $iMaxDuration seconds"); + $oP->p("Loop pause = $iCronSleep seconds"); + } + + ReSyncProcesses($oP, $bVerbose, $bDebug); + + while (time() < $iTimeLimit) { + CheckMaintenanceMode($oP); + + $oNow = new DateTime(); + $sNow = $oNow->format('Y-m-d H:i:s'); + $oSearch = new DBObjectSearch('BackgroundTask'); + $oSearch->AddCondition('next_run_date', $sNow, '<='); + $oSearch->AddCondition('status', 'active'); + $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); + $bWorkDone = false; + + if ($oTasks->CountExceeds(0)) { + $bWorkDone = true; + $aTasks = []; + if ($bVerbose) { + $sCount = $oTasks->Count(); + $oP->p("$sCount Tasks planned to run now ($sNow):"); + $oP->p('+---------------------------+---------+---------------------+---------------------+'); + $oP->p('| Task Class | Status | Last Run | Next Run |'); + $oP->p('+---------------------------+---------+---------------------+---------------------+'); + } + while ($oTask = $oTasks->Fetch()) { + $aTasks[$oTask->Get('class_name')] = $oTask; + if ($bVerbose) { + $sTaskName = $oTask->Get('class_name'); + $sStatus = $oTask->Get('status'); + $sLastRunDate = $oTask->Get('latest_run_date'); + $sNextRunDate = $oTask->Get('next_run_date'); + $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate)); + } + } + if ($bVerbose) { + $oP->p('+---------------------------+---------+---------------------+---------------------+'); + } + $aRunTasks = []; + foreach ($aTasks as $oTask) { + $sTaskClass = $oTask->Get('class_name'); + $aRunTasks[] = $sTaskClass; + + // N°3219 for each process will use a specific CMDBChange object with a specific track info + // Any BackgroundProcess can overrides this as needed + CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)"); + + // Run the task and record its next run time + if ($bVerbose) { + $oNow = new DateTime(); + $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=49s", ' '.$sTaskClass.' ')); + } + try { + $sMessage = RunTask($aTasks[$sTaskClass], $iTimeLimit); + } catch (MySQLHasGoneAwayException $e) { + $oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")"); + exit(EXIT_CODE_FATAL); + } catch (ProcessFatalException $e) { + $oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")"); + IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')'); + } + if ($bVerbose) { + if (!empty($sMessage)) { + $oP->p("$sTaskClass: $sMessage"); + } + $oEnd = new DateTime(); + $sNextRunDate = $oTask->Get('next_run_date'); + $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=42s", ' '.$sTaskClass.' ')." Next: $sNextRunDate"); + } + if (time() > $iTimeLimit) { + break 2; + } + CheckMaintenanceMode($oP); + } + + // Tasks to run later + if ($bVerbose) { + $oP->p('--'); + $oSearch = new DBObjectSearch('BackgroundTask'); + $oSearch->AddCondition('next_run_date', $sNow, '>'); + $oSearch->AddCondition('status', 'active'); + $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); + while ($oTask = $oTasks->Fetch()) { + if (!in_array($oTask->Get('class_name'), $aRunTasks)) { + $oP->p(sprintf("-- Skipping task: %-'-40s", $oTask->Get('class_name').' ')." until: ".$oTask->Get('next_run_date')); + } + } + } + } + + if ($bVerbose && $bWorkDone) { + $oP->p("Sleeping...\n"); + } + sleep($iCronSleep); + } + if ($bVerbose) { + $oP->p(''); + DisplayStatus($oP, ['next_run_date' => true]); + $oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)"); + } } /** @@ -301,11 +301,11 @@ function CronExec($oP, $bVerbose, $bDebug = false) */ function CheckMaintenanceMode(Page $oP) { - // Verify files instead of reloading the full config each time - if (file_exists(MAINTENANCE_MODE_FILE) || file_exists(READONLY_MODE_FILE)) { - $oP->p("Maintenance detected, exiting"); - exit(EXIT_CODE_ERROR); - } + // Verify files instead of reloading the full config each time + if (file_exists(MAINTENANCE_MODE_FILE) || file_exists(READONLY_MODE_FILE)) { + $oP->p("Maintenance detected, exiting"); + exit(EXIT_CODE_ERROR); + } } /** @@ -320,29 +320,29 @@ function CheckMaintenanceMode(Page $oP) */ function DisplayStatus($oP, $aTaskOrderBy = []) { - $oSearch = new DBObjectSearch('BackgroundTask'); - $oTasks = new DBObjectSet($oSearch, $aTaskOrderBy); - $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); - $oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |'); - $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); - while ($oTask = $oTasks->Fetch()) { - $sTaskName = $oTask->Get('class_name'); - $sStatus = $oTask->Get('status'); - $sLastRunDate = $oTask->Get('latest_run_date'); - $sNextRunDate = $oTask->Get('next_run_date'); - $iNbRun = (int)$oTask->Get('total_exec_count'); - $sAverageRunTime = $oTask->Get('average_run_duration'); - $oP->p(sprintf( - '| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', - $sTaskName, - $sStatus, - $sLastRunDate, - $sNextRunDate, - $iNbRun, - $sAverageRunTime - )); - } - $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); + $oSearch = new DBObjectSearch('BackgroundTask'); + $oTasks = new DBObjectSet($oSearch, $aTaskOrderBy); + $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); + $oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |'); + $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); + while ($oTask = $oTasks->Fetch()) { + $sTaskName = $oTask->Get('class_name'); + $sStatus = $oTask->Get('status'); + $sLastRunDate = $oTask->Get('latest_run_date'); + $sNextRunDate = $oTask->Get('next_run_date'); + $iNbRun = (int)$oTask->Get('total_exec_count'); + $sAverageRunTime = $oTask->Get('average_run_duration'); + $oP->p(sprintf( + '| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', + $sTaskName, + $sStatus, + $sLastRunDate, + $sNextRunDate, + $iNbRun, + $sAverageRunTime + )); + } + $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); } /** @@ -361,83 +361,83 @@ function DisplayStatus($oP, $aTaskOrderBy = []) */ function ReSyncProcesses($oP, $bVerbose, $bDebug) { - // Enumerate classes implementing BackgroundProcess - // - $oSearch = new DBObjectSearch('BackgroundTask'); - $oTasks = new DBObjectSet($oSearch); - $aTasks = []; - while ($oTask = $oTasks->Fetch()) { - $aTasks[$oTask->Get('class_name')] = $oTask; - } - $oNow = new DateTime(); - - $aProcesses = []; - foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProcess::class) as $sTaskClass) { - $oProcess = new $sTaskClass(); - $aProcesses[$sTaskClass] = $oProcess; - - // Create missing entry if needed - if (!array_key_exists($sTaskClass, $aTasks)) { - // New entry, let's create a new BackgroundTask record, and plan the first execution - $oTask = new BackgroundTask(); - $oTask->SetDebug($bDebug); - $oTask->Set('class_name', $sTaskClass); - $oTask->Set('total_exec_count', 0); - $oTask->Set('min_run_duration', 99999.999); - $oTask->Set('max_run_duration', 0); - $oTask->Set('average_run_duration', 0); - $oRefClass = new ReflectionClass($sTaskClass); - if ($oRefClass->implementsInterface('iScheduledProcess')) { - $oNextOcc = $oProcess->GetNextOccurrence(); - $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); - } else { - // Background processes do start asap, i.e. "now" - $oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); - } - if ($bVerbose) { - $oP->p('Creating record for: '.$sTaskClass); - $oP->p('First execution planned at: '.$oTask->Get('next_run_date')); - } - $oTask->DBInsert(); - } else { - /** @var \BackgroundTask $oTask */ - $oTask = $aTasks[$sTaskClass]; - if ($oTask->Get('next_run_date') == '3000-01-01 00:00:00') { - // check for rescheduled tasks - $oRefClass = new ReflectionClass($sTaskClass); - if ($oRefClass->implementsInterface('iScheduledProcess')) { - $oNextOcc = $oProcess->GetNextOccurrence(); - $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); - $oTask->DBUpdate(); - } - } - // Reactivate task if necessary - if ($oTask->Get('status') == 'removed') { - $oTask->Set('status', 'active'); - $oTask->DBUpdate(); - } - // task having a real class to execute - unset($aTasks[$sTaskClass]); - } - } - - // Remove all the tasks not having a valid class - foreach ($aTasks as $oTask) { - $sTaskClass = $oTask->Get('class_name'); - if (!class_exists($sTaskClass)) { - $oTask->Set('status', 'removed'); - $oTask->DBUpdate(); - } - } - - if ($bVerbose) { - $aDisplayProcesses = []; - foreach ($aProcesses as $oExecInstance) { - $aDisplayProcesses[] = get_class($oExecInstance); - } - $sDisplayProcesses = implode(', ', $aDisplayProcesses); - $oP->p("Background processes: ".$sDisplayProcesses); - } + // Enumerate classes implementing BackgroundProcess + // + $oSearch = new DBObjectSearch('BackgroundTask'); + $oTasks = new DBObjectSet($oSearch); + $aTasks = []; + while ($oTask = $oTasks->Fetch()) { + $aTasks[$oTask->Get('class_name')] = $oTask; + } + $oNow = new DateTime(); + + $aProcesses = []; + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProcess::class) as $sTaskClass) { + $oProcess = new $sTaskClass(); + $aProcesses[$sTaskClass] = $oProcess; + + // Create missing entry if needed + if (!array_key_exists($sTaskClass, $aTasks)) { + // New entry, let's create a new BackgroundTask record, and plan the first execution + $oTask = new BackgroundTask(); + $oTask->SetDebug($bDebug); + $oTask->Set('class_name', $sTaskClass); + $oTask->Set('total_exec_count', 0); + $oTask->Set('min_run_duration', 99999.999); + $oTask->Set('max_run_duration', 0); + $oTask->Set('average_run_duration', 0); + $oRefClass = new ReflectionClass($sTaskClass); + if ($oRefClass->implementsInterface('iScheduledProcess')) { + $oNextOcc = $oProcess->GetNextOccurrence(); + $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); + } else { + // Background processes do start asap, i.e. "now" + $oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); + } + if ($bVerbose) { + $oP->p('Creating record for: '.$sTaskClass); + $oP->p('First execution planned at: '.$oTask->Get('next_run_date')); + } + $oTask->DBInsert(); + } else { + /** @var \BackgroundTask $oTask */ + $oTask = $aTasks[$sTaskClass]; + if ($oTask->Get('next_run_date') == '3000-01-01 00:00:00') { + // check for rescheduled tasks + $oRefClass = new ReflectionClass($sTaskClass); + if ($oRefClass->implementsInterface('iScheduledProcess')) { + $oNextOcc = $oProcess->GetNextOccurrence(); + $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); + $oTask->DBUpdate(); + } + } + // Reactivate task if necessary + if ($oTask->Get('status') == 'removed') { + $oTask->Set('status', 'active'); + $oTask->DBUpdate(); + } + // task having a real class to execute + unset($aTasks[$sTaskClass]); + } + } + + // Remove all the tasks not having a valid class + foreach ($aTasks as $oTask) { + $sTaskClass = $oTask->Get('class_name'); + if (!class_exists($sTaskClass)) { + $oTask->Set('status', 'removed'); + $oTask->DBUpdate(); + } + } + + if ($bVerbose) { + $aDisplayProcesses = []; + foreach ($aProcesses as $oExecInstance) { + $aDisplayProcesses[] = get_class($oExecInstance); + } + $sDisplayProcesses = implode(', ', $aDisplayProcesses); + $oP->p("Background processes: ".$sDisplayProcesses); + } } //////////////////////////////////////////////////////////////////////////////// @@ -449,85 +449,85 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) $bIsModeCLI = utils::IsModeCLI(); if ($bIsModeCLI) { - $oP = new CLIPage("iTop - cron"); + $oP = new CLIPage("iTop - cron"); - SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); + SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); } else { - $oP = new WebPage("iTop - cron"); + $oP = new WebPage("iTop - cron"); } try { - utils::UseParamFile(); - - $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */); - $bDebug = utils::ReadParam('debug', false, true /* Allow CLI */); - - if ($bIsModeCLI) { - // Next steps: - // specific arguments: 'csv file' - // - $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); - $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { - UserRights::Login($sAuthUser); // Login & set the user's language - } else { - $oP->p("Access wrong credentials ('$sAuthUser')"); - $oP->output(); - exit(EXIT_CODE_ERROR); - } - } else { - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - } - - if (!UserRights::IsAdministrator()) { - $oP->p("Access restricted to administrators"); - $oP->Output(); - exit(EXIT_CODE_ERROR); - } - - if (utils::ReadParam('status_only', false, true /* Allow CLI */)) { - // Display status and exit - DisplayStatus($oP); - exit(0); - } - - require_once(APPROOT.'core/mutex.class.inc.php'); - $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')'); + utils::UseParamFile(); + + $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */); + $bDebug = utils::ReadParam('debug', false, true /* Allow CLI */); + + if ($bIsModeCLI) { + // Next steps: + // specific arguments: 'csv file' + // + $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); + $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + $oP->p("Access wrong credentials ('$sAuthUser')"); + $oP->output(); + exit(EXIT_CODE_ERROR); + } + } else { + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed + } + + if (!UserRights::IsAdministrator()) { + $oP->p("Access restricted to administrators"); + $oP->Output(); + exit(EXIT_CODE_ERROR); + } + + if (utils::ReadParam('status_only', false, true /* Allow CLI */)) { + // Display status and exit + DisplayStatus($oP); + exit(0); + } + + require_once(APPROOT.'core/mutex.class.inc.php'); + $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')'); } catch (Exception $e) { - $oP->p("Error: ".$e->GetMessage()); - $oP->output(); - exit(EXIT_CODE_FATAL); + $oP->p("Error: ".$e->GetMessage()); + $oP->output(); + exit(EXIT_CODE_FATAL); } try { - $oMutex = new iTopMutex('cron'); - if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { - $oP->p("A maintenance is ongoing"); - } else { - if ($oMutex->TryLock()) { - CronExec($oP, $bVerbose, $bDebug); - } else { - // Exit silently - $oP->p("Already running..."); - } - } + $oMutex = new iTopMutex('cron'); + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { + $oP->p("A maintenance is ongoing"); + } else { + if ($oMutex->TryLock()) { + CronExec($oP, $bVerbose, $bDebug); + } else { + // Exit silently + $oP->p("Already running..."); + } + } } catch (Exception $e) { - $oP->p("ERROR: '".$e->getMessage()."'"); - if ($bDebug) { - // Might contain verb parameters such a password... - $oP->p($e->getTraceAsString()); - } + $oP->p("ERROR: '".$e->getMessage()."'"); + if ($bDebug) { + // Might contain verb parameters such a password... + $oP->p($e->getTraceAsString()); + } } finally { - try { - $oMutex->Unlock(); - } catch (Exception $e) { - $oP->p("ERROR: '".$e->getMessage()."'"); - if ($bDebug) { - // Might contain verb parameters such a password... - $oP->p($e->getTraceAsString()); - } - } + try { + $oMutex->Unlock(); + } catch (Exception $e) { + $oP->p("ERROR: '".$e->getMessage()."'"); + if ($bDebug) { + // Might contain verb parameters such a password... + $oP->p($e->getTraceAsString()); + } + } } $oP->p("Exiting: ".time().' ('.date('Y-m-d H:i:s').')'); diff --git a/webservices/export-v2.php b/webservices/export-v2.php index e505ca6c34..ad268232ce 100644 --- a/webservices/export-v2.php +++ b/webservices/export-v2.php @@ -36,121 +36,121 @@ function ReportErrorAndExit($sErrorMessage) { - if (utils::IsModeCLI()) { - $oP = new CLIPage("iTop - Export"); - $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); - $oP->output(); - exit(EXIT_CODE_ERROR); - } else { - $oP = new WebPage("iTop - Export"); - $oP->add_http_headers(); - $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); - $oP->output(); - exit(EXIT_CODE_ERROR); - } + if (utils::IsModeCLI()) { + $oP = new CLIPage("iTop - Export"); + $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); + $oP->output(); + exit(EXIT_CODE_ERROR); + } else { + $oP = new WebPage("iTop - Export"); + $oP->add_http_headers(); + $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); + $oP->output(); + exit(EXIT_CODE_ERROR); + } } function ReportErrorAndUsage($sErrorMessage) { - if (utils::IsModeCLI()) { - $oP = new CLIPage("iTop - Export"); - $oP->p('ERROR: '.$sErrorMessage); - Usage($oP); - $oP->output(); - exit(EXIT_CODE_ERROR); - } else { - $oP = new WebPage("iTop - Export"); - $oP->add_http_headers(); - $oP->p('ERROR: '.$sErrorMessage); - Usage($oP); - $oP->output(); - exit(EXIT_CODE_ERROR); - } + if (utils::IsModeCLI()) { + $oP = new CLIPage("iTop - Export"); + $oP->p('ERROR: '.$sErrorMessage); + Usage($oP); + $oP->output(); + exit(EXIT_CODE_ERROR); + } else { + $oP = new WebPage("iTop - Export"); + $oP->add_http_headers(); + $oP->p('ERROR: '.$sErrorMessage); + Usage($oP); + $oP->output(); + exit(EXIT_CODE_ERROR); + } } function Usage(Page $oP) { - if (Utils::IsModeCLI()) { - $oP->p('Usage: php '.basename(__FILE__).' --auth_user= --auth_pwd= --expression= --query= [--arg_xxx=] [--no_localize=0|1] [--format=] [--format-options...]'); - $oP->p("Parameters:"); - $oP->p(" * auth_user: the iTop user account for authentication"); - $oP->p(" * auth_pwd: the password of the iTop user account"); - } else { - $oP->p("Parameters:"); - } - $oP->p(" * expression: an OQL expression (e.g. SELECT Contact WHERE name LIKE 'm%')"); - $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); - if (Utils::IsModeCLI()) { - $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); - } else { - $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); - } - $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); - $aSupportedFormats = BulkExport::FindSupportedFormats(); - $oP->p(" * format: (optional, default is html) the desired output format. Can be one of '".implode("', '", array_keys($aSupportedFormats))."'"); - foreach ($aSupportedFormats as $sFormatCode => $sLabel) { - $oExporter = BulkExport::FindExporter($sFormatCode); - if ($oExporter !== null) { - if (!Utils::IsModeCLI()) { - $oP->add('
'); - } - $oExporter->DisplayUsage($oP); - if (!Utils::IsModeCLI()) { - $oP->add(''); - } - } - } - //if (!Utils::IsModeCLI()) - //{ - // $oP->add(''); - //} + if (Utils::IsModeCLI()) { + $oP->p('Usage: php '.basename(__FILE__).' --auth_user= --auth_pwd= --expression= --query= [--arg_xxx=] [--no_localize=0|1] [--format=] [--format-options...]'); + $oP->p("Parameters:"); + $oP->p(" * auth_user: the iTop user account for authentication"); + $oP->p(" * auth_pwd: the password of the iTop user account"); + } else { + $oP->p("Parameters:"); + } + $oP->p(" * expression: an OQL expression (e.g. SELECT Contact WHERE name LIKE 'm%')"); + $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); + if (Utils::IsModeCLI()) { + $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); + } else { + $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); + } + $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); + $aSupportedFormats = BulkExport::FindSupportedFormats(); + $oP->p(" * format: (optional, default is html) the desired output format. Can be one of '".implode("', '", array_keys($aSupportedFormats))."'"); + foreach ($aSupportedFormats as $sFormatCode => $sLabel) { + $oExporter = BulkExport::FindExporter($sFormatCode); + if ($oExporter !== null) { + if (!Utils::IsModeCLI()) { + $oP->add('
'); + } + $oExporter->DisplayUsage($oP); + if (!Utils::IsModeCLI()) { + $oP->add(''); + } + } + } + //if (!Utils::IsModeCLI()) + //{ + // $oP->add(''); + //} } function DisplayExpressionForm(WebPage $oP, $sAction, $sExpression = '', $sExceptionMessage = '', $oForm = null) { - $oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('Core:BulkExport:ScopeDefinition')); - if ($oForm == null) { - $oForm = FormUIBlockFactory::MakeStandard('export-form'); - $oForm->SetAction($sAction); - $oP->AddSubBlock($oForm); - } - $oForm->AddSubBlock($oPanel); - - $oPanel->AddSubBlock(InputUIBlockFactory::MakeForHidden('interactive', '1')); - - $oFieldQuery = FieldUIBlockFactory::MakeStandard(''); - $oTextArea = new TextArea('expression', utils::EscapeHtml($sExpression), "textarea_oql", 70, 8); - $oTextArea->SetPlaceholder(Dict::S('Core:BulkExportQueryPlaceholder')); - $oTextArea->AddCSSClasses(["ibo-input-text", "ibo-query-oql", "ibo-is-code"]); - $oFieldQuery->AddSubBlock($oTextArea); - $oPanel->AddSubBlock($oFieldQuery); - if (!empty($sExceptionMessage)) { - $oAlert = AlertUIBlockFactory::MakeForFailure($sExceptionMessage); - $oAlert->SetIsCollapsible(false); - $oPanel->AddSubBlock($oAlert); - } - - $oFieldPhraseBook = FieldUIBlockFactory::MakeStandard(''); - $oSelect = SelectUIBlockFactory::MakeForSelect('query', "select_phrasebook"); - $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:SelectOne'), false)); - - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL'); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - while ($oQuery = $oQueries->Fetch()) { - $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($oQuery->GetKey(), $oQuery->Get('name'), false)); - } - $oFieldPhraseBook->AddSubBlock($oSelect); - $oPanel->AddSubBlock($oFieldPhraseBook); - - $oPanel->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Next'), "", "", true, "next-btn")); - $oP->p(''.Dict::S('Core:BulkExportCanRunNonInteractive').''); - $oP->p(''.Dict::S('Core:BulkExportLegacyExport').''); - $sJSEmptyOQL = json_encode(Dict::S('Core:BulkExportMessageEmptyOQL')); - $sJSEmptyQueryId = json_encode(Dict::S('Core:BulkExportMessageEmptyPhrasebookEntry')); - - $oP->add_ready_script( - <<SetAction($sAction); + $oP->AddSubBlock($oForm); + } + $oForm->AddSubBlock($oPanel); + + $oPanel->AddSubBlock(InputUIBlockFactory::MakeForHidden('interactive', '1')); + + $oFieldQuery = FieldUIBlockFactory::MakeStandard(''); + $oTextArea = new TextArea('expression', utils::EscapeHtml($sExpression), "textarea_oql", 70, 8); + $oTextArea->SetPlaceholder(Dict::S('Core:BulkExportQueryPlaceholder')); + $oTextArea->AddCSSClasses(["ibo-input-text", "ibo-query-oql", "ibo-is-code"]); + $oFieldQuery->AddSubBlock($oTextArea); + $oPanel->AddSubBlock($oFieldQuery); + if (!empty($sExceptionMessage)) { + $oAlert = AlertUIBlockFactory::MakeForFailure($sExceptionMessage); + $oAlert->SetIsCollapsible(false); + $oPanel->AddSubBlock($oAlert); + } + + $oFieldPhraseBook = FieldUIBlockFactory::MakeStandard(''); + $oSelect = SelectUIBlockFactory::MakeForSelect('query', "select_phrasebook"); + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:SelectOne'), false)); + + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL'); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + while ($oQuery = $oQueries->Fetch()) { + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($oQuery->GetKey(), $oQuery->Get('name'), false)); + } + $oFieldPhraseBook->AddSubBlock($oSelect); + $oPanel->AddSubBlock($oFieldPhraseBook); + + $oPanel->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Next'), "", "", true, "next-btn")); + $oP->p(''.Dict::S('Core:BulkExportCanRunNonInteractive').''); + $oP->p(''.Dict::S('Core:BulkExportLegacyExport').''); + $sJSEmptyOQL = json_encode(Dict::S('Core:BulkExportMessageEmptyOQL')); + $sJSEmptyQueryId = json_encode(Dict::S('Core:BulkExportMessageEmptyPhrasebookEntry')); + + $oP->add_ready_script( + <<add_script(DateTimeFormat::GetJSSQLToCustomFormat()); - $sJSDefaultDateTimeFormat = json_encode((string)AttributeDateTime::GetFormat()); - $oP->add_script( - <<add_script(DateTimeFormat::GetJSSQLToCustomFormat()); + $sJSDefaultDateTimeFormat = json_encode((string)AttributeDateTime::GetFormat()); + $oP->add_script( + <<LinkScriptFromAppRoot('js/tabularfieldsselector.js'); - $oP->LinkScriptFromAppRoot('js/jquery.dragtable.js'); - $oP->LinkStylesheetFromAppRoot('css/dragtable.css'); - - $oForm = FormUIBlockFactory::MakeStandard("export-form"); - $oForm->SetAction($sAction); - $oForm->AddDataAttribute("state", "not-yet-started"); - $oP->AddSubBlock($oForm); - - $bExpressionIsValid = true; - $sExpressionError = ''; - if (($sExpression === null) && ($sQueryId === null)) { - $bExpressionIsValid = false; - } elseif ($sExpression !== '') { - try { - $oExportSearch = DBObjectSearch::FromOQL($sExpression); - $oExportSearch->UpdateContextFromUser(); - } catch (OQLException $e) { - $bExpressionIsValid = false; - $sExpressionError = $e->getMessage(); - } - } - - if (!$bExpressionIsValid) { - DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError, $oForm); - - return; - } - - if ($sExpression !== '') { - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", $sExpression)); - $oExportSearch = DBObjectSearch::FromOQL($sExpression); - $oExportSearch->UpdateContextFromUser(); - } else { - $oQuery = MetaModel::GetObject('QueryOQL', $sQueryId); - $oExportSearch = DBObjectSearch::FromOQL($oQuery->Get('oql')); - $oExportSearch->UpdateContextFromUser(); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", $sQueryId)); - } - $aFormPartsByFormat = []; - $aAllFormParts = []; - if ($sFormat == null) { - // No specific format chosen - $sDefaultFormat = utils::ReadParam('format', 'xlsx'); - - $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel("format", Dict::S('Core:BulkExport:ExportFormatPrompt'), "format_selector"); - $oSelect->SetIsLabelBefore(true); - $oForm->AddSubBlock($oSelect); - - $aSupportedFormats = BulkExport::FindSupportedFormats(); - asort($aSupportedFormats); - foreach ($aSupportedFormats as $sFormatCode => $sLabel) { - $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sFormatCode, $sLabel, ($sFormatCode == $sDefaultFormat))); - $oExporter = BulkExport::FindExporter($sFormatCode); - $oExporter->SetObjectList($oExportSearch); - $aParts = $oExporter->EnumFormParts(); - foreach ($aParts as $sPartId => $void) { - $aAllFormParts[$sPartId] = $oExporter; - } - $aFormPartsByFormat[$sFormatCode] = array_keys($aParts); - } - - } else { - // One specific format was chosen - $oSelect = InputUIBlockFactory::MakeForHidden("format", utils::EscapeHtml($sFormat)); - $oForm->AddSubBlock($oSelect); - - $oExporter = BulkExport::FindExporter($sFormat, $oExportSearch); - $aParts = $oExporter->EnumFormParts(); - foreach ($aParts as $sPartId => $void) { - $aAllFormParts[$sPartId] = $oExporter; - } - $aFormPartsByFormat[$sFormat] = array_keys($aAllFormParts); - } - foreach ($aAllFormParts as $sPartId => $oExport) { - $UIContentBlock = UIContentBlockUIBlockFactory::MakeStandard('form_part_'.$sPartId)->AddCSSClass('form_part'); - $oForm->AddSubBlock($UIContentBlock); - $UIContentBlock->AddSubBlock($oExport->GetFormPart($oP, $sPartId)); - } - //end of form - $oBlockExport = UIContentBlockUIBlockFactory::MakeStandard("export-feedback")->SetIsHidden(true); - $oBlockExport->AddSubBlock(new Html('

'.Dict::S('ExcelExport:PreparingExport').'

')); - $oBlockExport->AddSubBlock(new Html('
')); - $oP->AddSubBlock($oBlockExport); - if ($sFormat == null) {//if it's global export - $oP->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction('export', Dict::S('UI:Button:Export'), 'export', false, 'export-btn')); - } - $oBlockResult = UIContentBlockUIBlockFactory::MakeStandard("export_text_result")->SetIsHidden(true); - $oBlockResult->AddSubBlock(new Html(Dict::S('Core:BulkExport:ExportResult'))); - - $oTextArea = new TextArea('export_content', '', 'export_content'); - $oTextArea->AddCSSClass('ibo-input-text--export'); - $oBlockResult->AddSubBlock($oTextArea); - $oP->AddSubBlock($oBlockResult); - - $sJSParts = json_encode($aFormPartsByFormat); - $oP->add_ready_script( - <<LinkScriptFromAppRoot('js/tabularfieldsselector.js'); + $oP->LinkScriptFromAppRoot('js/jquery.dragtable.js'); + $oP->LinkStylesheetFromAppRoot('css/dragtable.css'); + + $oForm = FormUIBlockFactory::MakeStandard("export-form"); + $oForm->SetAction($sAction); + $oForm->AddDataAttribute("state", "not-yet-started"); + $oP->AddSubBlock($oForm); + + $bExpressionIsValid = true; + $sExpressionError = ''; + if (($sExpression === null) && ($sQueryId === null)) { + $bExpressionIsValid = false; + } elseif ($sExpression !== '') { + try { + $oExportSearch = DBObjectSearch::FromOQL($sExpression); + $oExportSearch->UpdateContextFromUser(); + } catch (OQLException $e) { + $bExpressionIsValid = false; + $sExpressionError = $e->getMessage(); + } + } + + if (!$bExpressionIsValid) { + DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError, $oForm); + + return; + } + + if ($sExpression !== '') { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", $sExpression)); + $oExportSearch = DBObjectSearch::FromOQL($sExpression); + $oExportSearch->UpdateContextFromUser(); + } else { + $oQuery = MetaModel::GetObject('QueryOQL', $sQueryId); + $oExportSearch = DBObjectSearch::FromOQL($oQuery->Get('oql')); + $oExportSearch->UpdateContextFromUser(); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", $sQueryId)); + } + $aFormPartsByFormat = []; + $aAllFormParts = []; + if ($sFormat == null) { + // No specific format chosen + $sDefaultFormat = utils::ReadParam('format', 'xlsx'); + + $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel("format", Dict::S('Core:BulkExport:ExportFormatPrompt'), "format_selector"); + $oSelect->SetIsLabelBefore(true); + $oForm->AddSubBlock($oSelect); + + $aSupportedFormats = BulkExport::FindSupportedFormats(); + asort($aSupportedFormats); + foreach ($aSupportedFormats as $sFormatCode => $sLabel) { + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sFormatCode, $sLabel, ($sFormatCode == $sDefaultFormat))); + $oExporter = BulkExport::FindExporter($sFormatCode); + $oExporter->SetObjectList($oExportSearch); + $aParts = $oExporter->EnumFormParts(); + foreach ($aParts as $sPartId => $void) { + $aAllFormParts[$sPartId] = $oExporter; + } + $aFormPartsByFormat[$sFormatCode] = array_keys($aParts); + } + + } else { + // One specific format was chosen + $oSelect = InputUIBlockFactory::MakeForHidden("format", utils::EscapeHtml($sFormat)); + $oForm->AddSubBlock($oSelect); + + $oExporter = BulkExport::FindExporter($sFormat, $oExportSearch); + $aParts = $oExporter->EnumFormParts(); + foreach ($aParts as $sPartId => $void) { + $aAllFormParts[$sPartId] = $oExporter; + } + $aFormPartsByFormat[$sFormat] = array_keys($aAllFormParts); + } + foreach ($aAllFormParts as $sPartId => $oExport) { + $UIContentBlock = UIContentBlockUIBlockFactory::MakeStandard('form_part_'.$sPartId)->AddCSSClass('form_part'); + $oForm->AddSubBlock($UIContentBlock); + $UIContentBlock->AddSubBlock($oExport->GetFormPart($oP, $sPartId)); + } + //end of form + $oBlockExport = UIContentBlockUIBlockFactory::MakeStandard("export-feedback")->SetIsHidden(true); + $oBlockExport->AddSubBlock(new Html('

'.Dict::S('ExcelExport:PreparingExport').'

')); + $oBlockExport->AddSubBlock(new Html('
')); + $oP->AddSubBlock($oBlockExport); + if ($sFormat == null) {//if it's global export + $oP->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction('export', Dict::S('UI:Button:Export'), 'export', false, 'export-btn')); + } + $oBlockResult = UIContentBlockUIBlockFactory::MakeStandard("export_text_result")->SetIsHidden(true); + $oBlockResult->AddSubBlock(new Html(Dict::S('Core:BulkExport:ExportResult'))); + + $oTextArea = new TextArea('export_content', '', 'export_content'); + $oTextArea->AddCSSClass('ibo-input-text--export'); + $oBlockResult->AddSubBlock($oTextArea); + $oP->AddSubBlock($oBlockResult); + + $sJSParts = json_encode($aFormPartsByFormat); + $oP->add_ready_script( + <<add('
'); - $oP->add_ready_script( - <<add('
'); + $oP->add_ready_script( + <<SetBreadCrumbEntry('ui-tool-export', Dict::S('Menu:ExportMenu'), Dict::S('Menu:ExportMenu+'), '', 'fas fa-file-export', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); - } - - if ($sExpression === null) { - // No expression supplied, let's check if phrasebook entry is given - if ($sQueryId !== null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - } else { - ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); - } - } else { - if (utils::IsModeCLI()) { - Usage($oP); - ReportErrorAndExit("No expression or query phrasebook identifier supplied."); - } else { - // form to enter an OQL query or pick a query phrasebook identifier - DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); - $oP->output(); - exit; - } - } - } - - if ($sFormat !== null) { - $oExporter = BulkExport::FindExporter($sFormat); - if ($oExporter === null) { - $aSupportedFormats = BulkExport::FindSupportedFormats(); - ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); - } else { - DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); - } - } else { - DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); - } - if ($sMode == 'dialog') { - $oP->add('
'); - } - $oP->output(); + ); + } else { + $oP = new iTopWebPage('iTop Export'); + $oP->SetBreadCrumbEntry('ui-tool-export', Dict::S('Menu:ExportMenu'), Dict::S('Menu:ExportMenu+'), '', 'fas fa-file-export', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); + } + + if ($sExpression === null) { + // No expression supplied, let's check if phrasebook entry is given + if ($sQueryId !== null) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + } else { + ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); + } + } else { + if (utils::IsModeCLI()) { + Usage($oP); + ReportErrorAndExit("No expression or query phrasebook identifier supplied."); + } else { + // form to enter an OQL query or pick a query phrasebook identifier + DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); + $oP->output(); + exit; + } + } + } + + if ($sFormat !== null) { + $oExporter = BulkExport::FindExporter($sFormat); + if ($oExporter === null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + } else { + DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); + } + } else { + DisplayForm($oP, utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php', $sExpression, $sQueryId, $sFormat); + } + if ($sMode == 'dialog') { + $oP->add('
'); + } + $oP->output(); } /** @@ -422,101 +422,101 @@ function InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode) */ function CheckParameters($sExpression, $sQueryId, $sFormat) { - $oExporter = null; - $oQuery = null; - - if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { - ReportErrorAndExit("The user account is not authorized to access the archives"); - } - - if (($sExpression === null) && ($sQueryId === null)) { - ReportErrorAndUsage("Missing parameter. The parameter 'expression' or 'query' must be specified."); - } - - // Either $sExpression or $sQueryId must be specified - if ($sExpression === null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - } else { - ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); - } - } - if ($sFormat === null) { - ReportErrorAndUsage("Missing parameter 'format'."); - } - - // Check if the supplied query is valid (and all the parameters are supplied - try { - $oSearch = DBObjectSearch::FromOQL($sExpression); - $oSearch->UpdateContextFromUser(); - $aArgs = []; - foreach ($oSearch->GetQueryParams() as $sParam => $foo) { - $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) { - $aArgs[$sParam] = $value; - } else { - throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); - } - } - $oSearch->SetInternalParams($aArgs); - - $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); - $oExporter = BulkExport::FindExporter($sFormat, $oSearch); - if ($oExporter == null) { - $aSupportedFormats = BulkExport::FindSupportedFormats(); - ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); - } - } catch (MissingQueryArgument $e) { - $oSearch = null; - ReportErrorAndUsage("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); - } catch (OQLException $e) { - $oSearch = null; - ReportErrorAndExit("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); - } catch (Exception $e) { - $oSearch = null; - ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); - } - - // update last export information if check parameters ok - if ($oQuery != null) { - $oQuery->UpdateLastExportInformation(); - } - - $oExporter->SetFormat($sFormat); - $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); - $oExporter->SetObjectList($oSearch); - $oExporter->ReadParameters(); - - return $oExporter; + $oExporter = null; + $oQuery = null; + + if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { + ReportErrorAndExit("The user account is not authorized to access the archives"); + } + + if (($sExpression === null) && ($sQueryId === null)) { + ReportErrorAndUsage("Missing parameter. The parameter 'expression' or 'query' must be specified."); + } + + // Either $sExpression or $sQueryId must be specified + if ($sExpression === null) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + } else { + ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); + } + } + if ($sFormat === null) { + ReportErrorAndUsage("Missing parameter 'format'."); + } + + // Check if the supplied query is valid (and all the parameters are supplied + try { + $oSearch = DBObjectSearch::FromOQL($sExpression); + $oSearch->UpdateContextFromUser(); + $aArgs = []; + foreach ($oSearch->GetQueryParams() as $sParam => $foo) { + $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); + if (!is_null($value)) { + $aArgs[$sParam] = $value; + } else { + throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); + } + } + $oSearch->SetInternalParams($aArgs); + + $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); + $oExporter = BulkExport::FindExporter($sFormat, $oSearch); + if ($oExporter == null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + } + } catch (MissingQueryArgument $e) { + $oSearch = null; + ReportErrorAndUsage("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); + } catch (OQLException $e) { + $oSearch = null; + ReportErrorAndExit("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); + } catch (Exception $e) { + $oSearch = null; + ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); + } + + // update last export information if check parameters ok + if ($oQuery != null) { + $oQuery->UpdateLastExportInformation(); + } + + $oExporter->SetFormat($sFormat); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + $oExporter->SetObjectList($oSearch); + $oExporter->ReadParameters(); + + return $oExporter; } function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) { - $oExporter->SetHttpHeaders($oP); - $exportResult = $oExporter->GetHeader(); - $aStatus = []; - do { - $exportResult .= $oExporter->GetNextChunk($aStatus); - } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'error') { - $oExporter->Cleanup(); - ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); - } else { - $exportResult .= $oExporter->GetFooter(); - $sMimeType = $oExporter->GetMimeType(); - if (substr($sMimeType, 0, 5) == 'text/') { - $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); - } - $oP->SetContentType($sMimeType); - $oP->SetContentDisposition('attachment', $oExporter->GetDownloadFileName()); - $oP->add($exportResult); - $oExporter->Cleanup(); - } + $oExporter->SetHttpHeaders($oP); + $exportResult = $oExporter->GetHeader(); + $aStatus = []; + do { + $exportResult .= $oExporter->GetNextChunk($aStatus); + } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'error') { + $oExporter->Cleanup(); + ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); + } else { + $exportResult .= $oExporter->GetFooter(); + $sMimeType = $oExporter->GetMimeType(); + if (substr($sMimeType, 0, 5) == 'text/') { + $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); + } + $oP->SetContentType($sMimeType); + $oP->SetContentDisposition('attachment', $oExporter->GetDownloadFileName()); + $oP->add($exportResult); + $oExporter->Cleanup(); + } } ///////////////////////////////////////////////////////////////////////////// @@ -531,103 +531,103 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) $oCtx = new ContextTag(ContextTag::TAG_EXPORT); if (utils::IsModeCLI()) { - SetupUtils::CheckPhpAndExtensionsForCli(new CLIPage('iTop - Export')); - - try { - // Do this before loging, in order to allow setting user credentials from within the file - utils::UseParamFile(); - } catch (Exception $e) { - echo "Error: ".utils::HtmlEntities($e->getMessage())."
\n"; - exit(EXIT_CODE_FATAL); - } - - $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); - $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); - if ($sAuthUser == null) { - ReportErrorAndUsage("Missing parameter '--auth_user'"); - } - if ($sAuthPwd == null) { - ReportErrorAndUsage("Missing parameter '--auth_pwd'"); - } - - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { - UserRights::Login($sAuthUser); // Login & set the user's language - } else { - ReportErrorAndExit("Access restricted or wrong credentials for user '$sAuthUser'"); - } - - $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); - $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); - $bLocalize = (utils::ReadParam('no_localize', 0) != 1); - if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { - ReportErrorAndExit("The user account is not authorized to access the archives"); - } - - if (($sExpression == null) && ($sQueryId == null)) { - ReportErrorAndUsage("Missing parameter. At least one of '--expression' or '--query' must be specified."); - } - - if ($sExpression === null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); - $oSearch->UpdateContextFromUser(); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - } else { - ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); - } - } - try { - $oSearch = DBObjectSearch::FromOQL($sExpression); - $oSearch->UpdateContextFromUser(); - $aArgs = []; - foreach ($oSearch->GetQueryParams() as $sParam => $foo) { - $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) { - $aArgs[$sParam] = $value; - } else { - throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); - } - } - $oSearch->SetInternalParams($aArgs); - - $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); - $oExporter = BulkExport::FindExporter($sFormat); - if ($oExporter == null) { - $aSupportedFormats = BulkExport::FindSupportedFormats(); - ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); - } - - $oExporter->SetFormat($sFormat); - $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); - $oExporter->SetObjectList($oSearch); - $oExporter->ReadParameters(); - - $exportResult = $oExporter->GetHeader(); - $aStatus = []; - - do { - $exportResult .= $oExporter->GetNextChunk($aStatus); - } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'error') { - ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); - } else { - $exportResult .= $oExporter->GetFooter(); - echo $exportResult; - } - $oExporter->Cleanup(); - - } catch (MissingQueryArgument $e) { - ReportErrorAndUsage("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); - } catch (OQLException $e) { - ReportErrorAndExit("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); - } catch (Exception $e) { - ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); - } - - exit; + SetupUtils::CheckPhpAndExtensionsForCli(new CLIPage('iTop - Export')); + + try { + // Do this before loging, in order to allow setting user credentials from within the file + utils::UseParamFile(); + } catch (Exception $e) { + echo "Error: ".utils::HtmlEntities($e->getMessage())."
\n"; + exit(EXIT_CODE_FATAL); + } + + $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); + $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); + if ($sAuthUser == null) { + ReportErrorAndUsage("Missing parameter '--auth_user'"); + } + if ($sAuthPwd == null) { + ReportErrorAndUsage("Missing parameter '--auth_pwd'"); + } + + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + ReportErrorAndExit("Access restricted or wrong credentials for user '$sAuthUser'"); + } + + $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); + $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); + $bLocalize = (utils::ReadParam('no_localize', 0) != 1); + if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { + ReportErrorAndExit("The user account is not authorized to access the archives"); + } + + if (($sExpression == null) && ($sQueryId == null)) { + ReportErrorAndUsage("Missing parameter. At least one of '--expression' or '--query' must be specified."); + } + + if ($sExpression === null) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch->UpdateContextFromUser(); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + } else { + ReportErrorAndExit("Invalid query phrasebook identifier: '$sQueryId'"); + } + } + try { + $oSearch = DBObjectSearch::FromOQL($sExpression); + $oSearch->UpdateContextFromUser(); + $aArgs = []; + foreach ($oSearch->GetQueryParams() as $sParam => $foo) { + $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); + if (!is_null($value)) { + $aArgs[$sParam] = $value; + } else { + throw new MissingQueryArgument("Missing parameter '--arg_$sParam'"); + } + } + $oSearch->SetInternalParams($aArgs); + + $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); + $oExporter = BulkExport::FindExporter($sFormat); + if ($oExporter == null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + } + + $oExporter->SetFormat($sFormat); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + $oExporter->SetObjectList($oSearch); + $oExporter->ReadParameters(); + + $exportResult = $oExporter->GetHeader(); + $aStatus = []; + + do { + $exportResult .= $oExporter->GetNextChunk($aStatus); + } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'error') { + ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); + } else { + $exportResult .= $oExporter->GetFooter(); + echo $exportResult; + } + $oExporter->Cleanup(); + + } catch (MissingQueryArgument $e) { + ReportErrorAndUsage("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); + } catch (OQLException $e) { + ReportErrorAndExit("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); + } catch (Exception $e) { + ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); + } + + exit; } ///////////////////////////////////////////////////////////////////////////// @@ -637,55 +637,55 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) ///////////////////////////////////////////////////////////////////////////// try { - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - - // Main parameters - $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); - $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); - $sFormat = utils::ReadParam('format', null, true /* Allow CLI */); - $sFileName = utils::ReadParam('filename', '', true, 'string'); - $bInteractive = utils::ReadParam('interactive', false); - $sMode = utils::ReadParam('mode', ''); - - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - - ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); - - if ($bInteractive) { - InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode); - } else { - $oExporter = CheckParameters($sExpression, $sQueryId, $sFormat); - $sMimeType = $oExporter->GetMimeType(); - if ($sMimeType == 'text/html') { - // Note: Using NiceWebPage only for HTML export as it includes JS scripts & files, which makes no sense in other export formats. More over, it breaks Excel spreadsheet import. - if ($oExporter instanceof HTMLBulkExport) { - $oP = new NiceWebPage('iTop export'); - $oP->add_http_headers(); - $oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});"); - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); - } else { - $oP = new WebPage('iTop export'); - $oP->add_http_headers(); - $oP->add_style("table br { mso-data-placement:same-cell; }"); // Trick for Excel: keep line breaks inside the same cell ! - } - $oP->add_style("body { overflow: auto; }"); - } else { - $oP = new DownloadPage('iTop export'); - $oP->SetContentType($oExporter->GetMimeType()); - } - DoExport($oP, $oExporter, false); - $oP->output(); - } + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + + // Main parameters + $sExpression = utils::ReadParam('expression', null, true /* Allow CLI */, 'raw_data'); + $sQueryId = utils::ReadParam('query', null, true /* Allow CLI */, 'raw_data'); + $sFormat = utils::ReadParam('format', null, true /* Allow CLI */); + $sFileName = utils::ReadParam('filename', '', true, 'string'); + $bInteractive = utils::ReadParam('interactive', false); + $sMode = utils::ReadParam('mode', ''); + + LoginWebPage::DoLogin(); // Check user rights and prompt if needed + + ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); + + if ($bInteractive) { + InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode); + } else { + $oExporter = CheckParameters($sExpression, $sQueryId, $sFormat); + $sMimeType = $oExporter->GetMimeType(); + if ($sMimeType == 'text/html') { + // Note: Using NiceWebPage only for HTML export as it includes JS scripts & files, which makes no sense in other export formats. More over, it breaks Excel spreadsheet import. + if ($oExporter instanceof HTMLBulkExport) { + $oP = new NiceWebPage('iTop export'); + $oP->add_http_headers(); + $oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});"); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); + } else { + $oP = new WebPage('iTop export'); + $oP->add_http_headers(); + $oP->add_style("table br { mso-data-placement:same-cell; }"); // Trick for Excel: keep line breaks inside the same cell ! + } + $oP->add_style("body { overflow: auto; }"); + } else { + $oP = new DownloadPage('iTop export'); + $oP->SetContentType($oExporter->GetMimeType()); + } + DoExport($oP, $oExporter, false); + $oP->output(); + } } catch (BulkExportMissingParameterException $e) { - $oP = new AjaxPage('iTop Export'); - $oP->add(utils::HtmlEntities($e->getMessage())); - Usage($oP); - $oP->output(); + $oP = new AjaxPage('iTop Export'); + $oP->add(utils::HtmlEntities($e->getMessage())); + Usage($oP); + $oP->output(); } catch (Exception $e) { - $oP = new WebPage('iTop Export'); - $oP->add_http_headers(); - $oP->add('Error: '.utils::HtmlEntities($e->getMessage())); - IssueLog::Error(utils::HtmlEntities($e->getMessage())."\n".$e->getTraceAsString()); - $oP->output(); + $oP = new WebPage('iTop Export'); + $oP->add_http_headers(); + $oP->add('Error: '.utils::HtmlEntities($e->getMessage())); + IssueLog::Error(utils::HtmlEntities($e->getMessage())."\n".$e->getTraceAsString()); + $oP->output(); } diff --git a/webservices/export.php b/webservices/export.php index c35d4f1f3c..18fa2b441e 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -43,11 +43,11 @@ const EXIT_CODE_FATAL = -2; try { - // Do this before loging, in order to allow setting user credentials from within the file - utils::UseParamFile(); + // Do this before loging, in order to allow setting user credentials from within the file + utils::UseParamFile(); } catch (Exception $e) { - echo "Error: ".$e->GetMessage()."
\n"; - exit(EXIT_CODE_FATAL); + echo "Error: ".$e->GetMessage()."
\n"; + exit(EXIT_CODE_FATAL); } /** @@ -55,22 +55,22 @@ */ $oCtx = new ContextTag(ContextTag::TAG_EXPORT); if (utils::IsModeCLI()) { - $oP = new CLIPage("iTop - Export"); - SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); - - $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); - $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); - - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { - UserRights::Login($sAuthUser); // Login & set the user's language - } else { - $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); - $oP->output(); - exit(EXIT_CODE_ERROR); - } + $oP = new CLIPage("iTop - Export"); + SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); + + $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); + $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); + + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); + $oP->output(); + exit(EXIT_CODE_ERROR); + } } else { - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed } ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); @@ -79,10 +79,10 @@ $currentOrganization = utils::ReadParam('org_id', ''); if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { - $oP = new CLIPage("iTop - Export"); - $oP->p("The user account is not authorized to access the archives"); - $oP->output(); - exit(EXIT_CODE_ERROR); + $oP = new CLIPage("iTop - Export"); + $oP->p("The user account is not authorized to access the archives"); + $oP->output(); + exit(EXIT_CODE_ERROR); } $bLocalize = (utils::ReadParam('no_localize', 0) != 1); @@ -96,18 +96,18 @@ $oQuery = null; if (strlen($sExpression) == 0) { - $sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data')); - if (strlen($sQueryId) > 0) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { - $oQuery = $oQueries->Fetch(); - $sExpression = $oQuery->Get('oql'); - if (strlen($sFields) == 0) { - $sFields = trim($oQuery->Get('fields')); - } - } - } + $sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data')); + if (strlen($sQueryId) > 0) { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { + $oQuery = $oQueries->Fetch(); + $sExpression = $oQuery->Get('oql'); + if (strlen($sFields) == 0) { + $sFields = trim($oQuery->Get('fields')); + } + } + } } $sFormat = strtolower(utils::ReadParam('format', 'html', true /* Allow CLI */)); @@ -115,221 +115,221 @@ $aFields = explode(',', $sFields); // Clean the list of columns (empty it if every string is empty) foreach ($aFields as $index => $sField) { - $aFields[$index] = trim($sField); - if (strlen($aFields[$index]) == 0) { - unset($aFields[$index]); - } + $aFields[$index] = trim($sField); + if (strlen($aFields[$index]) == 0) { + unset($aFields[$index]); + } } $oP = null; if (!empty($sExpression)) { - try { - $oFilter = DBObjectSearch::FromOQL($sExpression); - - // Check and adjust column names - // - $aAliasToFields = []; - foreach ($aFields as $index => $sField) { - if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches)) { - $sClassAlias = $aMatches[1]; - $sAttCode = $aMatches[2]; - } else { - $sClassAlias = $oFilter->GetClassAlias(); - $sAttCode = $sField; - // Disambiguate the class alias - $aFields[$index] = $sClassAlias.'.'.$sAttCode; - } - $aAliasToFields[$sClassAlias][] = $sAttCode; - - $sClass = $oFilter->GetClassName($sClassAlias); - if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) { - throw new CoreException("Invalid field specification $sField: $sAttCode is not a valid attribute for $sClass"); - } - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef instanceof AttributeSubItem) { - $aAliasToFields[$sClassAlias][] = $oAttDef->GetParentAttCode(); - } elseif ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $aAliasToFields[$sClassAlias][] = $sKeyAttCode; - } - } - - // Read query parameters - // - $aArgs = []; - foreach ($oFilter->GetQueryParams() as $sParam => $foo) { - $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) { - $aArgs[$sParam] = $value; - } - } - $oFilter->SetInternalParams($aArgs); - foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass) { - if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0) { - throw new Exception("The current user does not have permission for exporting data of class $sClass"); - } - } - - // update last export information if check parameters ok - if ($oQuery != null) { - $oQuery->UpdateLastExportInformation(); - } - - if ($oFilter) { - $oSet = new CMDBObjectSet($oFilter, [], $aArgs); - $oSet->OptimizeColumnLoad($aAliasToFields); - switch ($sFormat) { - case 'html': - $oP = new NiceWebPage("iTop - Export"); - $oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); - $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); - - // Integration within MS-Excel web queries + HTTPS + IIS: - // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS - // Then the fix is to force the reset of header values Pragma and Cache-control - header("Cache-control:", true); - header("Pragma:", true); - - // The HTML output is made for pages located in the /pages/ folder - // since this page is in a different folder, let's adjust the HTML 'base' attribute - // to make the relative hyperlinks in the page work - $sUrl = utils::GetAbsoluteUrlAppRoot(); - $oP->set_base($sUrl.'pages/'); - - if (count($aFields) > 0) { - $iSearch = array_search('id', $aFields); - if ($iSearch !== false) { - $bViewLink = true; - unset($aFields[$iSearch]); - } else { - $bViewLink = false; - } - $sFields = implode(',', $aFields); - $aExtraParams = [ - 'menu' => false, - 'toolkit_menu' => false, - 'display_limit' => false, - 'localize_values' => $bLocalize, - 'zlist' => false, - 'extra_fields' => $sFields, - 'view_link' => $bViewLink, - ]; - } else { - $aExtraParams = [ - 'menu' => false, - 'toolkit_menu' => false, - 'display_limit' => false, - 'localize_values' => $bLocalize, - 'zlist' => 'details', - ]; - } - - $oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams); - $oResultBlock->Display($oP, 'expresult'); - break; - - case 'csv': - $oP = new CSVPage("iTop - Export"); - $sFields = implode(',', $aFields); - $sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data'); - $sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize], $sCharset); - if ($sCharset == 'UTF-8') { - $sOutputData = UTF8_BOM.$sCSVData; - } else { - $sOutputData = $sCSVData; - } - if ($sFileName == '') { - // Plain text => Firefox will NOT propose to download the file - $oP->add_header("Content-type: text/plain; charset=$sCharset"); - } else { - $oP->add_header("Content-type: text/csv; charset=$sCharset"); - } - $oP->add($sOutputData); - break; - - case 'spreadsheet': - $oP = new WebPage("iTop - Export for spreadsheet"); - - // Integration within MS-Excel web queries + HTTPS + IIS: - // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS - // Then the fix is to force the reset of header values Pragma and Cache-control - header("Pragma:", true); - header("Cache-control:", true); - - $sFields = implode(',', $aFields); - $oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell ! - cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize]); - break; - - case 'xml': - $oP = new XMLPage("iTop - Export", true /* passthrough */); - cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, ['localize_values' => $bLocalize]); - break; - - case 'xlsx': - $oP = new AjaxPage(''); - $oExporter = new ExcelExporter(); - $oExporter->SetObjectList($oFilter); - - // Run the export by chunk of 1000 objects to limit memory usage - $oExporter->SetChunkSize(1000); - do { - $aStatus = $oExporter->Run(); // process one chunk - } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'done') { - $oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - $oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx'); - $oP->add(file_get_contents($oExporter->GetExcelFilePath())); - $oExporter->Cleanup(); - } else { - $oP->add('Error, xlsx export failed: '.$aStatus['message']); - } - break; - - default: - $oP = new WebPage("iTop - Export"); - $oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml."); - } - } - } catch (Exception $e) { - $oP = new WebPage("iTop - Export"); - $oP->p("Error the query can not be executed."); - if ($e instanceof CoreException) { - $oP->p($e->GetHtmlDesc()); - } else { - $oP->p($e->getMessage()); - } - } + try { + $oFilter = DBObjectSearch::FromOQL($sExpression); + + // Check and adjust column names + // + $aAliasToFields = []; + foreach ($aFields as $index => $sField) { + if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches)) { + $sClassAlias = $aMatches[1]; + $sAttCode = $aMatches[2]; + } else { + $sClassAlias = $oFilter->GetClassAlias(); + $sAttCode = $sField; + // Disambiguate the class alias + $aFields[$index] = $sClassAlias.'.'.$sAttCode; + } + $aAliasToFields[$sClassAlias][] = $sAttCode; + + $sClass = $oFilter->GetClassName($sClassAlias); + if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) { + throw new CoreException("Invalid field specification $sField: $sAttCode is not a valid attribute for $sClass"); + } + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef instanceof AttributeSubItem) { + $aAliasToFields[$sClassAlias][] = $oAttDef->GetParentAttCode(); + } elseif ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $aAliasToFields[$sClassAlias][] = $sKeyAttCode; + } + } + + // Read query parameters + // + $aArgs = []; + foreach ($oFilter->GetQueryParams() as $sParam => $foo) { + $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); + if (!is_null($value)) { + $aArgs[$sParam] = $value; + } + } + $oFilter->SetInternalParams($aArgs); + foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass) { + if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0) { + throw new Exception("The current user does not have permission for exporting data of class $sClass"); + } + } + + // update last export information if check parameters ok + if ($oQuery != null) { + $oQuery->UpdateLastExportInformation(); + } + + if ($oFilter) { + $oSet = new CMDBObjectSet($oFilter, [], $aArgs); + $oSet->OptimizeColumnLoad($aAliasToFields); + switch ($sFormat) { + case 'html': + $oP = new NiceWebPage("iTop - Export"); + $oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); + + // Integration within MS-Excel web queries + HTTPS + IIS: + // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS + // Then the fix is to force the reset of header values Pragma and Cache-control + header("Cache-control:", true); + header("Pragma:", true); + + // The HTML output is made for pages located in the /pages/ folder + // since this page is in a different folder, let's adjust the HTML 'base' attribute + // to make the relative hyperlinks in the page work + $sUrl = utils::GetAbsoluteUrlAppRoot(); + $oP->set_base($sUrl.'pages/'); + + if (count($aFields) > 0) { + $iSearch = array_search('id', $aFields); + if ($iSearch !== false) { + $bViewLink = true; + unset($aFields[$iSearch]); + } else { + $bViewLink = false; + } + $sFields = implode(',', $aFields); + $aExtraParams = [ + 'menu' => false, + 'toolkit_menu' => false, + 'display_limit' => false, + 'localize_values' => $bLocalize, + 'zlist' => false, + 'extra_fields' => $sFields, + 'view_link' => $bViewLink, + ]; + } else { + $aExtraParams = [ + 'menu' => false, + 'toolkit_menu' => false, + 'display_limit' => false, + 'localize_values' => $bLocalize, + 'zlist' => 'details', + ]; + } + + $oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams); + $oResultBlock->Display($oP, 'expresult'); + break; + + case 'csv': + $oP = new CSVPage("iTop - Export"); + $sFields = implode(',', $aFields); + $sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data'); + $sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize], $sCharset); + if ($sCharset == 'UTF-8') { + $sOutputData = UTF8_BOM.$sCSVData; + } else { + $sOutputData = $sCSVData; + } + if ($sFileName == '') { + // Plain text => Firefox will NOT propose to download the file + $oP->add_header("Content-type: text/plain; charset=$sCharset"); + } else { + $oP->add_header("Content-type: text/csv; charset=$sCharset"); + } + $oP->add($sOutputData); + break; + + case 'spreadsheet': + $oP = new WebPage("iTop - Export for spreadsheet"); + + // Integration within MS-Excel web queries + HTTPS + IIS: + // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS + // Then the fix is to force the reset of header values Pragma and Cache-control + header("Pragma:", true); + header("Cache-control:", true); + + $sFields = implode(',', $aFields); + $oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell ! + cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize]); + break; + + case 'xml': + $oP = new XMLPage("iTop - Export", true /* passthrough */); + cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, ['localize_values' => $bLocalize]); + break; + + case 'xlsx': + $oP = new AjaxPage(''); + $oExporter = new ExcelExporter(); + $oExporter->SetObjectList($oFilter); + + // Run the export by chunk of 1000 objects to limit memory usage + $oExporter->SetChunkSize(1000); + do { + $aStatus = $oExporter->Run(); // process one chunk + } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'done') { + $oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx'); + $oP->add(file_get_contents($oExporter->GetExcelFilePath())); + $oExporter->Cleanup(); + } else { + $oP->add('Error, xlsx export failed: '.$aStatus['message']); + } + break; + + default: + $oP = new WebPage("iTop - Export"); + $oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml."); + } + } + } catch (Exception $e) { + $oP = new WebPage("iTop - Export"); + $oP->p("Error the query can not be executed."); + if ($e instanceof CoreException) { + $oP->p($e->GetHtmlDesc()); + } else { + $oP->p($e->getMessage()); + } + } } if (!$oP) { - // Display a short message about how to use this page - $bModeCLI = utils::IsModeCLI(); - if ($bModeCLI) { - $oP = new CLIPage("iTop - Export"); - } else { - $oP = new WebPage("iTop - Export"); - } - $oP->p("General purpose export page."); - $oP->p("Parameters:"); - $oP->p(" * expression: an OQL expression (URL encoded if needed)"); - $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); - if (Utils::IsModeCLI()) { - $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); - } else { - $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); - } - $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); - $oP->p(" * format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv', 'xlsx' or 'xml'"); - $oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a comma"); - $oP->p(" * fields_advanced: (optional, no effect on XML/HTML formats ; ignored is fields is specified) If set to 1, the default list of fields will include the external keys and their reconciliation keys"); - $oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file"); + // Display a short message about how to use this page + $bModeCLI = utils::IsModeCLI(); + if ($bModeCLI) { + $oP = new CLIPage("iTop - Export"); + } else { + $oP = new WebPage("iTop - Export"); + } + $oP->p("General purpose export page."); + $oP->p("Parameters:"); + $oP->p(" * expression: an OQL expression (URL encoded if needed)"); + $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); + if (Utils::IsModeCLI()) { + $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); + } else { + $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); + } + $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); + $oP->p(" * format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv', 'xlsx' or 'xml'"); + $oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a comma"); + $oP->p(" * fields_advanced: (optional, no effect on XML/HTML formats ; ignored is fields is specified) If set to 1, the default list of fields will include the external keys and their reconciliation keys"); + $oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file"); } if ($sFileName != '') { - $oP->add_header('Content-Disposition: attachment; filename="'.$sFileName.'"'); + $oP->add_header('Content-Disposition: attachment; filename="'.$sFileName.'"'); } $oP->TrashUnexpectedOutput(); diff --git a/webservices/import.php b/webservices/import.php index 2c757fc945..807011a6b5 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -42,160 +42,160 @@ class BulkLoadException extends Exception $aPageParams = [ - 'auth_user' => - [ - 'mandatory' => true, - 'modes' => 'cli', - 'default' => null, - 'description' => 'login (must have enough rights to create objects of the given class)', - ], - 'auth_pwd' => - [ - 'mandatory' => true, - 'modes' => 'cli', - 'default' => null, - 'description' => 'password', - ], - 'class' => - [ - 'mandatory' => true, - 'modes' => 'http,cli', - 'default' => null, - 'description' => 'class of loaded objects', - ], - 'csvdata' => - [ - 'mandatory' => true, - 'modes' => 'http', - 'default' => null, - 'description' => 'data', - ], - 'csvfile' => - [ - 'mandatory' => true, - 'modes' => 'cli', - 'default' => '', - 'description' => 'local data file, replaces csvdata if specified', - ], - 'charset' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'Character set encoding of the CSV data: UTF-8, ISO-8859-1, WINDOWS-1251, WINDOWS-1252, ISO-8859-15, If blank, then the charset is set to config(csv_file_default_charset)', - ], - 'date_format' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)', - ], - 'separator' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => ',', - 'description' => 'column separator in CSV data (1 char, or \'tab\')', - ], - 'qualifier' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '"', - 'description' => 'test qualifier in CSV data', - ], - 'output' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => 'summary', - 'description' => '[retcode] to return the count of lines in error, [summary] to return a concise report, [details] to get a detailed report (each line listed)', - ], + 'auth_user' => + [ + 'mandatory' => true, + 'modes' => 'cli', + 'default' => null, + 'description' => 'login (must have enough rights to create objects of the given class)', + ], + 'auth_pwd' => + [ + 'mandatory' => true, + 'modes' => 'cli', + 'default' => null, + 'description' => 'password', + ], + 'class' => + [ + 'mandatory' => true, + 'modes' => 'http,cli', + 'default' => null, + 'description' => 'class of loaded objects', + ], + 'csvdata' => + [ + 'mandatory' => true, + 'modes' => 'http', + 'default' => null, + 'description' => 'data', + ], + 'csvfile' => + [ + 'mandatory' => true, + 'modes' => 'cli', + 'default' => '', + 'description' => 'local data file, replaces csvdata if specified', + ], + 'charset' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'Character set encoding of the CSV data: UTF-8, ISO-8859-1, WINDOWS-1251, WINDOWS-1252, ISO-8859-15, If blank, then the charset is set to config(csv_file_default_charset)', + ], + 'date_format' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)', + ], + 'separator' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => ',', + 'description' => 'column separator in CSV data (1 char, or \'tab\')', + ], + 'qualifier' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '"', + 'description' => 'test qualifier in CSV data', + ], + 'output' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => 'summary', + 'description' => '[retcode] to return the count of lines in error, [summary] to return a concise report, [details] to get a detailed report (each line listed)', + ], /* - 'reportlevel' => array - ( - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => 'errors|warnings|created|changed|unchanged', - 'description' => 'combination of flags to limit the detailed output', - ), + 'reportlevel' => array + ( + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => 'errors|warnings|created|changed|unchanged', + 'description' => 'combination of flags to limit the detailed output', + ), */ - 'reconciliationkeys' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'name of the columns used to identify existing objects and update them, or create a new one', - ], - 'simulate' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '0', - 'description' => 'If set to 1, then the load will not be executed, but the expected report will be produced', - ], - 'comment' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '', - 'description' => 'Comment to be added into the change log', - ], - 'no_localize' => - [ - 'mandatory' => false, - 'modes' => 'http,cli', - 'default' => '0', - 'description' => 'If set to 0, then header and values are supposed to be localized in the language of the logged in user. Set to 1 to use internal attribute codes and values (enums)', - ], + 'reconciliationkeys' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'name of the columns used to identify existing objects and update them, or create a new one', + ], + 'simulate' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '0', + 'description' => 'If set to 1, then the load will not be executed, but the expected report will be produced', + ], + 'comment' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '', + 'description' => 'Comment to be added into the change log', + ], + 'no_localize' => + [ + 'mandatory' => false, + 'modes' => 'http,cli', + 'default' => '0', + 'description' => 'If set to 0, then header and values are supposed to be localized in the language of the logged in user. Set to 1 to use internal attribute codes and values (enums)', + ], ]; function UsageAndExit($oP) { - global $aPageParams; - $bModeCLI = utils::IsModeCLI(); - - $oP->p("USAGE:\n"); - foreach ($aPageParams as $sParam => $aParamData) { - $aModes = explode(',', $aParamData['modes']); - if ($bModeCLI) { - if (in_array('cli', $aModes)) { - $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); - $oP->p("$sParam = $sDesc"); - } - } else { - if (in_array('http', $aModes)) { - $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); - $oP->p("$sParam = $sDesc"); - } - } - } - $oP->output(); - exit; + global $aPageParams; + $bModeCLI = utils::IsModeCLI(); + + $oP->p("USAGE:\n"); + foreach ($aPageParams as $sParam => $aParamData) { + $aModes = explode(',', $aParamData['modes']); + if ($bModeCLI) { + if (in_array('cli', $aModes)) { + $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); + $oP->p("$sParam = $sDesc"); + } + } else { + if (in_array('http', $aModes)) { + $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); + $oP->p("$sParam = $sDesc"); + } + } + } + $oP->output(); + exit; } function ReadParam($oP, $sParam, $sSanitizationFilter = 'parameter') { - global $aPageParams; - assert(isset($aPageParams[$sParam])); - assert(!$aPageParams[$sParam]['mandatory']); - $sValue = utils::ReadParam($sParam, $aPageParams[$sParam]['default'], true /* Allow CLI */, $sSanitizationFilter); - return trim($sValue); + global $aPageParams; + assert(isset($aPageParams[$sParam])); + assert(!$aPageParams[$sParam]['mandatory']); + $sValue = utils::ReadParam($sParam, $aPageParams[$sParam]['default'], true /* Allow CLI */, $sSanitizationFilter); + return trim($sValue); } function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) { - global $aPageParams; - assert(isset($aPageParams[$sParam])); - assert($aPageParams[$sParam]['mandatory']); - - $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); - if (is_null($sValue)) { - $oP->p("ERROR: Missing argument '$sParam'\n"); - UsageAndExit($oP); - } - return trim($sValue); + global $aPageParams; + assert(isset($aPageParams[$sParam])); + assert($aPageParams[$sParam]['mandatory']); + + $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); + if (is_null($sValue)) { + $oP->p("ERROR: Missing argument '$sParam'\n"); + UsageAndExit($oP); + } + return trim($sValue); } ///////////////////////////////// @@ -206,599 +206,599 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) */ $oCtx = new ContextTag(ContextTag::TAG_IMPORT); if (utils::IsModeCLI()) { - $oP = new CLIPage("iTop - Bulk import"); - SetupUtils::CheckPhpAndExtensionsForCli($oP, -2); + $oP = new CLIPage("iTop - Bulk import"); + SetupUtils::CheckPhpAndExtensionsForCli($oP, -2); } else { - $oP = new CSVPage("iTop - Bulk import"); + $oP = new CSVPage("iTop - Bulk import"); } try { - utils::UseParamFile(); + utils::UseParamFile(); } catch (Exception $e) { - $oP->p("Error: ".$e->GetMessage()); - $oP->output(); - exit(-2); + $oP->p("Error: ".$e->GetMessage()); + $oP->output(); + exit(-2); } if (utils::IsModeCLI()) { - // Next steps: - // specific arguments: 'csvfile' - // - $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); - $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); - $sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { - UserRights::Login($sAuthUser); // Login & set the user's language - } else { - $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); - $oP->output(); - exit(-1); - } - - if (!is_readable($sCsvFile)) { - $oP->p("Input file could not be found or could not be read: '$sCsvFile'"); - $oP->output(); - exit(-1); - } - $sCSVData = file_get_contents($sCsvFile); + // Next steps: + // specific arguments: 'csvfile' + // + $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); + $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); + $sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data'); + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); // Login & set the user's language + } else { + $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); + $oP->output(); + exit(-1); + } + + if (!is_readable($sCsvFile)) { + $oP->p("Input file could not be found or could not be read: '$sCsvFile'"); + $oP->output(); + exit(-1); + } + $sCSVData = file_get_contents($sCsvFile); } else { - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::ResetSession(true); - $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); - if ($iRet !== LoginWebPage::EXIT_CODE_OK) { - switch ($iRet) { - case LoginWebPage::EXIT_CODE_MISSINGLOGIN: - $oP->p("Missing parameter 'auth_user'"); - break; - - case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: - $oP->p("Missing parameter 'auth_pwd'"); - break; - - case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: - $oP->p('Invalid login'); - break; - - case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: - $oP->p('Portal user is not allowed'); - break; - - case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: - $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); - break; - - default: - $oP->p("Unknown authentication error (retCode=$iRet)"); - } - $oP->output(); - exit -1; - } - - $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::ResetSession(true); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + if ($iRet !== LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + $oP->p("Missing parameter 'auth_user'"); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + $oP->p("Missing parameter 'auth_pwd'"); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + $oP->p('Invalid login'); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + $oP->p('Portal user is not allowed'); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); + break; + + default: + $oP->p("Unknown authentication error (retCode=$iRet)"); + } + $oP->output(); + exit -1; + } + + $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); } try { - $aWarnings = []; - - ////////////////////////////////////////////////// - // - // Read parameters - // - $sClass = ReadMandatoryParam($oP, 'class', 'raw_data'); // do not filter as a valid class, we want to produce the report "wrong class" ourselves - $sSep = ReadParam($oP, 'separator', 'raw_data'); - $sQualifier = ReadParam($oP, 'qualifier', 'raw_data'); - $sCharSet = ReadParam($oP, 'charset', 'raw_data'); - $sDateFormat = ReadParam($oP, 'date_format', 'raw_data'); - if (strpos($sDateFormat, '%') !== false) { - $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat); - } - $sOutput = ReadParam($oP, 'output', 'string'); - $sReconcKeys = ReadParam($oP, 'reconciliationkeys', 'raw_data'); - $sSimulate = ReadParam($oP, 'simulate'); - $sComment = ReadParam($oP, 'comment', 'raw_data'); - $bLocalize = (ReadParam($oP, 'no_localize') != 1); - - if (strtolower(trim($sSep)) == 'tab') { - $sSep = "\t"; - } - - ////////////////////////////////////////////////// - // - // Check parameters format/consistency - // - if (strlen($sCSVData) == 0) { - throw new BulkLoadException("Missing data - at least one line is expected"); - } - - if (!MetaModel::IsValidClass($sClass)) { - throw new BulkLoadException("Unknown class: '$sClass'"); - } - - if (strlen($sSep) > 1) { - throw new BulkLoadException("Separator is limited to one character, found '$sSep'"); - } - - if (strlen($sQualifier) > 1) { - throw new BulkLoadException("Text qualifier is limited to one character, found '$sQualifier'"); - } - - if (!in_array($sOutput, ['retcode', 'summary', 'details'])) { - throw new BulkLoadException("Unknown output format: '$sOutput'"); - } - - if (strlen($sDateFormat) == 0) { - $sDateFormat = null; - } - - if ($sCharSet == '') { - $sCharSet = MetaModel::GetConfig()->Get('csv_file_default_charset'); - } - - if ($sSimulate == '1') { - $bSimulate = true; - } else { - $bSimulate = false; - } - - if (($sOutput == "summary") || ($sOutput == 'details')) { - $oP->add_comment("Output format: ".$sOutput); - $oP->add_comment("Class: ".$sClass); - $oP->add_comment("Separator: ".$sSep); - $oP->add_comment("Qualifier: ".$sQualifier); - $oP->add_comment("Charset Encoding:".$sCharSet); - if (($sDateFormat !== null) && (strlen($sDateFormat) > 0)) { - $oP->add_comment("Date and time format: '$sDateFormat'"); - $oDateTimeFormat = new DateTimeFormat($sDateFormat); - $sDateOnlyFormat = $oDateTimeFormat->ToDateFormat(); - $oP->add_comment("Date format: '$sDateOnlyFormat'"); - } else { - $oP->add_comment("Date format: "); - } - $oP->add_comment("Localize: ".($bLocalize ? 'yes' : 'no')); - $oP->add_comment("Data Size: ".strlen($sCSVData)); - } - ////////////////////////////////////////////////// - // - // Security - // - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY)) { - throw new SecurityException(Dict::Format('UI:Error:BulkModifyNotAllowedOn_Class', $sClass)); - } - - ////////////////////////////////////////////////// - // - // Create an index of the known column names (in lower case) - // If data is localized, an array of => array of (several leads to ambiguity) - // Otherwise an array of => array of (1 element by construction) - // - // Examples (localized in french): - // 'lieu' => 'location_id' - // 'lieu->name' => 'location_id->name' - // - // Note: it may happen that an external field has the same label as the external key - // in that case, we consider that the external key has precedence - // - $aKnownColumnNames = ['id' => ['id']]; - foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { - if ($bLocalize) { - $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCode)); - } else { - $sColName = strtolower($sAttCode); - } - if (!$oAttDef->IsExternalField() || !array_key_exists($sColName, $aKnownColumnNames)) { - $aKnownColumnNames[$sColName][] = $sAttCode; - } - if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { - $sRemoteClass = $oAttDef->GetTargetClass(); - foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) { - $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; - if ($bLocalize) { - $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCodeEx)); - } else { - $sColName = strtolower($sAttCodeEx); - } - if (!array_key_exists($sColName, $aKnownColumnNames)) { - $aKnownColumnNames[$sColName][] = $sAttCodeEx; - } - } - } - } - - //print_r($aKnownColumnNames); - //print_r(array_keys($aKnownColumnNames)); - //exit; - - ////////////////////////////////////////////////// - // - // Parse first line, check attributes, analyse the request - // - if ($sCharSet == 'UTF-8') { - // Remove the BOM if any - if (substr($sCSVData, 0, 3) == UTF8_BOM) { - $sCSVData = substr($sCSVData, 3); - } - // Clean the input - // Todo: warn the user if some characters are lost/substituted - $sUTF8Data = iconv('UTF-8', 'UTF-8//IGNORE//TRANSLIT', $sCSVData); - } else { - $sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); - } - $oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier); - - // Limitation: as the attribute list is in the first line, we can not match external key by a third-party attribute - $aRawFieldList = $oCSVParser->ListFields(); - $iColCount = count($aRawFieldList); - - // Translate into internal names - $aFieldList = []; - foreach ($aRawFieldList as $iFieldId => $sFieldName) { - $sFieldName = trim($sFieldName); - $aMatches = []; - if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches)) { - // Ignore any trailing "star" (*) that simply indicates a mandatory field - $sFieldName = $aMatches[1]; - } elseif (preg_match('/^(.+)\*->(.+)$/', $sFieldName, $aMatches)) { - // Remove any trailing "star" character before the arrow (->) - // A star character at the end can be used to indicate a mandatory field - $sFieldName = $aMatches[1].'->'.$aMatches[2]; - } - if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames)) { - $aColumns = $aKnownColumnNames[strtolower($sFieldName)]; - if (count($aColumns) > 1) { - $aCompetitors = []; - foreach ($aColumns as $sAttCodeEx) { - $aCompetitors[] = $sAttCodeEx; - } - $aWarnings[] = "Input column '$sFieldName' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; - } - $aFieldList[$iFieldId] = $aColumns[0]; - } else { - // Protect against XSS injection - $sSafeName = str_replace(['"', '<', '>'], '', $sFieldName); - throw new BulkLoadException("Unknown column: '$sSafeName'. Possible columns: ".implode(', ', array_keys($aKnownColumnNames))); - } - } - // Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->') - - $aAttList = []; - $aExtKeys = []; - foreach ($aFieldList as $iFieldId => $sFieldName) { - $aMatches = []; - if (preg_match('/^(.+)->(.+)$/', trim($sFieldName), $aMatches)) { - // The column has been specified as "extkey->attcode" - // - $sExtKeyAttCode = $aMatches[1]; - $sRemoteAttCode = $aMatches[2]; - if (!MetaModel::IsValidAttCode($sClass, $sExtKeyAttCode)) { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown attribute '$sExtKeyAttCode' (class: '$sClass')"); - } - $oAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); - if (!$oAtt->IsExternalKey()) { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Not an external key '$sExtKeyAttCode' (class: '$sClass')"); - } - $sTargetClass = $oAtt->GetTargetClass(); - if (!MetaModel::IsValidAttCode($sTargetClass, $sRemoteAttCode)) { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown attribute '$sRemoteAttCode' (key: '$sExtKeyAttCode', class: '$sTargetClass')"); - } - $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; - } elseif ($sFieldName == 'id') { - $aAttList[$sFieldName] = $iFieldId; - } else { - // The column has been specified as "attcode" - // - if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown attribute '$sFieldName' (class: '$sClass')"); - } - $oAtt = MetaModel::GetAttributeDef($sClass, $sFieldName); - if ($oAtt->IsExternalKey()) { - $aExtKeys[$sFieldName]['id'] = $iFieldId; - $aAttList[$sFieldName] = $iFieldId; - } elseif ($oAtt->IsExternalField()) { - $sExtKeyAttCode = $oAtt->GetKeyAttCode(); - $sRemoteAttCode = $oAtt->GetExtAttCode(); - $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; - } else { - $aAttList[$sFieldName] = $iFieldId; - } - } - } - - // Make sure there are some reconciliation keys - // - if (empty($sReconcKeys)) { - $aReconcSpec = []; - // Base reconciliation scheme on the default one - // The reconciliation attributes not present in the data will be ignored - foreach (MetaModel::GetReconcKeys($sClass) as $sReconcKeyAttCode) { - if (in_array($sReconcKeyAttCode, $aFieldList)) { - if ($bLocalize) { - $aReconcSpec[] = MetaModel::GetLabel($sClass, $sReconcKeyAttCode); - } else { - $aReconcSpec[] = $sReconcKeyAttCode; - } - } - } - if (count($aReconcSpec) == 0) { - throw new BulkLoadException("No reconciliation scheme could be defined, please add a column corresponding to one defined reconciliation key (class: '$sClass', reconciliation:".implode(',', MetaModel::GetReconcKeys($sClass)).")"); - } - $sReconcKeys = implode(',', $aReconcSpec); - } - - // Interpret the list of reconciliation keys - // - $aFinalReconcilKeys = []; - $aReconcilKeysReport = []; - foreach (explode(',', $sReconcKeys) as $sReconcKey) { - $sReconcKey = trim($sReconcKey); - if (empty($sReconcKey)) { - continue; - } // skip empty spec - - if (array_key_exists(strtolower($sReconcKey), $aKnownColumnNames)) { - // Translate from a translated name to codes - $aColumns = $aKnownColumnNames[strtolower($sReconcKey)]; - if (count($aColumns) > 1) { - $aCompetitors = []; - foreach ($aColumns as $sAttCodeEx) { - $aCompetitors[] = $sAttCodeEx; - } - $aWarnings[] = "Reconciliation key '$sReconcKey' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; - } - $sReconcKey = $aColumns[0]; - } else { - // Protect against XSS injection - $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); - throw new BulkLoadException("Unknown reconciliation key: '$sSafeName'"); - } - - // Check that the reconciliation key is either a given column, or an external key - if (!in_array($sReconcKey, $aFieldList)) { - if (!array_key_exists($sReconcKey, $aExtKeys)) { - // Protect against XSS injection - $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); - throw new BulkLoadException("Reconciliation key not found in the input columns: '$sSafeName'"); - } - } - - if (preg_match('/^(.+)->(.+)$/', trim($sReconcKey), $aMatches)) { - // The column has been specified as "extkey->attcode" - // - $sExtKeyAttCode = $aMatches[1]; - $sRemoteAttCode = $aMatches[2]; - - $aFinalReconcilKeys[] = $sExtKeyAttCode; - $aReconcilKeysReport[$sExtKeyAttCode][] = $sRemoteAttCode; - } else { - if (!MetaModel::IsValidAttCode($sClass, $sReconcKey) && $sReconcKey != 'id') { - // Safety net - should not happen now that column names are checked against known names - throw new BulkLoadException("Unknown reconciliation attribute '$sReconcKey' (class: '$sClass')"); - } - if ($sReconcKey == 'id') { - $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey] = []; - } else { - $oAtt = MetaModel::GetAttributeDef($sClass, $sReconcKey); - if ($oAtt->IsExternalKey()) { - $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey][] = 'id'; - } elseif ($oAtt->IsExternalField()) { - $sReconcAttCode = $oAtt->GetKeyAttCode(); - $sReconcKeyReport = "$sReconcAttCode ($sReconcKey)"; - - $aFinalReconcilKeys[] = $sReconcAttCode; - $aReconcilKeysReport[$sReconcAttCode][] = $sReconcKeyReport; - } else { - $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey] = []; - } - } - } - } - - ////////////////////////////////////////////////// - // - // Go for parsing and interpretation - // - - $aData = $oCSVParser->ToArray(); - $iLineCount = count($aData); - - if (($sOutput == "summary") || ($sOutput == 'details')) { - $oP->add_comment("Data Lines: ".$iLineCount); - $oP->add_comment("Simulate: ".($bSimulate ? '1' : '0')); - $oP->add_comment("Columns: ".implode(', ', $aFieldList)); - - $aReconciliationReport = []; - foreach ($aReconcilKeysReport as $sKey => $aKeyDetails) { - if (count($aKeyDetails) > 0) { - $aReconciliationReport[] = $sKey.' ('.implode(',', $aKeyDetails).')'; - } else { - $aReconciliationReport[] = $sKey; - } - } - $oP->add_comment("Reconciliation Keys: ".implode(', ', $aReconciliationReport)); - - foreach ($aWarnings as $sWarning) { - $oP->add_comment("Warning: ".$sWarning); - } - } - - $oBulk = new BulkChange( - $sClass, - $aData, - $aAttList, - $aExtKeys, - $aFinalReconcilKeys, - null, // synchro scope - null, // on delete - $sDateFormat, - $bLocalize - ); - - if ($bSimulate) { - $oMyChange = null; - } else { - if (strlen($sComment) > 0) { - $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; - } else { - $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV)'; - } - CMDBObject::SetCurrentChangeFromParams($sMoreInfo, 'csv-import.php'); - $oMyChange = CMDBObject::GetCurrentChange(); - } - - $aRes = $oBulk->Process($oMyChange); - - ////////////////////////////////////////////////// - // - // Compute statistics - // - $iCountErrors = 0; - $iCountWarnings = 0; - $iCountCreations = 0; - $iCountUpdates = 0; - $iCountUnchanged = 0; - foreach ($aRes as $iRow => $aRowData) { - $bWritten = false; - - $oStatus = $aRowData["__STATUS__"]; - switch (get_class($oStatus)) { - case 'RowStatus_NoChange': - $iCountUnchanged++; - break; - case 'RowStatus_Modify': - $iCountUpdates++; - $bWritten = true; - break; - case 'RowStatus_NewObj': - $iCountCreations++; - $bWritten = true; - break; - case 'RowStatus_Issue': - $iCountErrors++; - break; - } - - if ($bWritten) { - // Something has been done, still there may be some issues to report - foreach ($aRowData as $key => $value) { - if (!is_object($value)) { - continue; - } - - switch (get_class($value)) { - case 'CellStatus_Void': - case 'CellStatus_Modify': - break; - case 'CellStatus_Issue': - case 'CellStatus_SearchIssue': - case 'CellStatus_NullIssue': - case 'CellStatus_Ambiguous': - $iCountWarnings++; - break; - } - } - } - } - - ////////////////////////////////////////////////// - // - // Summary of settings and results - // - if ($sOutput == 'retcode') { - $oP->add($iCountErrors); - } - - if (($sOutput == "summary") || ($sOutput == 'details')) { - $oP->add_comment("Change tracking comment: ".$sComment); - $oP->add_comment("Issues: ".$iCountErrors); - $oP->add_comment("Warnings: ".$iCountWarnings); - $oP->add_comment("Created: ".$iCountCreations); - $oP->add_comment("Updated: ".$iCountUpdates); - $oP->add_comment("Unchanged: ".$iCountUnchanged); - } - - if ($sOutput == 'details') { - // Setup result presentation - // - $aDisplayConfig = []; - $aDisplayConfig["__LINE__"] = ["label" => "Line", "description" => ""]; - $aDisplayConfig["__STATUS__"] = ["label" => "Status", "description" => ""]; - $aDisplayConfig["__OBJECT_CLASS__"] = ["label" => "Object Class", "description" => ""]; - $aDisplayConfig["__OBJECT_ID__"] = ["label" => "Object Id", "description" => ""]; - foreach ($aExtKeys as $sExtKeyAttCode => $aRemoteAtt) { - $sLabel = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode)->GetLabel(); - $aDisplayConfig["$sExtKeyAttCode"] = ["label" => $sExtKeyAttCode, "description" => $sLabel." - ext key"]; - } - foreach ($aFinalReconcilKeys as $iCol => $sAttCode) { - // $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - // $aDisplayConfig["$iCol"] = array("label"=>"$sLabel", "description"=>""); - } - foreach ($aAttList as $sAttCode => $iCol) { - if ($sAttCode == 'id') { - $sLabel = Dict::S('UI:CSVImport:idField'); - - $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; - } else { - $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; - } - } - - $aResultDisp = []; // to be displayed - foreach ($aRes as $iRow => $aRowData) { - $aRowDisp = []; - $aRowDisp["__LINE__"] = $iRow; - if (is_object($aRowData["__STATUS__"])) { - $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); - } else { - $aRowDisp["__STATUS__"] = "*No status available*"; - } - if (isset($aRowData["finalclass"]) && isset($aRowData["id"])) { - $aRowDisp["__OBJECT_CLASS__"] = $aRowData["finalclass"]; - $aRowDisp["__OBJECT_ID__"] = $aRowData["id"]->GetCLIValue(); - } else { - $aRowDisp["__OBJECT_CLASS__"] = "n/a"; - $aRowDisp["__OBJECT_ID__"] = "n/a"; - } - foreach ($aRowData as $key => $value) { - $sKey = (string) $key; - - if ($sKey == '__STATUS__') { - continue; - } - //__ERRORS__ used by tests only - if ($sKey == '__ERRORS__') { - continue; - } - if ($sKey == 'finalclass') { - continue; - } - if ($sKey == 'id') { - continue; - } - - if (is_object($value)) { - $aRowDisp["$sKey"] = $value->GetCLIValueAndDescription(); - } else { - $aRowDisp["$sKey"] = $value; - } - } - $aResultDisp[$iRow] = $aRowDisp; - } - $oP->table($aDisplayConfig, $aResultDisp); - } + $aWarnings = []; + + ////////////////////////////////////////////////// + // + // Read parameters + // + $sClass = ReadMandatoryParam($oP, 'class', 'raw_data'); // do not filter as a valid class, we want to produce the report "wrong class" ourselves + $sSep = ReadParam($oP, 'separator', 'raw_data'); + $sQualifier = ReadParam($oP, 'qualifier', 'raw_data'); + $sCharSet = ReadParam($oP, 'charset', 'raw_data'); + $sDateFormat = ReadParam($oP, 'date_format', 'raw_data'); + if (strpos($sDateFormat, '%') !== false) { + $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat); + } + $sOutput = ReadParam($oP, 'output', 'string'); + $sReconcKeys = ReadParam($oP, 'reconciliationkeys', 'raw_data'); + $sSimulate = ReadParam($oP, 'simulate'); + $sComment = ReadParam($oP, 'comment', 'raw_data'); + $bLocalize = (ReadParam($oP, 'no_localize') != 1); + + if (strtolower(trim($sSep)) == 'tab') { + $sSep = "\t"; + } + + ////////////////////////////////////////////////// + // + // Check parameters format/consistency + // + if (strlen($sCSVData) == 0) { + throw new BulkLoadException("Missing data - at least one line is expected"); + } + + if (!MetaModel::IsValidClass($sClass)) { + throw new BulkLoadException("Unknown class: '$sClass'"); + } + + if (strlen($sSep) > 1) { + throw new BulkLoadException("Separator is limited to one character, found '$sSep'"); + } + + if (strlen($sQualifier) > 1) { + throw new BulkLoadException("Text qualifier is limited to one character, found '$sQualifier'"); + } + + if (!in_array($sOutput, ['retcode', 'summary', 'details'])) { + throw new BulkLoadException("Unknown output format: '$sOutput'"); + } + + if (strlen($sDateFormat) == 0) { + $sDateFormat = null; + } + + if ($sCharSet == '') { + $sCharSet = MetaModel::GetConfig()->Get('csv_file_default_charset'); + } + + if ($sSimulate == '1') { + $bSimulate = true; + } else { + $bSimulate = false; + } + + if (($sOutput == "summary") || ($sOutput == 'details')) { + $oP->add_comment("Output format: ".$sOutput); + $oP->add_comment("Class: ".$sClass); + $oP->add_comment("Separator: ".$sSep); + $oP->add_comment("Qualifier: ".$sQualifier); + $oP->add_comment("Charset Encoding:".$sCharSet); + if (($sDateFormat !== null) && (strlen($sDateFormat) > 0)) { + $oP->add_comment("Date and time format: '$sDateFormat'"); + $oDateTimeFormat = new DateTimeFormat($sDateFormat); + $sDateOnlyFormat = $oDateTimeFormat->ToDateFormat(); + $oP->add_comment("Date format: '$sDateOnlyFormat'"); + } else { + $oP->add_comment("Date format: "); + } + $oP->add_comment("Localize: ".($bLocalize ? 'yes' : 'no')); + $oP->add_comment("Data Size: ".strlen($sCSVData)); + } + ////////////////////////////////////////////////// + // + // Security + // + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY)) { + throw new SecurityException(Dict::Format('UI:Error:BulkModifyNotAllowedOn_Class', $sClass)); + } + + ////////////////////////////////////////////////// + // + // Create an index of the known column names (in lower case) + // If data is localized, an array of => array of (several leads to ambiguity) + // Otherwise an array of => array of (1 element by construction) + // + // Examples (localized in french): + // 'lieu' => 'location_id' + // 'lieu->name' => 'location_id->name' + // + // Note: it may happen that an external field has the same label as the external key + // in that case, we consider that the external key has precedence + // + $aKnownColumnNames = ['id' => ['id']]; + foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { + if ($bLocalize) { + $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCode)); + } else { + $sColName = strtolower($sAttCode); + } + if (!$oAttDef->IsExternalField() || !array_key_exists($sColName, $aKnownColumnNames)) { + $aKnownColumnNames[$sColName][] = $sAttCode; + } + if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { + $sRemoteClass = $oAttDef->GetTargetClass(); + foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) { + $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; + if ($bLocalize) { + $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCodeEx)); + } else { + $sColName = strtolower($sAttCodeEx); + } + if (!array_key_exists($sColName, $aKnownColumnNames)) { + $aKnownColumnNames[$sColName][] = $sAttCodeEx; + } + } + } + } + + //print_r($aKnownColumnNames); + //print_r(array_keys($aKnownColumnNames)); + //exit; + + ////////////////////////////////////////////////// + // + // Parse first line, check attributes, analyse the request + // + if ($sCharSet == 'UTF-8') { + // Remove the BOM if any + if (substr($sCSVData, 0, 3) == UTF8_BOM) { + $sCSVData = substr($sCSVData, 3); + } + // Clean the input + // Todo: warn the user if some characters are lost/substituted + $sUTF8Data = iconv('UTF-8', 'UTF-8//IGNORE//TRANSLIT', $sCSVData); + } else { + $sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); + } + $oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier); + + // Limitation: as the attribute list is in the first line, we can not match external key by a third-party attribute + $aRawFieldList = $oCSVParser->ListFields(); + $iColCount = count($aRawFieldList); + + // Translate into internal names + $aFieldList = []; + foreach ($aRawFieldList as $iFieldId => $sFieldName) { + $sFieldName = trim($sFieldName); + $aMatches = []; + if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches)) { + // Ignore any trailing "star" (*) that simply indicates a mandatory field + $sFieldName = $aMatches[1]; + } elseif (preg_match('/^(.+)\*->(.+)$/', $sFieldName, $aMatches)) { + // Remove any trailing "star" character before the arrow (->) + // A star character at the end can be used to indicate a mandatory field + $sFieldName = $aMatches[1].'->'.$aMatches[2]; + } + if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames)) { + $aColumns = $aKnownColumnNames[strtolower($sFieldName)]; + if (count($aColumns) > 1) { + $aCompetitors = []; + foreach ($aColumns as $sAttCodeEx) { + $aCompetitors[] = $sAttCodeEx; + } + $aWarnings[] = "Input column '$sFieldName' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; + } + $aFieldList[$iFieldId] = $aColumns[0]; + } else { + // Protect against XSS injection + $sSafeName = str_replace(['"', '<', '>'], '', $sFieldName); + throw new BulkLoadException("Unknown column: '$sSafeName'. Possible columns: ".implode(', ', array_keys($aKnownColumnNames))); + } + } + // Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->') + + $aAttList = []; + $aExtKeys = []; + foreach ($aFieldList as $iFieldId => $sFieldName) { + $aMatches = []; + if (preg_match('/^(.+)->(.+)$/', trim($sFieldName), $aMatches)) { + // The column has been specified as "extkey->attcode" + // + $sExtKeyAttCode = $aMatches[1]; + $sRemoteAttCode = $aMatches[2]; + if (!MetaModel::IsValidAttCode($sClass, $sExtKeyAttCode)) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown attribute '$sExtKeyAttCode' (class: '$sClass')"); + } + $oAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); + if (!$oAtt->IsExternalKey()) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Not an external key '$sExtKeyAttCode' (class: '$sClass')"); + } + $sTargetClass = $oAtt->GetTargetClass(); + if (!MetaModel::IsValidAttCode($sTargetClass, $sRemoteAttCode)) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown attribute '$sRemoteAttCode' (key: '$sExtKeyAttCode', class: '$sTargetClass')"); + } + $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; + } elseif ($sFieldName == 'id') { + $aAttList[$sFieldName] = $iFieldId; + } else { + // The column has been specified as "attcode" + // + if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown attribute '$sFieldName' (class: '$sClass')"); + } + $oAtt = MetaModel::GetAttributeDef($sClass, $sFieldName); + if ($oAtt->IsExternalKey()) { + $aExtKeys[$sFieldName]['id'] = $iFieldId; + $aAttList[$sFieldName] = $iFieldId; + } elseif ($oAtt->IsExternalField()) { + $sExtKeyAttCode = $oAtt->GetKeyAttCode(); + $sRemoteAttCode = $oAtt->GetExtAttCode(); + $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; + } else { + $aAttList[$sFieldName] = $iFieldId; + } + } + } + + // Make sure there are some reconciliation keys + // + if (empty($sReconcKeys)) { + $aReconcSpec = []; + // Base reconciliation scheme on the default one + // The reconciliation attributes not present in the data will be ignored + foreach (MetaModel::GetReconcKeys($sClass) as $sReconcKeyAttCode) { + if (in_array($sReconcKeyAttCode, $aFieldList)) { + if ($bLocalize) { + $aReconcSpec[] = MetaModel::GetLabel($sClass, $sReconcKeyAttCode); + } else { + $aReconcSpec[] = $sReconcKeyAttCode; + } + } + } + if (count($aReconcSpec) == 0) { + throw new BulkLoadException("No reconciliation scheme could be defined, please add a column corresponding to one defined reconciliation key (class: '$sClass', reconciliation:".implode(',', MetaModel::GetReconcKeys($sClass)).")"); + } + $sReconcKeys = implode(',', $aReconcSpec); + } + + // Interpret the list of reconciliation keys + // + $aFinalReconcilKeys = []; + $aReconcilKeysReport = []; + foreach (explode(',', $sReconcKeys) as $sReconcKey) { + $sReconcKey = trim($sReconcKey); + if (empty($sReconcKey)) { + continue; + } // skip empty spec + + if (array_key_exists(strtolower($sReconcKey), $aKnownColumnNames)) { + // Translate from a translated name to codes + $aColumns = $aKnownColumnNames[strtolower($sReconcKey)]; + if (count($aColumns) > 1) { + $aCompetitors = []; + foreach ($aColumns as $sAttCodeEx) { + $aCompetitors[] = $sAttCodeEx; + } + $aWarnings[] = "Reconciliation key '$sReconcKey' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; + } + $sReconcKey = $aColumns[0]; + } else { + // Protect against XSS injection + $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); + throw new BulkLoadException("Unknown reconciliation key: '$sSafeName'"); + } + + // Check that the reconciliation key is either a given column, or an external key + if (!in_array($sReconcKey, $aFieldList)) { + if (!array_key_exists($sReconcKey, $aExtKeys)) { + // Protect against XSS injection + $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); + throw new BulkLoadException("Reconciliation key not found in the input columns: '$sSafeName'"); + } + } + + if (preg_match('/^(.+)->(.+)$/', trim($sReconcKey), $aMatches)) { + // The column has been specified as "extkey->attcode" + // + $sExtKeyAttCode = $aMatches[1]; + $sRemoteAttCode = $aMatches[2]; + + $aFinalReconcilKeys[] = $sExtKeyAttCode; + $aReconcilKeysReport[$sExtKeyAttCode][] = $sRemoteAttCode; + } else { + if (!MetaModel::IsValidAttCode($sClass, $sReconcKey) && $sReconcKey != 'id') { + // Safety net - should not happen now that column names are checked against known names + throw new BulkLoadException("Unknown reconciliation attribute '$sReconcKey' (class: '$sClass')"); + } + if ($sReconcKey == 'id') { + $aFinalReconcilKeys[] = $sReconcKey; + $aReconcilKeysReport[$sReconcKey] = []; + } else { + $oAtt = MetaModel::GetAttributeDef($sClass, $sReconcKey); + if ($oAtt->IsExternalKey()) { + $aFinalReconcilKeys[] = $sReconcKey; + $aReconcilKeysReport[$sReconcKey][] = 'id'; + } elseif ($oAtt->IsExternalField()) { + $sReconcAttCode = $oAtt->GetKeyAttCode(); + $sReconcKeyReport = "$sReconcAttCode ($sReconcKey)"; + + $aFinalReconcilKeys[] = $sReconcAttCode; + $aReconcilKeysReport[$sReconcAttCode][] = $sReconcKeyReport; + } else { + $aFinalReconcilKeys[] = $sReconcKey; + $aReconcilKeysReport[$sReconcKey] = []; + } + } + } + } + + ////////////////////////////////////////////////// + // + // Go for parsing and interpretation + // + + $aData = $oCSVParser->ToArray(); + $iLineCount = count($aData); + + if (($sOutput == "summary") || ($sOutput == 'details')) { + $oP->add_comment("Data Lines: ".$iLineCount); + $oP->add_comment("Simulate: ".($bSimulate ? '1' : '0')); + $oP->add_comment("Columns: ".implode(', ', $aFieldList)); + + $aReconciliationReport = []; + foreach ($aReconcilKeysReport as $sKey => $aKeyDetails) { + if (count($aKeyDetails) > 0) { + $aReconciliationReport[] = $sKey.' ('.implode(',', $aKeyDetails).')'; + } else { + $aReconciliationReport[] = $sKey; + } + } + $oP->add_comment("Reconciliation Keys: ".implode(', ', $aReconciliationReport)); + + foreach ($aWarnings as $sWarning) { + $oP->add_comment("Warning: ".$sWarning); + } + } + + $oBulk = new BulkChange( + $sClass, + $aData, + $aAttList, + $aExtKeys, + $aFinalReconcilKeys, + null, // synchro scope + null, // on delete + $sDateFormat, + $bLocalize + ); + + if ($bSimulate) { + $oMyChange = null; + } else { + if (strlen($sComment) > 0) { + $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; + } else { + $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV)'; + } + CMDBObject::SetCurrentChangeFromParams($sMoreInfo, 'csv-import.php'); + $oMyChange = CMDBObject::GetCurrentChange(); + } + + $aRes = $oBulk->Process($oMyChange); + + ////////////////////////////////////////////////// + // + // Compute statistics + // + $iCountErrors = 0; + $iCountWarnings = 0; + $iCountCreations = 0; + $iCountUpdates = 0; + $iCountUnchanged = 0; + foreach ($aRes as $iRow => $aRowData) { + $bWritten = false; + + $oStatus = $aRowData["__STATUS__"]; + switch (get_class($oStatus)) { + case 'RowStatus_NoChange': + $iCountUnchanged++; + break; + case 'RowStatus_Modify': + $iCountUpdates++; + $bWritten = true; + break; + case 'RowStatus_NewObj': + $iCountCreations++; + $bWritten = true; + break; + case 'RowStatus_Issue': + $iCountErrors++; + break; + } + + if ($bWritten) { + // Something has been done, still there may be some issues to report + foreach ($aRowData as $key => $value) { + if (!is_object($value)) { + continue; + } + + switch (get_class($value)) { + case 'CellStatus_Void': + case 'CellStatus_Modify': + break; + case 'CellStatus_Issue': + case 'CellStatus_SearchIssue': + case 'CellStatus_NullIssue': + case 'CellStatus_Ambiguous': + $iCountWarnings++; + break; + } + } + } + } + + ////////////////////////////////////////////////// + // + // Summary of settings and results + // + if ($sOutput == 'retcode') { + $oP->add($iCountErrors); + } + + if (($sOutput == "summary") || ($sOutput == 'details')) { + $oP->add_comment("Change tracking comment: ".$sComment); + $oP->add_comment("Issues: ".$iCountErrors); + $oP->add_comment("Warnings: ".$iCountWarnings); + $oP->add_comment("Created: ".$iCountCreations); + $oP->add_comment("Updated: ".$iCountUpdates); + $oP->add_comment("Unchanged: ".$iCountUnchanged); + } + + if ($sOutput == 'details') { + // Setup result presentation + // + $aDisplayConfig = []; + $aDisplayConfig["__LINE__"] = ["label" => "Line", "description" => ""]; + $aDisplayConfig["__STATUS__"] = ["label" => "Status", "description" => ""]; + $aDisplayConfig["__OBJECT_CLASS__"] = ["label" => "Object Class", "description" => ""]; + $aDisplayConfig["__OBJECT_ID__"] = ["label" => "Object Id", "description" => ""]; + foreach ($aExtKeys as $sExtKeyAttCode => $aRemoteAtt) { + $sLabel = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode)->GetLabel(); + $aDisplayConfig["$sExtKeyAttCode"] = ["label" => $sExtKeyAttCode, "description" => $sLabel." - ext key"]; + } + foreach ($aFinalReconcilKeys as $iCol => $sAttCode) { + // $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + // $aDisplayConfig["$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + foreach ($aAttList as $sAttCode => $iCol) { + if ($sAttCode == 'id') { + $sLabel = Dict::S('UI:CSVImport:idField'); + + $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; + } else { + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; + } + } + + $aResultDisp = []; // to be displayed + foreach ($aRes as $iRow => $aRowData) { + $aRowDisp = []; + $aRowDisp["__LINE__"] = $iRow; + if (is_object($aRowData["__STATUS__"])) { + $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); + } else { + $aRowDisp["__STATUS__"] = "*No status available*"; + } + if (isset($aRowData["finalclass"]) && isset($aRowData["id"])) { + $aRowDisp["__OBJECT_CLASS__"] = $aRowData["finalclass"]; + $aRowDisp["__OBJECT_ID__"] = $aRowData["id"]->GetCLIValue(); + } else { + $aRowDisp["__OBJECT_CLASS__"] = "n/a"; + $aRowDisp["__OBJECT_ID__"] = "n/a"; + } + foreach ($aRowData as $key => $value) { + $sKey = (string) $key; + + if ($sKey == '__STATUS__') { + continue; + } + //__ERRORS__ used by tests only + if ($sKey == '__ERRORS__') { + continue; + } + if ($sKey == 'finalclass') { + continue; + } + if ($sKey == 'id') { + continue; + } + + if (is_object($value)) { + $aRowDisp["$sKey"] = $value->GetCLIValueAndDescription(); + } else { + $aRowDisp["$sKey"] = $value; + } + } + $aResultDisp[$iRow] = $aRowDisp; + } + $oP->table($aDisplayConfig, $aResultDisp); + } } catch (BulkLoadException $e) { - $oP->add_comment($e->getMessage()); + $oP->add_comment($e->getMessage()); } catch (SecurityException $e) { - $oP->add_comment($e->getMessage()); + $oP->add_comment($e->getMessage()); } catch (Exception $e) { - $oP->add_comment((string)$e); + $oP->add_comment((string)$e); } $oP->output(); diff --git a/webservices/itop.wsdl.php b/webservices/itop.wsdl.php index d818859889..03aaed90d7 100644 --- a/webservices/itop.wsdl.php +++ b/webservices/itop.wsdl.php @@ -19,17 +19,17 @@ */ if (isset($_REQUEST['debug'])) { - if ($_REQUEST['debug'] == 'text') { - header('Content-Type: text/plain; charset=UTF-8'); - } else { - header('Content-Type: application/xml; charset=UTF-8'); - } + if ($_REQUEST['debug'] == 'text') { + header('Content-Type: text/plain; charset=UTF-8'); + } else { + header('Content-Type: application/xml; charset=UTF-8'); + } } else { - // This is to make sure that the client will accept it.... - // - header('Content-Type: application/xml; charset=UTF-8'); - ////header('Content-Disposition: attachment; filename="itop.wsdl"'); - header('Content-Disposition: online; filename="itop.wsdl"'); + // This is to make sure that the client will accept it.... + // + header('Content-Type: application/xml; charset=UTF-8'); + ////header('Content-Disposition: attachment; filename="itop.wsdl"'); + header('Content-Disposition: online; filename="itop.wsdl"'); } require_once('../approot.inc.php'); @@ -45,20 +45,20 @@ $sServiceCategory = utils::ReadParam('service_category'); if (!empty($sServiceCategory)) { - $sRawFile = WebServicesBase::GetWSDLContents($sServiceCategory); + $sRawFile = WebServicesBase::GetWSDLContents($sServiceCategory); } else { - $sRawFile = WebServicesBase::GetWSDLContents(); + $sRawFile = WebServicesBase::GetWSDLContents(); } $sServerURI = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php'; if (!empty($sServiceCategory)) { - $sServerURI .= "?service_category=".$sServiceCategory; + $sServerURI .= "?service_category=".$sServiceCategory; } $sFinalFile = str_replace( - '___SOAP_SERVER_URI___', - $sServerURI, - $sRawFile + '___SOAP_SERVER_URI___', + $sServerURI, + $sRawFile ); echo $sFinalFile; diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index 58d02c63d1..4d07389c19 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -38,99 +38,99 @@ */ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = []) { - // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. + // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. - if (function_exists('curl_init')) { - // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options - // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 - // by setting the SSLVERSION to 3 as done below. - $aHTTPHeaders = []; - if ($sOptionnalHeaders !== null) { - $aHeaders = explode("\n", $sOptionnalHeaders); - // N°3267 - Webservices: Fix optional headers not being taken into account - // See https://www.php.net/curl_setopt CURLOPT_HTTPHEADER - $aHTTPHeaders = []; - foreach ($aHeaders as $sHeaderString) { - $aHTTPHeaders[] = trim($sHeaderString); - } - } - // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions - $aOptions = [ - CURLOPT_RETURNTRANSFER => true, // return the content of the request - CURLOPT_HEADER => false, // don't return the headers in the output - CURLOPT_FOLLOWLOCATION => true, // follow redirects - CURLOPT_ENCODING => "", // handle all encodings - CURLOPT_USERAGENT => "spider", // who am i - CURLOPT_AUTOREFERER => true, // set referer on redirect - CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect - CURLOPT_TIMEOUT => 120, // timeout on response - CURLOPT_MAXREDIRS => 10, // stop after 10 redirects - CURLOPT_SSL_VERIFYHOST => 0, // Disabled SSL Cert checks - CURLOPT_SSL_VERIFYPEER => 0, // Disabled SSL Cert checks - // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why - // but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 - // CURLOPT_SSLVERSION => 3, - CURLOPT_POST => count($aData), - CURLOPT_POSTFIELDS => http_build_query($aData), - CURLOPT_HTTPHEADER => $aHTTPHeaders, - ]; - $aAllOptions = $aCurlOptions + $aOptions; - $ch = curl_init($sUrl); - curl_setopt_array($ch, $aAllOptions); - $response = curl_exec($ch); - $iErr = curl_errno($ch); - $sErrMsg = curl_error($ch); - if ($iErr !== 0) { - throw new Exception("Problem opening URL: $sUrl, $sErrMsg"); - } - if (is_array($aResponseHeaders)) { - $aHeaders = curl_getinfo($ch); - foreach ($aHeaders as $sCode => $sValue) { - $sName = str_replace(' ', '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" - $aResponseHeaders[$sName] = $sValue; - } - } - curl_close($ch); - } else { - // cURL is not available let's try with streams and fopen... + if (function_exists('curl_init')) { + // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options + // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 + // by setting the SSLVERSION to 3 as done below. + $aHTTPHeaders = []; + if ($sOptionnalHeaders !== null) { + $aHeaders = explode("\n", $sOptionnalHeaders); + // N°3267 - Webservices: Fix optional headers not being taken into account + // See https://www.php.net/curl_setopt CURLOPT_HTTPHEADER + $aHTTPHeaders = []; + foreach ($aHeaders as $sHeaderString) { + $aHTTPHeaders[] = trim($sHeaderString); + } + } + // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions + $aOptions = [ + CURLOPT_RETURNTRANSFER => true, // return the content of the request + CURLOPT_HEADER => false, // don't return the headers in the output + CURLOPT_FOLLOWLOCATION => true, // follow redirects + CURLOPT_ENCODING => "", // handle all encodings + CURLOPT_USERAGENT => "spider", // who am i + CURLOPT_AUTOREFERER => true, // set referer on redirect + CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect + CURLOPT_TIMEOUT => 120, // timeout on response + CURLOPT_MAXREDIRS => 10, // stop after 10 redirects + CURLOPT_SSL_VERIFYHOST => 0, // Disabled SSL Cert checks + CURLOPT_SSL_VERIFYPEER => 0, // Disabled SSL Cert checks + // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why + // but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 + // CURLOPT_SSLVERSION => 3, + CURLOPT_POST => count($aData), + CURLOPT_POSTFIELDS => http_build_query($aData), + CURLOPT_HTTPHEADER => $aHTTPHeaders, + ]; + $aAllOptions = $aCurlOptions + $aOptions; + $ch = curl_init($sUrl); + curl_setopt_array($ch, $aAllOptions); + $response = curl_exec($ch); + $iErr = curl_errno($ch); + $sErrMsg = curl_error($ch); + if ($iErr !== 0) { + throw new Exception("Problem opening URL: $sUrl, $sErrMsg"); + } + if (is_array($aResponseHeaders)) { + $aHeaders = curl_getinfo($ch); + foreach ($aHeaders as $sCode => $sValue) { + $sName = str_replace(' ', '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" + $aResponseHeaders[$sName] = $sValue; + } + } + curl_close($ch); + } else { + // cURL is not available let's try with streams and fopen... - $sData = http_build_query($aData); - $aParams = ['http' => [ - 'method' => 'POST', - 'content' => $sData, - 'header' => "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", - ]]; - if ($sOptionnalHeaders !== null) { - $aParams['http']['header'] .= $sOptionnalHeaders; - } - $ctx = stream_context_create($aParams); + $sData = http_build_query($aData); + $aParams = ['http' => [ + 'method' => 'POST', + 'content' => $sData, + 'header' => "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", + ]]; + if ($sOptionnalHeaders !== null) { + $aParams['http']['header'] .= $sOptionnalHeaders; + } + $ctx = stream_context_create($aParams); - $fp = @fopen($sUrl, 'rb', false, $ctx); - if (!$fp) { - global $php_errormsg; - if (isset($php_errormsg)) { - throw new Exception("Wrong URL: $sUrl, $php_errormsg"); - } elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) { - throw new Exception("Cannot connect to $sUrl: missing module 'openssl'"); - } else { - throw new Exception("Wrong URL: $sUrl"); - } - } - $response = @stream_get_contents($fp); - if ($response === false) { - throw new Exception("Problem reading data from $sUrl, $php_errormsg"); - } - if (is_array($aResponseHeaders)) { - $aMeta = stream_get_meta_data($fp); - $aHeaders = $aMeta['wrapper_data']; - foreach ($aHeaders as $sHeaderString) { - if (preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) { - $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]); - } - } - } - } - return $response; + $fp = @fopen($sUrl, 'rb', false, $ctx); + if (!$fp) { + global $php_errormsg; + if (isset($php_errormsg)) { + throw new Exception("Wrong URL: $sUrl, $php_errormsg"); + } elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) { + throw new Exception("Cannot connect to $sUrl: missing module 'openssl'"); + } else { + throw new Exception("Wrong URL: $sUrl"); + } + } + $response = @stream_get_contents($fp); + if ($response === false) { + throw new Exception("Problem reading data from $sUrl, $php_errormsg"); + } + if (is_array($aResponseHeaders)) { + $aMeta = stream_get_meta_data($fp); + $aHeaders = $aMeta['wrapper_data']; + foreach ($aHeaders as $sHeaderString) { + if (preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) { + $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]); + } + } + } + } + return $response; } //////////////////////////////////////////////////////////////////////////////// @@ -143,195 +143,195 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead // $aOperations = [ - [ - 'operation' => 'list_operations', // operation code - ], - [ - 'operation' => 'core/create', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => [ - 'org_id' => "SELECT Organization WHERE name = 'Demo'", - 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], - 'title' => 'issue blah', - 'description' => 'something happened' - ], - ], - [ - 'operation' => 'core/update', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest WHERE id=1', - 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => [ - 'title' => 'Issue #'.rand(0, 100), - 'contacts_list' => [ - [ - 'role' => 'fireman #'.rand(0, 100), - 'contact_id' => ['finalclass' => 'Person', 'name' => 'monet', 'first_name' => 'claude'], - ], - ], - ], - ], - // Rewrite the full CaseLog on an existing UserRequest with id=1, setting date and user (optional) - [ - 'operation' => 'core/update', - 'comment' => 'Synchronization from Client A', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest WHERE id=1', - 'output_fields' => 'id, friendlyname, title', - 'fields' => [ - 'public_log' => [ - 'items' => [ - 0 => [ - 'date' => '2001-02-01 23:59:59', //Allow to set the date of a true event, an alarm for eg. - 'user_login' => 'Alarm monitoring', //Free text - 'user_id' => 0, //0 is required for the user_login to be taken into account - 'message' => 'This is 1st entry as an HTML formatted
text', - ], - 1 => [ - 'date' => '2001-02-02 00:00:00', //If ommitted set automatically. - 'user_login' => 'Alarm monitoring', //user=id=0 is missing so will be ignored - 'message' => 'Second entry in text format: + [ + 'operation' => 'list_operations', // operation code + ], + [ + 'operation' => 'core/create', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'org_id' => "SELECT Organization WHERE name = 'Demo'", + 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], + 'title' => 'issue blah', + 'description' => 'something happened' + ], + ], + [ + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE id=1', + 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'title' => 'Issue #'.rand(0, 100), + 'contacts_list' => [ + [ + 'role' => 'fireman #'.rand(0, 100), + 'contact_id' => ['finalclass' => 'Person', 'name' => 'monet', 'first_name' => 'claude'], + ], + ], + ], + ], + // Rewrite the full CaseLog on an existing UserRequest with id=1, setting date and user (optional) + [ + 'operation' => 'core/update', + 'comment' => 'Synchronization from Client A', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE id=1', + 'output_fields' => 'id, friendlyname, title', + 'fields' => [ + 'public_log' => [ + 'items' => [ + 0 => [ + 'date' => '2001-02-01 23:59:59', //Allow to set the date of a true event, an alarm for eg. + 'user_login' => 'Alarm monitoring', //Free text + 'user_id' => 0, //0 is required for the user_login to be taken into account + 'message' => 'This is 1st entry as an HTML formatted
text', + ], + 1 => [ + 'date' => '2001-02-02 00:00:00', //If ommitted set automatically. + 'user_login' => 'Alarm monitoring', //user=id=0 is missing so will be ignored + 'message' => 'Second entry in text format: with new line, but format not specified, so treated as HTML!, user_id=0 missing, so user_login ignored', - ], - ], - ], - ], - ], - // Add a Text entry in the HTML CaseLog of the UserRequest with id=1, setting date and user (optional) - [ - 'operation' => 'core/update', // operation code - 'comment' => 'Synchronization from Alarm Monitoring', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 1, // object id or OQL - 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) - // Example of adding an entry into a CaseLog on an existing UserRequest - 'fields' => [ - 'public_log' => [ - 'add_item' => [ - 'user_login' => 'New Entry', //Free text - 'user_id' => 0, //0 is required for the user_login to be taken into account - 'format' => 'text', //If ommitted, source is expected to be HTML - 'message' => 'This text is not HTML formatted with 3 lines: + ], + ], + ], + ], + ], + // Add a Text entry in the HTML CaseLog of the UserRequest with id=1, setting date and user (optional) + [ + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from Alarm Monitoring', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 1, // object id or OQL + 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) + // Example of adding an entry into a CaseLog on an existing UserRequest + 'fields' => [ + 'public_log' => [ + 'add_item' => [ + 'user_login' => 'New Entry', //Free text + 'user_id' => 0, //0 is required for the user_login to be taken into account + 'format' => 'text', //If ommitted, source is expected to be HTML + 'message' => 'This text is not HTML formatted with 3 lines: new line 3rd and last line', - ], - ], - ], - ], - [ - 'operation' => 'core/get', // operation code - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest', - 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) - ], - [ - 'operation' => 'core/delete', // operation code - 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 'SELECT UserRequest WHERE org_id = 2', - 'simulate' => true, - ], - [ - 'operation' => 'core/apply_stimulus', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'key' => 1, - 'stimulus' => 'ev_assign', - // Values to set - 'fields' => [ - 'team_id' => 15, // Helpdesk - 'agent_id' => 9 // Jules Verne - ], - 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) - ], - [ - 'operation' => 'core/get_related', // operation code - 'class' => 'Server', - 'key' => 'SELECT Server', - 'relation' => 'impacts', // relation code - 'depth' => 4, // max recursion depth - ], + ], + ], + ], + ], + [ + 'operation' => 'core/get', // operation code + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest', + 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) + ], + [ + 'operation' => 'core/delete', // operation code + 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 'SELECT UserRequest WHERE org_id = 2', + 'simulate' => true, + ], + [ + 'operation' => 'core/apply_stimulus', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'key' => 1, + 'stimulus' => 'ev_assign', + // Values to set + 'fields' => [ + 'team_id' => 15, // Helpdesk + 'agent_id' => 9 // Jules Verne + ], + 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) + ], + [ + 'operation' => 'core/get_related', // operation code + 'class' => 'Server', + 'key' => 'SELECT Server', + 'relation' => 'impacts', // relation code + 'depth' => 4, // max recursion depth + ], ]; $aOperations = [ - [ - 'operation' => 'core/create', // operation code - 'comment' => 'Automatic creation of attachment blah blah...', // comment recorded in the change tracking log - 'class' => 'Attachment', - 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => [ - 'item_class' => 'UserRequest', - 'item_id' => 1, - 'item_org_id' => 3, - 'contents' => [ - 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACmSURBVChTfZHRDYMwDESzQ2fqhHx3C3ao+MkW/WlnaFxfzk7sEnE6JHJ+NgaKZN2zLHVN2ssfkae0Da7FQ5PRk/ve4Hcx19Ie6CEGuh/6vMgNhwanHVUNbt73lUDbYJ+6pg8b3+m2RehsVPdMXyvQY+OVkB+Rrv64lUjb3nq+aCA6v4leRqtfaIgimr53atBy9PlfUhoh3fFCNDmErv9FWR6ylBL5AREbmHBnFj5lAAAAAElFTkSuQmCC', - 'filename' => 'myself.png', - 'mimetype' => 'image/png' - ], - ], - ], - [ - 'operation' => 'core/get', // operation code - 'class' => 'Attachment', - 'key' => 'SELECT Attachment', - 'output_fields' => '*', - ] + [ + 'operation' => 'core/create', // operation code + 'comment' => 'Automatic creation of attachment blah blah...', // comment recorded in the change tracking log + 'class' => 'Attachment', + 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'item_class' => 'UserRequest', + 'item_id' => 1, + 'item_org_id' => 3, + 'contents' => [ + 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACmSURBVChTfZHRDYMwDESzQ2fqhHx3C3ao+MkW/WlnaFxfzk7sEnE6JHJ+NgaKZN2zLHVN2ssfkae0Da7FQ5PRk/ve4Hcx19Ie6CEGuh/6vMgNhwanHVUNbt73lUDbYJ+6pg8b3+m2RehsVPdMXyvQY+OVkB+Rrv64lUjb3nq+aCA6v4leRqtfaIgimr53atBy9PlfUhoh3fFCNDmErv9FWR6ylBL5AREbmHBnFj5lAAAAAElFTkSuQmCC', + 'filename' => 'myself.png', + 'mimetype' => 'image/png' + ], + ], + ], + [ + 'operation' => 'core/get', // operation code + 'class' => 'Attachment', + 'key' => 'SELECT Attachment', + 'output_fields' => '*', + ] ]; $aOperations = [ - [ - 'operation' => 'core/update', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'Server', - 'key' => 'SELECT Server WHERE name="Server1"', - 'output_fields' => 'id, friendlyname, description', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => [ - 'description' => 'Issue #'.time(), - ], - ], + [ + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'Server', + 'key' => 'SELECT Server WHERE name="Server1"', + 'output_fields' => 'id, friendlyname, description', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'description' => 'Issue #'.time(), + ], + ], ]; $aOperations = [ - [ - 'operation' => 'core/create', // operation code - 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log - 'class' => 'UserRequest', - 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) - // Values for the object to create - 'fields' => [ - 'org_id' => "SELECT Organization WHERE name = 'Demo'", - 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], - 'title' => 'issue blah', - 'description' => 'something happened' - ], - ], + [ + 'operation' => 'core/create', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'UserRequest', + 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => [ + 'org_id' => "SELECT Organization WHERE name = 'Demo'", + 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], + 'title' => 'issue blah', + 'description' => 'something happened' + ], + ], ]; $aXXXOperations = [ - [ - 'operation' => 'core/check_credentials', // operation code - 'user' => 'admin', - 'password' => 'admin', - ], + [ + 'operation' => 'core/check_credentials', // operation code + 'user' => 'admin', + 'password' => 'admin', + ], ]; $aDeleteOperations = [ - [ - 'operation' => 'core/delete', // operation code - 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log - 'class' => 'Server', - 'key' => 'SELECT Server', - 'simulate' => false, - ], + [ + 'operation' => 'core/delete', // operation code + 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log + 'class' => 'Server', + 'key' => 'SELECT Server', + 'simulate' => false, + ], ]; if (false) { - echo "Please edit the sample script and configure the server URL"; - exit; + echo "Please edit the sample script and configure the server URL"; + exit; } else { - $sUrl = "https://localhost/itop/webservices/rest.php?version=1.3"; + $sUrl = "https://localhost/itop/webservices/rest.php?version=1.3"; } $aData = []; @@ -339,26 +339,26 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead $aData['auth_pwd'] = 'rest'; foreach ($aOperations as $iOp => $aOperation) { - echo "======================================\n"; - echo "Operation #$iOp: ".$aOperation['operation']."\n"; - $aData['json_data'] = json_encode($aOperation); + echo "======================================\n"; + echo "Operation #$iOp: ".$aOperation['operation']."\n"; + $aData['json_data'] = json_encode($aOperation); - echo "--------------------------------------\n"; - echo "Input:\n"; - print_r($aOperation); - $aResults = null; - try { - $response = DoPostRequest($sUrl, $aData); - $aResults = json_decode($response); - } catch (Exception $e) { - $response = $e->getMessage(); - } - if ($aResults) { - echo "--------------------------------------\n"; - echo "Reply:\n"; - print_r($aResults); - } else { - echo "ERROR rest.php replied:\n"; - echo $response; - } + echo "--------------------------------------\n"; + echo "Input:\n"; + print_r($aOperation); + $aResults = null; + try { + $response = DoPostRequest($sUrl, $aData); + $aResults = json_decode($response); + } catch (Exception $e) { + $response = $e->getMessage(); + } + if ($aResults) { + echo "--------------------------------------\n"; + echo "Reply:\n"; + print_r($aResults); + } else { + echo "ERROR rest.php replied:\n"; + echo $response; + } } diff --git a/webservices/itopsoap.examples.php b/webservices/itopsoap.examples.php index b869dde628..6427a02843 100644 --- a/webservices/itopsoap.examples.php +++ b/webservices/itopsoap.examples.php @@ -33,100 +33,100 @@ ini_set("soap.wsdl_cache_enabled", "0"); $oSoapClient = new SoapClient( - $sWsdlUri, - [ - 'trace' => 1, - 'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php - ] + $sWsdlUri, + [ + 'trace' => 1, + 'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php + ] ); try { - // The most simple service, returning a string - // - $sServerVersion = $oSoapClient->GetVersion(); - echo "

GetVersion() returned $sServerVersion

"; + // The most simple service, returning a string + // + $sServerVersion = $oSoapClient->GetVersion(); + echo "

GetVersion() returned $sServerVersion

"; - // More complex ones, returning a SOAPResult structure - // (run the page to know more about the returned data) - // - $oRes = $oSoapClient->CreateIncidentTicket( - 'admin', /* login */ - 'admin', /* password */ - 'Email server down', /* title */ - 'HW found shutdown', /* description */ - null, /* caller */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* customer */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW Management')]), /* service */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Troubleshooting')]), /* service subcategory */ - '', /* product */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW support')]), /* workgroup */ - [ - new SOAPLinkCreationSpec( - 'Device', - [new SOAPSearchCondition('name', 'switch01')], - [] - ), - new SOAPLinkCreationSpec( - 'Server', - [new SOAPSearchCondition('name', 'dbserver1.demo.com')], - [] - ), - ], /* impacted cis */ - '1', /* impact */ - '1' /* urgency */ - ); + // More complex ones, returning a SOAPResult structure + // (run the page to know more about the returned data) + // + $oRes = $oSoapClient->CreateIncidentTicket( + 'admin', /* login */ + 'admin', /* password */ + 'Email server down', /* title */ + 'HW found shutdown', /* description */ + null, /* caller */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* customer */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW Management')]), /* service */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Troubleshooting')]), /* service subcategory */ + '', /* product */ + new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW support')]), /* workgroup */ + [ + new SOAPLinkCreationSpec( + 'Device', + [new SOAPSearchCondition('name', 'switch01')], + [] + ), + new SOAPLinkCreationSpec( + 'Server', + [new SOAPSearchCondition('name', 'dbserver1.demo.com')], + [] + ), + ], /* impacted cis */ + '1', /* impact */ + '1' /* urgency */ + ); - echo "

CreateIncidentTicket() returned:\n"; - echo "

\n";
-    print_r($oRes);
-    echo "
\n"; - echo "

\n"; + echo "

CreateIncidentTicket() returned:\n"; + echo "

\n";
+	print_r($oRes);
+	echo "
\n"; + echo "

\n"; - $oRes = $oSoapClient->SearchObjects( - 'admin', /* login */ - 'admin', /* password */ - 'SELECT URP_Profiles' /* oql */ - ); + $oRes = $oSoapClient->SearchObjects( + 'admin', /* login */ + 'admin', /* password */ + 'SELECT URP_Profiles' /* oql */ + ); - echo "

SearchObjects() returned:\n"; - if ($oRes->status) { - $aResults = $oRes->result; + echo "

SearchObjects() returned:\n"; + if ($oRes->status) { + $aResults = $oRes->result; - echo "\n"; + echo "
\n"; - // Header made after the first line - echo "\n"; - foreach ($aResults[0]->values as $aKeyValuePair) { - echo " \n"; - } - echo "\n"; + // Header made after the first line + echo "\n"; + foreach ($aResults[0]->values as $aKeyValuePair) { + echo " \n"; + } + echo "\n"; - foreach ($aResults as $iRow => $aData) { - echo "\n"; - foreach ($aData->values as $aKeyValuePair) { - echo " \n"; - } - echo "\n"; - } - echo "
".$aKeyValuePair->key."
".$aKeyValuePair->key."
".$aKeyValuePair->value."
\n"; - } else { - $aErrors = []; - foreach ($oRes->errors->messages as $oMessage) { - $aErrors[] = $oMessage->text; - } - $sErrorMsg = implode(', ', $aErrors); - echo "

SearchObjects() failed with message: $sErrorMsg

\n"; - //echo "
\n";
-        //print_r($oRes);
-        //echo "
\n"; - } - echo "

\n"; + foreach ($aResults as $iRow => $aData) { + echo "\n"; + foreach ($aData->values as $aKeyValuePair) { + echo " ".$aKeyValuePair->value."\n"; + } + echo "\n"; + } + echo "\n"; + } else { + $aErrors = []; + foreach ($oRes->errors->messages as $oMessage) { + $aErrors[] = $oMessage->text; + } + $sErrorMsg = implode(', ', $aErrors); + echo "

SearchObjects() failed with message: $sErrorMsg

\n"; + //echo "
\n";
+		//print_r($oRes);
+		//echo "
\n"; + } + echo "

\n"; } catch (SoapFault $e) { - echo "

SoapFault Exception: {$e->getMessage()}

\n"; - echo "

Request

\n"; - echo "
\n";
-    echo htmlspecialchars($oSoapClient->__getLastRequest())."\n";
-    echo "
"; - echo "

Response

"; - echo $oSoapClient->__getLastResponse()."\n"; + echo "

SoapFault Exception: {$e->getMessage()}

\n"; + echo "

Request

\n"; + echo "
\n";
+	echo htmlspecialchars($oSoapClient->__getLastRequest())."\n";
+	echo "
"; + echo "

Response

"; + echo $oSoapClient->__getLastResponse()."\n"; } diff --git a/webservices/itopsoaptypes.class.inc.php b/webservices/itopsoaptypes.class.inc.php index ce1ca86a59..679dc49961 100644 --- a/webservices/itopsoaptypes.class.inc.php +++ b/webservices/itopsoaptypes.class.inc.php @@ -29,153 +29,153 @@ class SOAPSearchCondition { - public $attcode; // string - public $value; // mixed - - public function __construct($sAttCode, $value) - { - $this->attcode = $sAttCode; - $this->value = $value; - } + public $attcode; // string + public $value; // mixed + + public function __construct($sAttCode, $value) + { + $this->attcode = $sAttCode; + $this->value = $value; + } } class SOAPExternalKeySearch { - public $conditions; // array of SOAPSearchCondition - - public function __construct($aConditions = null) - { - $this->conditions = $aConditions; - } - - public function IsVoid() - { - if (is_null($this->conditions)) { - return true; - } - if (count($this->conditions) == 0) { - return true; - } - } + public $conditions; // array of SOAPSearchCondition + + public function __construct($aConditions = null) + { + $this->conditions = $aConditions; + } + + public function IsVoid() + { + if (is_null($this->conditions)) { + return true; + } + if (count($this->conditions) == 0) { + return true; + } + } } class SOAPAttributeValue { - public $attcode; // string - public $value; // mixed - - public function __construct($sAttCode, $value) - { - $this->attcode = $sAttCode; - $this->value = $value; - } + public $attcode; // string + public $value; // mixed + + public function __construct($sAttCode, $value) + { + $this->attcode = $sAttCode; + $this->value = $value; + } } class SOAPLinkCreationSpec { - public $class; - public $conditions; // array of SOAPSearchCondition - public $attributes; // array of SOAPAttributeValue - - public function __construct($sClass, $aConditions, $aAttributes) - { - $this->class = $sClass; - $this->conditions = $aConditions; - $this->attributes = $aAttributes; - } + public $class; + public $conditions; // array of SOAPSearchCondition + public $attributes; // array of SOAPAttributeValue + + public function __construct($sClass, $aConditions, $aAttributes) + { + $this->class = $sClass; + $this->conditions = $aConditions; + $this->attributes = $aAttributes; + } } class SOAPLogMessage { - public $text; // string + public $text; // string - public function __construct($sText) - { - $this->text = $sText; - } + public function __construct($sText) + { + $this->text = $sText; + } } class SOAPResultLog { - public $messages; // array of SOAPLogMessage + public $messages; // array of SOAPLogMessage - public function __construct($aMessages) - { - $this->messages = $aMessages; - } + public function __construct($aMessages) + { + $this->messages = $aMessages; + } } class SOAPKeyValue { - public $key; // string - public $value; // string - - public function __construct($sKey, $sValue) - { - $this->key = $sKey; - $this->value = $sValue; - } + public $key; // string + public $value; // string + + public function __construct($sKey, $sValue) + { + $this->key = $sKey; + $this->value = $sValue; + } } class SOAPResultMessage { - public $label; // string - public $values; // array of SOAPKeyValue - - public function __construct($sLabel, $aValues) - { - $this->label = $sLabel; - $this->values = $aValues; - } + public $label; // string + public $values; // array of SOAPKeyValue + + public function __construct($sLabel, $aValues) + { + $this->label = $sLabel; + $this->values = $aValues; + } } class SOAPResult { - public $status; // boolean - public $result; // array of SOAPResultMessage - public $errors; // array of SOAPResultLog - public $warnings; // array of SOAPResultLog - public $infos; // array of SOAPResultLog - - public function __construct($bStatus, $aResult, $aErrors, $aWarnings, $aInfos) - { - $this->status = $bStatus; - $this->result = $aResult; - $this->errors = $aErrors; - $this->warnings = $aWarnings; - $this->infos = $aInfos; - } + public $status; // boolean + public $result; // array of SOAPResultMessage + public $errors; // array of SOAPResultLog + public $warnings; // array of SOAPResultLog + public $infos; // array of SOAPResultLog + + public function __construct($bStatus, $aResult, $aErrors, $aWarnings, $aInfos) + { + $this->status = $bStatus; + $this->result = $aResult; + $this->errors = $aErrors; + $this->warnings = $aWarnings; + $this->infos = $aInfos; + } } class SOAPSimpleResult { - public $status; // boolean - public $message; // string - - public function __construct($bStatus, $sMessage) - { - $this->status = $bStatus; - $this->message = $sMessage; - } + public $status; // boolean + public $message; // string + + public function __construct($bStatus, $sMessage) + { + $this->status = $bStatus; + $this->message = $sMessage; + } } class SOAPMapping { - public static function GetMapping() - { - $aSOAPMapping = [ - 'SearchCondition' => 'SOAPSearchCondition', - 'ExternalKeySearch' => 'SOAPExternalKeySearch', - 'AttributeValue' => 'SOAPAttributeValue', - 'LinkCreationSpec' => 'SOAPLinkCreationSpec', - 'KeyValue' => 'SOAPKeyValue', - 'LogMessage' => 'SOAPLogMessage', - 'ResultLog' => 'SOAPResultLog', - 'ResultData' => 'SOAPKeyValue', - 'ResultMessage' => 'SOAPResultMessage', - 'Result' => 'SOAPResult', - 'SimpleResult' => 'SOAPSimpleResult', - ]; - return $aSOAPMapping; - } + public static function GetMapping() + { + $aSOAPMapping = [ + 'SearchCondition' => 'SOAPSearchCondition', + 'ExternalKeySearch' => 'SOAPExternalKeySearch', + 'AttributeValue' => 'SOAPAttributeValue', + 'LinkCreationSpec' => 'SOAPLinkCreationSpec', + 'KeyValue' => 'SOAPKeyValue', + 'LogMessage' => 'SOAPLogMessage', + 'ResultLog' => 'SOAPResultLog', + 'ResultData' => 'SOAPKeyValue', + 'ResultMessage' => 'SOAPResultMessage', + 'Result' => 'SOAPResult', + 'SimpleResult' => 'SOAPSimpleResult', + ]; + return $aSOAPMapping; + } } diff --git a/webservices/rest.php b/webservices/rest.php index c16b8df703..878a3e9faf 100644 --- a/webservices/rest.php +++ b/webservices/rest.php @@ -33,34 +33,34 @@ */ class RestResultListOperations extends RestResult { - public $version; - public $operations; - - public function AddOperation($sVerb, $sDescription, $sServiceProviderClass) - { - $this->operations[] = [ - 'verb' => $sVerb, - 'description' => $sDescription, - 'extension' => $sServiceProviderClass, - ]; - } + public $version; + public $operations; + + public function AddOperation($sVerb, $sDescription, $sServiceProviderClass) + { + $this->operations[] = [ + 'verb' => $sVerb, + 'description' => $sDescription, + 'extension' => $sServiceProviderClass, + ]; + } } if (!function_exists('json_last_error_msg')) { - function json_last_error_msg() - { - static $ERRORS = [ - JSON_ERROR_NONE => 'No error', - JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', - JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', - JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', - JSON_ERROR_SYNTAX => 'Syntax error', - JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', - ]; - - $error = json_last_error(); - return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; - } + function json_last_error_msg() + { + static $ERRORS = [ + JSON_ERROR_NONE => 'No error', + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', + JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', + JSON_ERROR_SYNTAX => 'Syntax error', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + ]; + + $error = json_last_error(); + return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; + } } //////////////////////////////////////////////////////////////////////////////// @@ -76,150 +76,150 @@ function json_last_error_msg() $sJsonString = utils::ReadParam('json_data', null, false, 'raw_data'); if (empty($sJsonString)) { - //N °3455: read json_data parameter via a file passed by http protocol - if (isset($_FILES['json_data']['tmp_name'])) { - $sTmpFilePath = $_FILES['json_data']['tmp_name']; - if (is_file($sTmpFilePath)) { - $sValue = file_get_contents($sTmpFilePath); - unlink($sTmpFilePath); - if (! empty($sValue)) { - $sJsonString = utils::Sanitize($sValue, null, 'raw_data'); - } - } - } + //N °3455: read json_data parameter via a file passed by http protocol + if (isset($_FILES['json_data']['tmp_name'])) { + $sTmpFilePath = $_FILES['json_data']['tmp_name']; + if (is_file($sTmpFilePath)) { + $sValue = file_get_contents($sTmpFilePath); + unlink($sTmpFilePath); + if (! empty($sValue)) { + $sJsonString = utils::Sanitize($sValue, null, 'raw_data'); + } + } + } } $sProvider = ''; $oKPI = new ExecutionKPI(); try { - utils::UseParamFile(); - - $oKPI->ComputeAndReport('Data model loaded'); - - // N°6358 - force credentials for REST calls - LoginWebPage::ResetSession(true); - $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); - $oKPI->ComputeAndReport('User login'); - - if ($iRet == LoginWebPage::EXIT_CODE_OK) { - // Extra validation of the profile - if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) { - // Web services access is limited to the users with the profile REST Web Services - $iRet = LoginWebPage::EXIT_CODE_NOTAUTHORIZED; - } - } - if ($iRet != LoginWebPage::EXIT_CODE_OK) { - switch ($iRet) { - case LoginWebPage::EXIT_CODE_MISSINGLOGIN: - throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER); - break; - - case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: - throw new Exception("Missing parameter 'auth_pwd'", RestResult::MISSING_AUTH_PWD); - break; - - case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: - throw new Exception("Invalid login", RestResult::UNAUTHORIZED); - break; - - case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: - throw new Exception("Portal user is not allowed", RestResult::UNAUTHORIZED); - break; - - case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: - throw new Exception("This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)", RestResult::UNAUTHORIZED); - break; - - default: - throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED); - } - } - - if ($sVersion == null) { - throw new Exception("Missing parameter 'version' (e.g. '1.0')", RestResult::MISSING_VERSION); - } - - if ($sJsonString == null) { - throw new Exception("Missing parameter 'json_data'", RestResult::MISSING_JSON); - } - - if (is_string($sJsonString)) { - $aJsonData = @json_decode($sJsonString); - } elseif (is_array($sJsonString)) { - $aJsonData = (object) $sJsonString; - $sJsonString = json_encode($aJsonData); - } else { - $aJsonData = null; - } - - if ($aJsonData == null) { - throw new Exception('Parameter json_data is not a valid JSON structure', RestResult::INVALID_JSON); - } - - $oKPI->ComputeAndReport('Parameters validated'); - - /** @var iRestServiceProvider[] $aProviders */ - $oKPI = new ExecutionKPI(); - $aProviders = []; - foreach (get_declared_classes() as $sPHPClass) { - $oRefClass = new ReflectionClass($sPHPClass); - if ($oRefClass->implementsInterface('iRestServiceProvider')) { - $aProviders[] = new $sPHPClass(); - } - } - - $aOpToRestService = []; // verb => $oRestServiceProvider - /** @var iRestServiceProvider $oRestSP */ - foreach ($aProviders as $oRestSP) { - $aOperations = $oRestSP->ListOperations($sVersion); - foreach ($aOperations as $aOpData) { - $aOpToRestService[$aOpData['verb']] = - [ - 'service_provider' => $oRestSP, - 'description' => $aOpData['description'], - ]; - } - } - $oKPI->ComputeAndReport('iRestServiceProvider loaded with operations'); - - if (count($aOpToRestService) == 0) { - throw new Exception("There is no service available for version '$sVersion'", RestResult::UNSUPPORTED_VERSION); - } - - $sOperation = RestUtils::GetMandatoryParam($aJsonData, 'operation'); - if ($sOperation == 'list_operations') { - $oResult = new RestResultListOperations(); - $oResult->message = "Operations: ".count($aOpToRestService); - $oResult->version = $sVersion; - foreach ($aOpToRestService as $sVerb => $aOpData) { - $oResult->AddOperation($sVerb, $aOpData['description'], get_class($aOpData['service_provider'])); - } - } else { - if (!array_key_exists($sOperation, $aOpToRestService)) { - throw new Exception("Unknown verb '$sOperation' in version '$sVersion'", RestResult::UNKNOWN_OPERATION); - } - /** @var iRestServiceProvider $oRS */ - $oRS = $aOpToRestService[$sOperation]['service_provider']; - $sProvider = get_class($oRS); - - if ($oRS instanceof iRestInputSanitizer) { - $sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString); - } - - CMDBObject::SetTrackOrigin('webservice-rest'); - $oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData); - } - $oKPI->ComputeAndReport('Operation finished'); + utils::UseParamFile(); + + $oKPI->ComputeAndReport('Data model loaded'); + + // N°6358 - force credentials for REST calls + LoginWebPage::ResetSession(true); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + $oKPI->ComputeAndReport('User login'); + + if ($iRet == LoginWebPage::EXIT_CODE_OK) { + // Extra validation of the profile + if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) { + // Web services access is limited to the users with the profile REST Web Services + $iRet = LoginWebPage::EXIT_CODE_NOTAUTHORIZED; + } + } + if ($iRet != LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + throw new Exception("Missing parameter 'auth_pwd'", RestResult::MISSING_AUTH_PWD); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + throw new Exception("Invalid login", RestResult::UNAUTHORIZED); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + throw new Exception("Portal user is not allowed", RestResult::UNAUTHORIZED); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + throw new Exception("This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)", RestResult::UNAUTHORIZED); + break; + + default: + throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED); + } + } + + if ($sVersion == null) { + throw new Exception("Missing parameter 'version' (e.g. '1.0')", RestResult::MISSING_VERSION); + } + + if ($sJsonString == null) { + throw new Exception("Missing parameter 'json_data'", RestResult::MISSING_JSON); + } + + if (is_string($sJsonString)) { + $aJsonData = @json_decode($sJsonString); + } elseif (is_array($sJsonString)) { + $aJsonData = (object) $sJsonString; + $sJsonString = json_encode($aJsonData); + } else { + $aJsonData = null; + } + + if ($aJsonData == null) { + throw new Exception('Parameter json_data is not a valid JSON structure', RestResult::INVALID_JSON); + } + + $oKPI->ComputeAndReport('Parameters validated'); + + /** @var iRestServiceProvider[] $aProviders */ + $oKPI = new ExecutionKPI(); + $aProviders = []; + foreach (get_declared_classes() as $sPHPClass) { + $oRefClass = new ReflectionClass($sPHPClass); + if ($oRefClass->implementsInterface('iRestServiceProvider')) { + $aProviders[] = new $sPHPClass(); + } + } + + $aOpToRestService = []; // verb => $oRestServiceProvider + /** @var iRestServiceProvider $oRestSP */ + foreach ($aProviders as $oRestSP) { + $aOperations = $oRestSP->ListOperations($sVersion); + foreach ($aOperations as $aOpData) { + $aOpToRestService[$aOpData['verb']] = + [ + 'service_provider' => $oRestSP, + 'description' => $aOpData['description'], + ]; + } + } + $oKPI->ComputeAndReport('iRestServiceProvider loaded with operations'); + + if (count($aOpToRestService) == 0) { + throw new Exception("There is no service available for version '$sVersion'", RestResult::UNSUPPORTED_VERSION); + } + + $sOperation = RestUtils::GetMandatoryParam($aJsonData, 'operation'); + if ($sOperation == 'list_operations') { + $oResult = new RestResultListOperations(); + $oResult->message = "Operations: ".count($aOpToRestService); + $oResult->version = $sVersion; + foreach ($aOpToRestService as $sVerb => $aOpData) { + $oResult->AddOperation($sVerb, $aOpData['description'], get_class($aOpData['service_provider'])); + } + } else { + if (!array_key_exists($sOperation, $aOpToRestService)) { + throw new Exception("Unknown verb '$sOperation' in version '$sVersion'", RestResult::UNKNOWN_OPERATION); + } + /** @var iRestServiceProvider $oRS */ + $oRS = $aOpToRestService[$sOperation]['service_provider']; + $sProvider = get_class($oRS); + + if ($oRS instanceof iRestInputSanitizer) { + $sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString); + } + + CMDBObject::SetTrackOrigin('webservice-rest'); + $oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData); + } + $oKPI->ComputeAndReport('Operation finished'); } catch (Exception $e) { - $oResult = new RestResult(); - if ($e->GetCode() == 0) { - $oResult->code = RestResult::INTERNAL_ERROR; - } else { - $oResult->code = $e->GetCode(); - } - $oResult->message = "Error: ".$e->GetMessage(); - $oKPI->ComputeAndReport('Exception catched'); + $oResult = new RestResult(); + if ($e->GetCode() == 0) { + $oResult->code = RestResult::INTERNAL_ERROR; + } else { + $oResult->code = $e->GetCode(); + } + $oResult->message = "Error: ".$e->GetMessage(); + $oKPI->ComputeAndReport('Exception catched'); } // Output the results @@ -227,17 +227,17 @@ function json_last_error_msg() $sResponse = json_encode($oResult); if ($sResponse === false) { - $oJsonIssue = new RestResult(); - $oJsonIssue->code = RestResult::INTERNAL_ERROR; - $oJsonIssue->message = 'json encoding failed with message: '.json_last_error_msg().'. Full response structure for debugging purposes (print_r+bin2hex): '.bin2hex(print_r($oResult, true)); - $sResponse = json_encode($oJsonIssue); + $oJsonIssue = new RestResult(); + $oJsonIssue->code = RestResult::INTERNAL_ERROR; + $oJsonIssue->message = 'json encoding failed with message: '.json_last_error_msg().'. Full response structure for debugging purposes (print_r+bin2hex): '.bin2hex(print_r($oResult, true)); + $sResponse = json_encode($oJsonIssue); } $sCallback = utils::ReadParam('callback', null); if ($sCallback == null) { - $oP = new JsonPage(); + $oP = new JsonPage(); } else { - $oP = new JsonPPage($sCallback); + $oP = new JsonPPage($sCallback); } $oP->add_header('Access-Control-Allow-Origin: *'); $oP->SetData(json_decode($sResponse, true)); @@ -248,28 +248,28 @@ function json_last_error_msg() // Log usage // if (MetaModel::GetConfig()->Get('log_rest_service')) { - $oLog = new EventRestService(); - $oLog->SetTrim('userinfo', UserRights::GetUser()); - $oLog->Set('version', $sVersion); - $oLog->Set('operation', $sOperation); - $oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString); - - $oLog->Set('provider', $sProvider); - $sMessage = $oResult->message; - if (empty($oResult->message)) { - $sMessage = 'Ok'; - } - $oLog->SetTrim('message', $sMessage); - $oLog->Set('code', $oResult->code); - $oResult->SanitizeContent(); - $iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; - $sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT); - $sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode); - !StringFitsInLogField($sJsonOuputWithPrettyPrinting) ? - $oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty - $oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting); - - $oLog->DBInsertNoReload(); + $oLog = new EventRestService(); + $oLog->SetTrim('userinfo', UserRights::GetUser()); + $oLog->Set('version', $sVersion); + $oLog->Set('operation', $sOperation); + $oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString); + + $oLog->Set('provider', $sProvider); + $sMessage = $oResult->message; + if (empty($oResult->message)) { + $sMessage = 'Ok'; + } + $oLog->SetTrim('message', $sMessage); + $oLog->Set('code', $oResult->code); + $oResult->SanitizeContent(); + $iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + $sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT); + $sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode); + !StringFitsInLogField($sJsonOuputWithPrettyPrinting) ? + $oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty + $oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting); + + $oLog->DBInsertNoReload(); } /** @@ -277,5 +277,5 @@ function json_last_error_msg() */ function StringFitsInLogField(string $sLog): bool { - return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260 + return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260 } diff --git a/webservices/soapserver.php b/webservices/soapserver.php index e0b01ac4ab..3c45c11653 100644 --- a/webservices/soapserver.php +++ b/webservices/soapserver.php @@ -29,60 +29,60 @@ $sWsdlUri = utils::GetAbsoluteUrlAppRoot().'webservices/itop.wsdl.php'; $sServiceCategory = utils::ReadParam('service_category'); if (!empty($sServiceCategory)) { - $sWsdlUri .= "?service_category=".$sServiceCategory; + $sWsdlUri .= "?service_category=".$sServiceCategory; } ini_set("soap.wsdl_cache_enabled", "0"); $aSOAPMapping = SOAPMapping::GetMapping(); $oSoapServer = new SoapServer( - $sWsdlUri, - [ - 'classmap' => $aSOAPMapping - ] + $sWsdlUri, + [ + 'classmap' => $aSOAPMapping + ] ); // $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION); if (!empty($sServiceCategory)) { - $sServiceClass = $sServiceCategory; - if (!class_exists($sServiceClass)) { - // not a valid class name (not a PHP class at all) - throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not a PHP class"); - } elseif (!is_subclass_of($sServiceClass, 'WebServicesBase')) { - // not a valid class name (not deriving from WebServicesBase) - throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not derived from WebServicesBase"); - } else { - $oSoapServer->setClass($sServiceClass, null); - } + $sServiceClass = $sServiceCategory; + if (!class_exists($sServiceClass)) { + // not a valid class name (not a PHP class at all) + throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not a PHP class"); + } elseif (!is_subclass_of($sServiceClass, 'WebServicesBase')) { + // not a valid class name (not deriving from WebServicesBase) + throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not derived from WebServicesBase"); + } else { + $oSoapServer->setClass($sServiceClass, null); + } } else { - $oSoapServer->setClass('BasicServices', null); + $oSoapServer->setClass('BasicServices', null); } if ($_SERVER["REQUEST_METHOD"] == "POST") { - CMDBObject::SetTrackOrigin('webservice-soap'); - $oSoapServer->handle(); + CMDBObject::SetTrackOrigin('webservice-soap'); + $oSoapServer->handle(); } else { - echo "This SOAP server can handle the following functions: "; - $aFunctions = $oSoapServer->getFunctions(); - echo "
    \n"; - foreach ($aFunctions as $sFunc) { - if ($sFunc == 'GetWSDLContents') { - continue; - } + echo "This SOAP server can handle the following functions: "; + $aFunctions = $oSoapServer->getFunctions(); + echo "
      \n"; + foreach ($aFunctions as $sFunc) { + if ($sFunc == 'GetWSDLContents') { + continue; + } - echo "
    • $sFunc
    • \n"; - } - echo "
    \n"; - echo "

    Here the WSDL file

    "; + echo "

  • $sFunc
  • \n"; + } + echo "
\n"; + echo "

Here the WSDL file

"; - echo "You may also want to try the following service categories: "; - echo "

    \n"; - foreach (get_declared_classes() as $sPHPClass) { - if (is_subclass_of($sPHPClass, 'WebServicesBase')) { - $sServiceCategory = $sPHPClass; - $sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php'; - $sSoapServerUri .= "?service_category=$sServiceCategory"; - echo "
  • $sServiceCategory
  • \n"; - } - } - echo "
\n"; + echo "You may also want to try the following service categories: "; + echo "
    \n"; + foreach (get_declared_classes() as $sPHPClass) { + if (is_subclass_of($sPHPClass, 'WebServicesBase')) { + $sServiceCategory = $sPHPClass; + $sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php'; + $sSoapServerUri .= "?service_category=$sServiceCategory"; + echo "
  • $sServiceCategory
  • \n"; + } + } + echo "
\n"; } diff --git a/webservices/status.php b/webservices/status.php index 55f297519d..793ebca8e5 100644 --- a/webservices/status.php +++ b/webservices/status.php @@ -7,12 +7,12 @@ //Do check Status try { - new Status(); - $aResult = ['status' => STATUS_RUNNING, 'code' => RestResult::OK, 'message' => '']; + new Status(); + $aResult = ['status' => STATUS_RUNNING, 'code' => RestResult::OK, 'message' => '']; } catch (Exception $e) { - $iCode = (defined('\RestResult::INTERNAL_ERROR')) ? RestResult::INTERNAL_ERROR : 100; - $aResult = ['status' => STATUS_ERROR, 'code' => $iCode, 'message' => $e->getMessage()]; - http_response_code(500); + $iCode = (defined('\RestResult::INTERNAL_ERROR')) ? RestResult::INTERNAL_ERROR : 100; + $aResult = ['status' => STATUS_ERROR, 'code' => $iCode, 'message' => $e->getMessage()]; + http_response_code(500); } //Set headers, based on webservices/rest.php diff --git a/webservices/webservices.basic.php b/webservices/webservices.basic.php index 336ace0713..e322aee18a 100644 --- a/webservices/webservices.basic.php +++ b/webservices/webservices.basic.php @@ -28,232 +28,232 @@ class BasicServices extends WebServicesBase { - protected static function GetWSDLFilePath() - { - return APPROOT.'/webservices/itop.wsdl.tpl'; - } - - /** - * Get the server version (TODO: get it dynamically, where ?) - * - * @return string WebServiceResult - */ - public static function GetVersion() - { - if (ITOP_REVISION == 'svn') { - $sVersionString = ITOP_VERSION.' [dev]'; - } else { - // This is a build made from SVN, let display the full information - $sVersionString = ITOP_VERSION_FULL." ".ITOP_BUILD_DATE; - } - - return $sVersionString; - } - - public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) - { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) { - $oRes = new WebServiceResultFailedLogin($sLogin); - $this->LogUsage(__FUNCTION__, $oRes); - - return $oRes->ToSoapStructure(); - } - UserRights::Login($sLogin); - - $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); - $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); - $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); - $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); - $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); - - $aImpactedCIs = []; - if (is_null($aSOAPImpactedCIs)) { - $aSOAPImpactedCIs = []; - } - foreach ($aSOAPImpactedCIs as $oImpactedCIs) { - $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); - } - - $oRes = $this->_CreateResponseTicket( - 'UserRequest', - $sTitle, - $sDescription, - $aCallerDesc, - $aCustomerDesc, - $aServiceDesc, - $aServiceSubcategoryDesc, - $sProduct, - $aWorkgroupDesc, - $aImpactedCIs, - $sImpact, - $sUrgency - ); - return $oRes->ToSoapStructure(); - } - - public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) - { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) { - $oRes = new WebServiceResultFailedLogin($sLogin); - $this->LogUsage(__FUNCTION__, $oRes); - - return $oRes->ToSoapStructure(); - } - UserRights::Login($sLogin); - - if (!class_exists('Incident')) { - $oRes = new WebServiceResult(); - $oRes->LogError("The class Incident does not exist. Did you install the Incident Management (ITIL) module ?"); - return $oRes->ToSoapStructure(); - } - - $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); - $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); - $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); - $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); - $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); - - $aImpactedCIs = []; - if (is_null($aSOAPImpactedCIs)) { - $aSOAPImpactedCIs = []; - } - foreach ($aSOAPImpactedCIs as $oImpactedCIs) { - $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); - } - - $oRes = $this->_CreateResponseTicket( - 'Incident', - $sTitle, - $sDescription, - $aCallerDesc, - $aCustomerDesc, - $aServiceDesc, - $aServiceSubcategoryDesc, - $sProduct, - $aWorkgroupDesc, - $aImpactedCIs, - $sImpact, - $sUrgency - ); - return $oRes->ToSoapStructure(); - } - - /** - * Create an ResponseTicket (Incident or UserRequest) from an external system - * Some CIs might be specified (by their name/IP) - * - * @param string sClass The class of the ticket: Incident or UserRequest - * @param string sTitle - * @param string sDescription - * @param array aCallerDesc - * @param array aCustomerDesc - * @param array aServiceDesc - * @param array aServiceSubcategoryDesc - * @param string sProduct - * @param array aWorkgroupDesc - * @param array aImpactedCIs - * @param string sImpact - * @param string sUrgency - * - * @return WebServiceResult - */ - protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCallerDesc, $aCustomerDesc, $aServiceDesc, $aServiceSubcategoryDesc, $sProduct, $aWorkgroupDesc, $aImpactedCIs, $sImpact, $sUrgency) - { - - $oRes = new WebServiceResult(); - - try { - CMDBObject::SetTrackInfo('Administrator'); - - $oNewTicket = MetaModel::NewObject($sClass); - $this->MyObjectSetScalar('title', 'title', $sTitle, $oNewTicket, $oRes); - $this->MyObjectSetScalar('description', 'description', $sDescription, $oNewTicket, $oRes); - - $this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes); - $this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes); - - $this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes); - if (!array_key_exists('service_id', $aServiceSubcategoryDesc)) { - $aServiceSubcategoryDesc['service_id'] = $oNewTicket->Get('service_id'); - } - $this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes); - if (MetaModel::IsValidAttCode($sClass, 'product')) { - // 1.x data models - $this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes); - } - - if (MetaModel::IsValidAttCode($sClass, 'workgroup_id')) { - // 1.x data models - $this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); - } elseif (MetaModel::IsValidAttCode($sClass, 'team_id')) { - // 2.x data models - $this->MyObjectSetExternalKey('team_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); - } - - if (MetaModel::IsValidAttCode($sClass, 'ci_list')) { - // 1.x data models - $aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); - } elseif (MetaModel::IsValidAttCode($sClass, 'functionalcis_list')) { - // 2.x data models - $aDevicesNotFound = $this->AddLinkedObjects('functionalcis_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); - } - - if (count($aDevicesNotFound) > 0) { - $this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes); - } else { - $this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes); - } - - $this->MyObjectSetScalar('impact', 'impact', $sImpact, $oNewTicket, $oRes); - $this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes); - - $this->MyObjectInsert($oNewTicket, 'created', $oRes); - } catch (CoreException $e) { - $oRes->LogError($e->getMessage()); - } catch (Exception $e) { - $oRes->LogError($e->getMessage()); - } - - $this->LogUsage(__FUNCTION__, $oRes); - return $oRes; - } - - /** - * Given an OQL, returns a set of objects (several objects could be on the same row) - * - * @param string sOQL - */ - public function SearchObjects($sLogin, $sPassword, $sOQL) - { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) { - $oRes = new WebServiceResultFailedLogin($sLogin); - $this->LogUsage(__FUNCTION__, $oRes); - - return $oRes->ToSoapStructure(); - } - UserRights::Login($sLogin); - - $oRes = $this->_SearchObjects($sOQL); - return $oRes->ToSoapStructure(); - } - - protected function _SearchObjects($sOQL) - { - $oRes = new WebServiceResult(); - try { - $oSearch = DBObjectSearch::FromOQL($sOQL); - $oSet = new DBObjectSet($oSearch); - $aData = $oSet->ToArrayOfValues(); - foreach ($aData as $iRow => $aRow) { - $oRes->AddResultRow("row_$iRow", $aRow); - } - } catch (CoreException $e) { - $oRes->LogError($e->getMessage()); - } catch (Exception $e) { - $oRes->LogError($e->getMessage()); - } - - $this->LogUsage(__FUNCTION__, $oRes); - return $oRes; - } + protected static function GetWSDLFilePath() + { + return APPROOT.'/webservices/itop.wsdl.tpl'; + } + + /** + * Get the server version (TODO: get it dynamically, where ?) + * + * @return string WebServiceResult + */ + public static function GetVersion() + { + if (ITOP_REVISION == 'svn') { + $sVersionString = ITOP_VERSION.' [dev]'; + } else { + // This is a build made from SVN, let display the full information + $sVersionString = ITOP_VERSION_FULL." ".ITOP_BUILD_DATE; + } + + return $sVersionString; + } + + public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) + { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + $oRes = new WebServiceResultFailedLogin($sLogin); + $this->LogUsage(__FUNCTION__, $oRes); + + return $oRes->ToSoapStructure(); + } + UserRights::Login($sLogin); + + $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); + $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); + $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); + $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); + $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); + + $aImpactedCIs = []; + if (is_null($aSOAPImpactedCIs)) { + $aSOAPImpactedCIs = []; + } + foreach ($aSOAPImpactedCIs as $oImpactedCIs) { + $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); + } + + $oRes = $this->_CreateResponseTicket( + 'UserRequest', + $sTitle, + $sDescription, + $aCallerDesc, + $aCustomerDesc, + $aServiceDesc, + $aServiceSubcategoryDesc, + $sProduct, + $aWorkgroupDesc, + $aImpactedCIs, + $sImpact, + $sUrgency + ); + return $oRes->ToSoapStructure(); + } + + public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) + { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + $oRes = new WebServiceResultFailedLogin($sLogin); + $this->LogUsage(__FUNCTION__, $oRes); + + return $oRes->ToSoapStructure(); + } + UserRights::Login($sLogin); + + if (!class_exists('Incident')) { + $oRes = new WebServiceResult(); + $oRes->LogError("The class Incident does not exist. Did you install the Incident Management (ITIL) module ?"); + return $oRes->ToSoapStructure(); + } + + $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); + $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); + $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); + $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); + $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); + + $aImpactedCIs = []; + if (is_null($aSOAPImpactedCIs)) { + $aSOAPImpactedCIs = []; + } + foreach ($aSOAPImpactedCIs as $oImpactedCIs) { + $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); + } + + $oRes = $this->_CreateResponseTicket( + 'Incident', + $sTitle, + $sDescription, + $aCallerDesc, + $aCustomerDesc, + $aServiceDesc, + $aServiceSubcategoryDesc, + $sProduct, + $aWorkgroupDesc, + $aImpactedCIs, + $sImpact, + $sUrgency + ); + return $oRes->ToSoapStructure(); + } + + /** + * Create an ResponseTicket (Incident or UserRequest) from an external system + * Some CIs might be specified (by their name/IP) + * + * @param string sClass The class of the ticket: Incident or UserRequest + * @param string sTitle + * @param string sDescription + * @param array aCallerDesc + * @param array aCustomerDesc + * @param array aServiceDesc + * @param array aServiceSubcategoryDesc + * @param string sProduct + * @param array aWorkgroupDesc + * @param array aImpactedCIs + * @param string sImpact + * @param string sUrgency + * + * @return WebServiceResult + */ + protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCallerDesc, $aCustomerDesc, $aServiceDesc, $aServiceSubcategoryDesc, $sProduct, $aWorkgroupDesc, $aImpactedCIs, $sImpact, $sUrgency) + { + + $oRes = new WebServiceResult(); + + try { + CMDBObject::SetTrackInfo('Administrator'); + + $oNewTicket = MetaModel::NewObject($sClass); + $this->MyObjectSetScalar('title', 'title', $sTitle, $oNewTicket, $oRes); + $this->MyObjectSetScalar('description', 'description', $sDescription, $oNewTicket, $oRes); + + $this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes); + $this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes); + + $this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes); + if (!array_key_exists('service_id', $aServiceSubcategoryDesc)) { + $aServiceSubcategoryDesc['service_id'] = $oNewTicket->Get('service_id'); + } + $this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes); + if (MetaModel::IsValidAttCode($sClass, 'product')) { + // 1.x data models + $this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes); + } + + if (MetaModel::IsValidAttCode($sClass, 'workgroup_id')) { + // 1.x data models + $this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); + } elseif (MetaModel::IsValidAttCode($sClass, 'team_id')) { + // 2.x data models + $this->MyObjectSetExternalKey('team_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); + } + + if (MetaModel::IsValidAttCode($sClass, 'ci_list')) { + // 1.x data models + $aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); + } elseif (MetaModel::IsValidAttCode($sClass, 'functionalcis_list')) { + // 2.x data models + $aDevicesNotFound = $this->AddLinkedObjects('functionalcis_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); + } + + if (count($aDevicesNotFound) > 0) { + $this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes); + } else { + $this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes); + } + + $this->MyObjectSetScalar('impact', 'impact', $sImpact, $oNewTicket, $oRes); + $this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes); + + $this->MyObjectInsert($oNewTicket, 'created', $oRes); + } catch (CoreException $e) { + $oRes->LogError($e->getMessage()); + } catch (Exception $e) { + $oRes->LogError($e->getMessage()); + } + + $this->LogUsage(__FUNCTION__, $oRes); + return $oRes; + } + + /** + * Given an OQL, returns a set of objects (several objects could be on the same row) + * + * @param string sOQL + */ + public function SearchObjects($sLogin, $sPassword, $sOQL) + { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + $oRes = new WebServiceResultFailedLogin($sLogin); + $this->LogUsage(__FUNCTION__, $oRes); + + return $oRes->ToSoapStructure(); + } + UserRights::Login($sLogin); + + $oRes = $this->_SearchObjects($sOQL); + return $oRes->ToSoapStructure(); + } + + protected function _SearchObjects($sOQL) + { + $oRes = new WebServiceResult(); + try { + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new DBObjectSet($oSearch); + $aData = $oSet->ToArrayOfValues(); + foreach ($aData as $iRow => $aRow) { + $oRes->AddResultRow("row_$iRow", $aRow); + } + } catch (CoreException $e) { + $oRes->LogError($e->getMessage()); + } catch (Exception $e) { + $oRes->LogError($e->getMessage()); + } + + $this->LogUsage(__FUNCTION__, $oRes); + return $oRes; + } } diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index f411b49d8d..a65f56ab19 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -33,195 +33,195 @@ */ class WebServiceResult { - /** - * Overall status - * - * @var m_bStatus - */ - public $m_bStatus; - - /** - * Error log - * - * @var m_aErrors - */ - public $m_aErrors; - - /** - * Warning log - * - * @var m_aWarnings - */ - public $m_aWarnings; - - /** - * Information log - * - * @var m_aInfos - */ - public $m_aInfos; - - /** - * Constructor - * - * @param status $bStatus - */ - public function __construct() - { - $this->m_bStatus = true; - $this->m_aResult = []; - $this->m_aErrors = []; - $this->m_aWarnings = []; - $this->m_aInfos = []; - } - - public function ToSoapStructure() - { - $aResults = []; - foreach ($this->m_aResult as $sLabel => $aData) { - $aValues = []; - foreach ($aData as $sKey => $value) { - $aValues[] = new SOAPKeyValue($sKey, $value); - } - $aResults[] = new SoapResultMessage($sLabel, $aValues); - } - $aInfos = []; - foreach ($this->m_aInfos as $sMessage) { - $aInfos[] = new SoapLogMessage($sMessage); - } - $aWarnings = []; - foreach ($this->m_aWarnings as $sMessage) { - $aWarnings[] = new SoapLogMessage($sMessage); - } - $aErrors = []; - foreach ($this->m_aErrors as $sMessage) { - $aErrors[] = new SoapLogMessage($sMessage); - } - - $oRet = new SOAPResult( - $this->m_bStatus, - $aResults, - new SOAPResultLog($aErrors), - new SOAPResultLog($aWarnings), - new SOAPResultLog($aInfos) - ); - - return $oRet; - } - - /** - * Did the current processing encounter a stopper issue ? - * - * @return bool - */ - public function IsOk() - { - return $this->m_bStatus; - } - - /** - * Add result details - object reference - * - * @param string sLabel - * @param object oObject - */ - public function AddResultObject($sLabel, $oObject) - { - $oAppContext = new ApplicationContext(); - $this->m_aResult[$sLabel] = [ - 'id' => $oObject->GetKey(), - 'name' => $oObject->GetRawName(), - 'url' => $oAppContext->MakeObjectUrl(get_class($oObject), $oObject->GetKey(), null, false), // Raw URL without HTML tags - ]; - } - - /** - * Add result details - a table row - * - * @param string sLabel - * @param object oObject - */ - public function AddResultRow($sLabel, $aRow) - { - $this->m_aResult[$sLabel] = $aRow; - } - - /** - * Log an error - * - * @param string sDescription - */ - public function LogError($sDescription) - { - $this->m_aErrors[] = $sDescription; - // Note: SOAP do transform false into null - $this->m_bStatus = 0; - } - - /** - * Log a warning - * - * @param string sDescription - */ - public function LogWarning($sDescription) - { - $this->m_aWarnings[] = $sDescription; - } - - /** - * Log an error or a warning - * - * @param string sDescription - * @param boolean bIsStopper - */ - public function LogIssue($sDescription, $bIsStopper = true) - { - if ($bIsStopper) { - $this->LogError($sDescription); - } else { - $this->LogWarning($sDescription); - } - } - - /** - * Log operation details - * - * @param description $sDescription - */ - public function LogInfo($sDescription) - { - $this->m_aInfos[] = $sDescription; - } - - protected static function LogToText($aLog) - { - return implode("\n", $aLog); - } - - public function GetInfoAsText() - { - return self::LogToText($this->m_aInfos); - } - - public function GetWarningsAsText() - { - return self::LogToText($this->m_aWarnings); - } - - public function GetErrorsAsText() - { - return self::LogToText($this->m_aErrors); - } - - public function GetReturnedDataAsText() - { - $sRet = ''; - foreach ($this->m_aResult as $sKey => $value) { - $sRet .= "===== $sKey =====\n"; - $sRet .= print_r($value, true); - } - return $sRet; - } + /** + * Overall status + * + * @var m_bStatus + */ + public $m_bStatus; + + /** + * Error log + * + * @var m_aErrors + */ + public $m_aErrors; + + /** + * Warning log + * + * @var m_aWarnings + */ + public $m_aWarnings; + + /** + * Information log + * + * @var m_aInfos + */ + public $m_aInfos; + + /** + * Constructor + * + * @param status $bStatus + */ + public function __construct() + { + $this->m_bStatus = true; + $this->m_aResult = []; + $this->m_aErrors = []; + $this->m_aWarnings = []; + $this->m_aInfos = []; + } + + public function ToSoapStructure() + { + $aResults = []; + foreach ($this->m_aResult as $sLabel => $aData) { + $aValues = []; + foreach ($aData as $sKey => $value) { + $aValues[] = new SOAPKeyValue($sKey, $value); + } + $aResults[] = new SoapResultMessage($sLabel, $aValues); + } + $aInfos = []; + foreach ($this->m_aInfos as $sMessage) { + $aInfos[] = new SoapLogMessage($sMessage); + } + $aWarnings = []; + foreach ($this->m_aWarnings as $sMessage) { + $aWarnings[] = new SoapLogMessage($sMessage); + } + $aErrors = []; + foreach ($this->m_aErrors as $sMessage) { + $aErrors[] = new SoapLogMessage($sMessage); + } + + $oRet = new SOAPResult( + $this->m_bStatus, + $aResults, + new SOAPResultLog($aErrors), + new SOAPResultLog($aWarnings), + new SOAPResultLog($aInfos) + ); + + return $oRet; + } + + /** + * Did the current processing encounter a stopper issue ? + * + * @return bool + */ + public function IsOk() + { + return $this->m_bStatus; + } + + /** + * Add result details - object reference + * + * @param string sLabel + * @param object oObject + */ + public function AddResultObject($sLabel, $oObject) + { + $oAppContext = new ApplicationContext(); + $this->m_aResult[$sLabel] = [ + 'id' => $oObject->GetKey(), + 'name' => $oObject->GetRawName(), + 'url' => $oAppContext->MakeObjectUrl(get_class($oObject), $oObject->GetKey(), null, false), // Raw URL without HTML tags + ]; + } + + /** + * Add result details - a table row + * + * @param string sLabel + * @param object oObject + */ + public function AddResultRow($sLabel, $aRow) + { + $this->m_aResult[$sLabel] = $aRow; + } + + /** + * Log an error + * + * @param string sDescription + */ + public function LogError($sDescription) + { + $this->m_aErrors[] = $sDescription; + // Note: SOAP do transform false into null + $this->m_bStatus = 0; + } + + /** + * Log a warning + * + * @param string sDescription + */ + public function LogWarning($sDescription) + { + $this->m_aWarnings[] = $sDescription; + } + + /** + * Log an error or a warning + * + * @param string sDescription + * @param boolean bIsStopper + */ + public function LogIssue($sDescription, $bIsStopper = true) + { + if ($bIsStopper) { + $this->LogError($sDescription); + } else { + $this->LogWarning($sDescription); + } + } + + /** + * Log operation details + * + * @param description $sDescription + */ + public function LogInfo($sDescription) + { + $this->m_aInfos[] = $sDescription; + } + + protected static function LogToText($aLog) + { + return implode("\n", $aLog); + } + + public function GetInfoAsText() + { + return self::LogToText($this->m_aInfos); + } + + public function GetWarningsAsText() + { + return self::LogToText($this->m_aWarnings); + } + + public function GetErrorsAsText() + { + return self::LogToText($this->m_aErrors); + } + + public function GetReturnedDataAsText() + { + $sRet = ''; + foreach ($this->m_aResult as $sKey => $value) { + $sRet .= "===== $sKey =====\n"; + $sRet .= print_r($value, true); + } + return $sRet; + } } /** @@ -231,11 +231,11 @@ public function GetReturnedDataAsText() */ class WebServiceResultFailedLogin extends WebServiceResult { - public function __construct($sLogin) - { - parent::__construct(); - $this->LogError("Wrong credentials: '$sLogin'"); - } + public function __construct($sLogin) + { + parent::__construct(); + $this->LogError("Wrong credentials: '$sLogin'"); + } } /** @@ -245,319 +245,319 @@ public function __construct($sLogin) */ abstract class WebServicesBase { - public static function GetWSDLContents($sServiceCategory = '') - { - if ($sServiceCategory == '') { - $sServiceCategory = 'BasicServices'; - } - $sWsdlFilePath = call_user_func([$sServiceCategory, 'GetWSDLFilePath']); - return file_get_contents($sWsdlFilePath); - } - - /** - * Helper to log a service delivery - * - * @param string sVerb - * @param array aArgs - * @param WebServiceResult oRes - * - */ - protected function LogUsage($sVerb, $oRes) - { - if (!MetaModel::IsLogEnabledWebService()) { - return; - } - - $oLog = new EventWebService(); - if ($oRes->IsOk()) { - $oLog->Set('message', $sVerb.' was successfully invoked'); - } else { - $oLog->Set('message', $sVerb.' returned errors'); - } - $oLog->Set('userinfo', UserRights::GetUser()); - $oLog->Set('verb', $sVerb); - $oLog->Set('result', $oRes->IsOk()); - $this->TrimAndSetValue($oLog, 'log_info', (string)$oRes->GetInfoAsText()); - $this->TrimAndSetValue($oLog, 'log_warning', (string)$oRes->GetWarningsAsText()); - $this->TrimAndSetValue($oLog, 'log_error', (string)$oRes->GetErrorsAsText()); - $this->TrimAndSetValue($oLog, 'data', (string)$oRes->GetReturnedDataAsText()); - $oLog->DBInsertNoReload(); - } - - protected function TrimAndSetValue($oLog, $sAttCode, $sValue) - { - $oAttDef = MetaModel::GetAttributeDef(get_class($oLog), $sAttCode); - if (is_object($oAttDef)) { - $iMaxSize = $oAttDef->GetMaxSize(); - if ($iMaxSize && (mb_strlen($sValue) > $iMaxSize)) { - $sValue = mb_substr($sValue, 0, $iMaxSize); - } - $oLog->Set($sAttCode, $sValue); - } - } - - /** - * Helper to set a scalar attribute - * - * @param string sAttCode - * @param scalar value - * @param DBObject oTargetObj - * @param WebServiceResult oRes - * - */ - protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) - { - $res = $oTargetObj->CheckValue($sAttCode, $value); - if ($res === true) { - $oTargetObj->Set($sAttCode, $value); - } else { - // $res contains the error description - $oRes->LogError("Unexpected value for parameter $sParamName: $res"); - } - } - - /** - * Helper to set an external key - * - * @param string sAttCode - * @param array aExtKeyDesc - * @param DBObject oTargetObj - * @param WebServiceResult oRes - * - */ - protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, &$oTargetObj, &$oRes) - { - $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); - - $bIsMandatory = !$oExtKey->IsNullAllowed(); - - if (is_null($aExtKeyDesc)) { - if ($bIsMandatory) { - $oRes->LogError("Parameter $sParamName: found null for a mandatory key"); - } else { - // skip silently - return; - } - } - - if (count($aExtKeyDesc) == 0) { - $oRes->LogIssue("Parameter $sParamName: no search condition has been specified", $bIsMandatory); - return; - } - - $sKeyClass = $oExtKey->GetTargetClass(); - $oReconFilter = new DBObjectSearch($sKeyClass); - foreach ($aExtKeyDesc as $sForeignAttCode => $value) { - if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) { - $aCodes = MetaModel::GetFiltersList($sKeyClass); - $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; - $oRes->LogIssue($sMsg, $bIsMandatory); - } - // The foreign attribute is one of our reconciliation key - $oReconFilter->AddCondition($sForeignAttCode, $value, '='); - } - $oExtObjects = new CMDBObjectSet($oReconFilter); - switch ($oExtObjects->Count()) { - case 0: - $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL(true)."')"; - $oRes->LogIssue($sMsg, $bIsMandatory); - break; - case 1: - // Do change the external key attribute - $oForeignObj = $oExtObjects->Fetch(); - $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); - - // Report it (no need to report if the object already had this value - if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { - $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); - } - break; - default: - $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL(true)."')"; - $oRes->LogIssue($sMsg, $bIsMandatory); - } - } - - /** - * Helper to link objects - * - * @param string sLinkAttCode - * @param string sLinkedClass - * @param array $aLinkList - * @param DBObject oTargetObj - * @param WebServiceResult oRes - * - * @return array List of objects that could not be found - */ - protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) - { - $oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode); - $sLinkClass = $oLinkAtt->GetLinkedClass(); - $sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote(); - - $aItemsFound = []; - $aItemsNotFound = []; - - if (is_null($aLinkList)) { - return $aItemsNotFound; - } - - foreach ($aLinkList as $aItemData) { - if (!array_key_exists('class', $aItemData)) { - $oRes->LogWarning("Parameter $sParamName: missing 'class' specification"); - continue; // skip - } - $sTargetClass = $aItemData['class']; - if (!MetaModel::IsValidClass($sTargetClass)) { - $oRes->LogError("Parameter $sParamName: invalid class '$sTargetClass'"); - continue; // skip - } - if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) { - $oRes->LogError("Parameter $sParamName: '$sTargetClass' is not a child class of '$sLinkedClass'"); - continue; // skip - } - $oReconFilter = new DBObjectSearch($sTargetClass); - $aCIStringDesc = []; - foreach ($aItemData['search'] as $sAttCode => $value) { - if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { - $aCodes = MetaModel::GetFiltersList($sTargetClass); - $oRes->LogError("Parameter $sParamName: '$sAttCode' is not a valid filter code for class '$sTargetClass', expecting a value in {".implode(', ', $aCodes)."}"); - continue 2; // skip the entire item - } - $aCIStringDesc[] = "$sAttCode: $value"; - - // The attribute is one of our reconciliation key - $oReconFilter->AddCondition($sAttCode, $value, '='); - } - if (count($aCIStringDesc) == 1) { - // take the last and unique value to describe the object - $sItemDesc = $value; - } else { - // describe the object by the given keys - $sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')'; - } - - $oExtObjects = new CMDBObjectSet($oReconFilter); - switch ($oExtObjects->Count()) { - case 0: - $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL(true)."')"); - $aItemsNotFound[] = $sItemDesc; - break; - case 1: - $aItemsFound[] = [ - 'object' => $oExtObjects->Fetch(), - 'link_values' => @$aItemData['link_values'], - 'desc' => $sItemDesc, - ]; - break; - default: - $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL(true)."')"); - $aItemsNotFound[] = $sItemDesc; - } - } - - if (count($aItemsFound) > 0) { - $aLinks = []; - foreach ($aItemsFound as $aItemData) { - $oLink = MetaModel::NewObject($sLinkClass); - $oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey()); - foreach ($aItemData['link_values'] as $sKey => $value) { - if (!MetaModel::IsValidAttCode($sLinkClass, $sKey)) { - $oRes->LogWarning("Parameter $sParamName: Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); - } else { - $oLink->Set($sKey, $value); - } - } - $aLinks[] = $oLink; - } - $oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks); - $oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet); - } - - return $aItemsNotFound; - } - - /** - * @param \CMDBObject $oTargetObj - * @param string $sResultLabel - * @param \WebServiceResult $oRes - * - * @throws \ArchivedObjectException - * @throws \CoreCannotSaveObjectException - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \CoreWarning - * @throws \MySQLException - * @throws \OQLException - * @throws \SecurityException - */ - protected function MyObjectInsert($oTargetObj, $sResultLabel, &$oRes) - { - if ($oRes->IsOk()) { - list($bRes, $aIssues) = $oTargetObj->CheckToWrite(); - if ($bRes) { - $iId = $oTargetObj->DBInsertNoReload(); - $oRes->LogInfo("Created object ".get_class($oTargetObj)."::$iId"); - $oRes->AddResultObject($sResultLabel, $oTargetObj); - } else { - $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); - foreach ($aIssues as $iIssue => $sIssue) { - $oRes->LogError("Issue #$iIssue: $sIssue"); - } - } - } - } - - protected static function SoapStructToExternalKeySearch($oExternalKeySearch) - { - if (is_null($oExternalKeySearch)) { - return null; - } - if ($oExternalKeySearch->IsVoid()) { - return null; - } - - $aRes = []; - foreach ($oExternalKeySearch->conditions as $oSearchCondition) { - $aRes[$oSearchCondition->attcode] = $oSearchCondition->value; - } - return $aRes; - } - - protected static function SoapStructToLinkCreationSpec(SoapLinkCreationSpec $oLinkCreationSpec) - { - $aRes = - [ - 'class' => $oLinkCreationSpec->class, - 'search' => [], - 'link_values' => [], - ]; - - foreach ($oLinkCreationSpec->conditions as $oSearchCondition) { - $aRes['search'][$oSearchCondition->attcode] = $oSearchCondition->value; - } - - foreach ($oLinkCreationSpec->attributes as $oAttributeValue) { - $aRes['link_values'][$oAttributeValue->attcode] = $oAttributeValue->value; - } - - return $aRes; - } - - protected static function SoapStructToAssociativeArray($aArrayOfAssocArray) - { - if (is_null($aArrayOfAssocArray)) { - return []; - } - - $aRes = []; - foreach ($aArrayOfAssocArray as $aAssocArray) { - $aRow = []; - foreach ($aAssocArray as $oKeyValuePair) { - $aRow[$oKeyValuePair->key] = $oKeyValuePair->value; - } - $aRes[] = $aRow; - } - return $aRes; - } + public static function GetWSDLContents($sServiceCategory = '') + { + if ($sServiceCategory == '') { + $sServiceCategory = 'BasicServices'; + } + $sWsdlFilePath = call_user_func([$sServiceCategory, 'GetWSDLFilePath']); + return file_get_contents($sWsdlFilePath); + } + + /** + * Helper to log a service delivery + * + * @param string sVerb + * @param array aArgs + * @param WebServiceResult oRes + * + */ + protected function LogUsage($sVerb, $oRes) + { + if (!MetaModel::IsLogEnabledWebService()) { + return; + } + + $oLog = new EventWebService(); + if ($oRes->IsOk()) { + $oLog->Set('message', $sVerb.' was successfully invoked'); + } else { + $oLog->Set('message', $sVerb.' returned errors'); + } + $oLog->Set('userinfo', UserRights::GetUser()); + $oLog->Set('verb', $sVerb); + $oLog->Set('result', $oRes->IsOk()); + $this->TrimAndSetValue($oLog, 'log_info', (string)$oRes->GetInfoAsText()); + $this->TrimAndSetValue($oLog, 'log_warning', (string)$oRes->GetWarningsAsText()); + $this->TrimAndSetValue($oLog, 'log_error', (string)$oRes->GetErrorsAsText()); + $this->TrimAndSetValue($oLog, 'data', (string)$oRes->GetReturnedDataAsText()); + $oLog->DBInsertNoReload(); + } + + protected function TrimAndSetValue($oLog, $sAttCode, $sValue) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oLog), $sAttCode); + if (is_object($oAttDef)) { + $iMaxSize = $oAttDef->GetMaxSize(); + if ($iMaxSize && (mb_strlen($sValue) > $iMaxSize)) { + $sValue = mb_substr($sValue, 0, $iMaxSize); + } + $oLog->Set($sAttCode, $sValue); + } + } + + /** + * Helper to set a scalar attribute + * + * @param string sAttCode + * @param scalar value + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + */ + protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) + { + $res = $oTargetObj->CheckValue($sAttCode, $value); + if ($res === true) { + $oTargetObj->Set($sAttCode, $value); + } else { + // $res contains the error description + $oRes->LogError("Unexpected value for parameter $sParamName: $res"); + } + } + + /** + * Helper to set an external key + * + * @param string sAttCode + * @param array aExtKeyDesc + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + */ + protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, &$oTargetObj, &$oRes) + { + $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); + + $bIsMandatory = !$oExtKey->IsNullAllowed(); + + if (is_null($aExtKeyDesc)) { + if ($bIsMandatory) { + $oRes->LogError("Parameter $sParamName: found null for a mandatory key"); + } else { + // skip silently + return; + } + } + + if (count($aExtKeyDesc) == 0) { + $oRes->LogIssue("Parameter $sParamName: no search condition has been specified", $bIsMandatory); + return; + } + + $sKeyClass = $oExtKey->GetTargetClass(); + $oReconFilter = new DBObjectSearch($sKeyClass); + foreach ($aExtKeyDesc as $sForeignAttCode => $value) { + if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) { + $aCodes = MetaModel::GetFiltersList($sKeyClass); + $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; + $oRes->LogIssue($sMsg, $bIsMandatory); + } + // The foreign attribute is one of our reconciliation key + $oReconFilter->AddCondition($sForeignAttCode, $value, '='); + } + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch ($oExtObjects->Count()) { + case 0: + $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL(true)."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + break; + case 1: + // Do change the external key attribute + $oForeignObj = $oExtObjects->Fetch(); + $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); + + // Report it (no need to report if the object already had this value + if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { + $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); + } + break; + default: + $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL(true)."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + } + } + + /** + * Helper to link objects + * + * @param string sLinkAttCode + * @param string sLinkedClass + * @param array $aLinkList + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + * @return array List of objects that could not be found + */ + protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) + { + $oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode); + $sLinkClass = $oLinkAtt->GetLinkedClass(); + $sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote(); + + $aItemsFound = []; + $aItemsNotFound = []; + + if (is_null($aLinkList)) { + return $aItemsNotFound; + } + + foreach ($aLinkList as $aItemData) { + if (!array_key_exists('class', $aItemData)) { + $oRes->LogWarning("Parameter $sParamName: missing 'class' specification"); + continue; // skip + } + $sTargetClass = $aItemData['class']; + if (!MetaModel::IsValidClass($sTargetClass)) { + $oRes->LogError("Parameter $sParamName: invalid class '$sTargetClass'"); + continue; // skip + } + if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) { + $oRes->LogError("Parameter $sParamName: '$sTargetClass' is not a child class of '$sLinkedClass'"); + continue; // skip + } + $oReconFilter = new DBObjectSearch($sTargetClass); + $aCIStringDesc = []; + foreach ($aItemData['search'] as $sAttCode => $value) { + if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { + $aCodes = MetaModel::GetFiltersList($sTargetClass); + $oRes->LogError("Parameter $sParamName: '$sAttCode' is not a valid filter code for class '$sTargetClass', expecting a value in {".implode(', ', $aCodes)."}"); + continue 2; // skip the entire item + } + $aCIStringDesc[] = "$sAttCode: $value"; + + // The attribute is one of our reconciliation key + $oReconFilter->AddCondition($sAttCode, $value, '='); + } + if (count($aCIStringDesc) == 1) { + // take the last and unique value to describe the object + $sItemDesc = $value; + } else { + // describe the object by the given keys + $sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')'; + } + + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch ($oExtObjects->Count()) { + case 0: + $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL(true)."')"); + $aItemsNotFound[] = $sItemDesc; + break; + case 1: + $aItemsFound[] = [ + 'object' => $oExtObjects->Fetch(), + 'link_values' => @$aItemData['link_values'], + 'desc' => $sItemDesc, + ]; + break; + default: + $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL(true)."')"); + $aItemsNotFound[] = $sItemDesc; + } + } + + if (count($aItemsFound) > 0) { + $aLinks = []; + foreach ($aItemsFound as $aItemData) { + $oLink = MetaModel::NewObject($sLinkClass); + $oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey()); + foreach ($aItemData['link_values'] as $sKey => $value) { + if (!MetaModel::IsValidAttCode($sLinkClass, $sKey)) { + $oRes->LogWarning("Parameter $sParamName: Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); + } else { + $oLink->Set($sKey, $value); + } + } + $aLinks[] = $oLink; + } + $oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks); + $oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet); + } + + return $aItemsNotFound; + } + + /** + * @param \CMDBObject $oTargetObj + * @param string $sResultLabel + * @param \WebServiceResult $oRes + * + * @throws \ArchivedObjectException + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \CoreWarning + * @throws \MySQLException + * @throws \OQLException + * @throws \SecurityException + */ + protected function MyObjectInsert($oTargetObj, $sResultLabel, &$oRes) + { + if ($oRes->IsOk()) { + list($bRes, $aIssues) = $oTargetObj->CheckToWrite(); + if ($bRes) { + $iId = $oTargetObj->DBInsertNoReload(); + $oRes->LogInfo("Created object ".get_class($oTargetObj)."::$iId"); + $oRes->AddResultObject($sResultLabel, $oTargetObj); + } else { + $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); + foreach ($aIssues as $iIssue => $sIssue) { + $oRes->LogError("Issue #$iIssue: $sIssue"); + } + } + } + } + + protected static function SoapStructToExternalKeySearch($oExternalKeySearch) + { + if (is_null($oExternalKeySearch)) { + return null; + } + if ($oExternalKeySearch->IsVoid()) { + return null; + } + + $aRes = []; + foreach ($oExternalKeySearch->conditions as $oSearchCondition) { + $aRes[$oSearchCondition->attcode] = $oSearchCondition->value; + } + return $aRes; + } + + protected static function SoapStructToLinkCreationSpec(SoapLinkCreationSpec $oLinkCreationSpec) + { + $aRes = + [ + 'class' => $oLinkCreationSpec->class, + 'search' => [], + 'link_values' => [], + ]; + + foreach ($oLinkCreationSpec->conditions as $oSearchCondition) { + $aRes['search'][$oSearchCondition->attcode] = $oSearchCondition->value; + } + + foreach ($oLinkCreationSpec->attributes as $oAttributeValue) { + $aRes['link_values'][$oAttributeValue->attcode] = $oAttributeValue->value; + } + + return $aRes; + } + + protected static function SoapStructToAssociativeArray($aArrayOfAssocArray) + { + if (is_null($aArrayOfAssocArray)) { + return []; + } + + $aRes = []; + foreach ($aArrayOfAssocArray as $aAssocArray) { + $aRow = []; + foreach ($aAssocArray as $oKeyValuePair) { + $aRow[$oKeyValuePair->key] = $oKeyValuePair->value; + } + $aRes[] = $aRow; + } + return $aRes; + } } From 2d5acc4c698c50e9e642b49f73254cb8f05d5af6 Mon Sep 17 00:00:00 2001 From: odain Date: Fri, 17 Oct 2025 12:08:40 +0200 Subject: [PATCH 11/15] format concat with spaces --- tests/php-code-style/.php-cs-fixer.dist.php | 23 +++++++++++---------- webservices/cron.php | 4 ++-- webservices/status.php | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/php-code-style/.php-cs-fixer.dist.php b/tests/php-code-style/.php-cs-fixer.dist.php index 7ff4a436e0..6ee2e9dd9f 100644 --- a/tests/php-code-style/.php-cs-fixer.dist.php +++ b/tests/php-code-style/.php-cs-fixer.dist.php @@ -5,17 +5,17 @@ echo $APPROOT; $finder = PhpCsFixer\Finder::create() ->exclude('oql') - ->in($APPROOT.'/addons') - ->in($APPROOT.'/application') - ->in($APPROOT.'/core') - ->in($APPROOT.'/datamodels') - ->in($APPROOT.'/dictionaries') - ->in($APPROOT.'/pages') - ->in($APPROOT.'/portal') - ->in($APPROOT.'/setup') - ->in($APPROOT.'/sources') - ->in($APPROOT.'/synchro') - ->in($APPROOT.'/tests') + ->in($APPROOT . '/addons') + ->in($APPROOT . '/application') + ->in($APPROOT . '/core') + ->in($APPROOT . '/datamodels') + ->in($APPROOT . '/dictionaries') + ->in($APPROOT . '/pages') + ->in($APPROOT . '/portal') + ->in($APPROOT . '/setup') + ->in($APPROOT . '/sources') + ->in($APPROOT . '/synchro') + ->in($APPROOT . '/tests') ->in($APPROOT . '/webservices') ; @@ -26,6 +26,7 @@ 'indentation_type' => true, 'no_extra_blank_lines' => true, 'array_syntax' => ['syntax' => 'short'], + 'concat_space' => true, ]) ->setIndent("\t") ->setLineEnding("\n") diff --git a/webservices/cron.php b/webservices/cron.php index 66549e7d15..1d788cca64 100644 --- a/webservices/cron.php +++ b/webservices/cron.php @@ -124,9 +124,9 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) $oExceptionToThrow = $e; } catch (Exception $e) { // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running if ($oTask->IsDebug()) { - $sMessage = 'Processing failed with message: '. $e->getMessage() . '. ' . $e->getTraceAsString(); + $sMessage = 'Processing failed with message: '.$e->getMessage().'. '.$e->getTraceAsString(); } else { - $sMessage = 'Processing failed with message: '. $e->getMessage(); + $sMessage = 'Processing failed with message: '.$e->getMessage(); } } $fDuration = microtime(true) - $fStart; diff --git a/webservices/status.php b/webservices/status.php index 793ebca8e5..7ef91b2c0a 100644 --- a/webservices/status.php +++ b/webservices/status.php @@ -17,7 +17,7 @@ //Set headers, based on webservices/rest.php $sContentType = 'application/json'; -header('Content-type: ' . $sContentType); +header('Content-type: '.$sContentType); header('Access-Control-Allow-Origin: *'); //Output result From 013ab40599c6662a48882acaf15426edd19cea8e Mon Sep 17 00:00:00 2001 From: odain Date: Fri, 17 Oct 2025 16:03:27 +0200 Subject: [PATCH 12/15] finalize included/excluded folders to formatting tool --- tests/php-code-style/.php-cs-fixer.dist.php | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/tests/php-code-style/.php-cs-fixer.dist.php b/tests/php-code-style/.php-cs-fixer.dist.php index 6ee2e9dd9f..6b23a172b2 100644 --- a/tests/php-code-style/.php-cs-fixer.dist.php +++ b/tests/php-code-style/.php-cs-fixer.dist.php @@ -4,19 +4,9 @@ echo $APPROOT; $finder = PhpCsFixer\Finder::create() - ->exclude('oql') - ->in($APPROOT . '/addons') - ->in($APPROOT . '/application') - ->in($APPROOT . '/core') - ->in($APPROOT . '/datamodels') - ->in($APPROOT . '/dictionaries') - ->in($APPROOT . '/pages') - ->in($APPROOT . '/portal') - ->in($APPROOT . '/setup') - ->in($APPROOT . '/sources') - ->in($APPROOT . '/synchro') - ->in($APPROOT . '/tests') - ->in($APPROOT . '/webservices') + ->in($APPROOT) + ->exclude(['oql', 'data', 'extensions']) + ->notPath(['/env-*/', '/cache-*/', 'lib', 'vendor', 'node_modules']) ; $config = new PhpCsFixer\Config(); @@ -31,4 +21,4 @@ ->setIndent("\t") ->setLineEnding("\n") ->setFinder($finder) -; +; \ No newline at end of file From 44d0910992f891b656d95820be0c7863d2827289 Mon Sep 17 00:00:00 2001 From: odain Date: Fri, 17 Oct 2025 16:22:58 +0200 Subject: [PATCH 13/15] add trailing_comma_in_multiline option --- tests/php-code-style/.php-cs-fixer.dist.php | 1 + webservices/itoprest.examples.php | 10 +++++----- webservices/soapserver.php | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/php-code-style/.php-cs-fixer.dist.php b/tests/php-code-style/.php-cs-fixer.dist.php index 6b23a172b2..1478f735cd 100644 --- a/tests/php-code-style/.php-cs-fixer.dist.php +++ b/tests/php-code-style/.php-cs-fixer.dist.php @@ -17,6 +17,7 @@ 'no_extra_blank_lines' => true, 'array_syntax' => ['syntax' => 'short'], 'concat_space' => true, + 'trailing_comma_in_multiline' => true, ]) ->setIndent("\t") ->setLineEnding("\n") diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index 4d07389c19..6408a1ff8f 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -156,7 +156,7 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead 'org_id' => "SELECT Organization WHERE name = 'Demo'", 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], 'title' => 'issue blah', - 'description' => 'something happened' + 'description' => 'something happened', ], ], [ @@ -245,7 +245,7 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead // Values to set 'fields' => [ 'team_id' => 15, // Helpdesk - 'agent_id' => 9 // Jules Verne + 'agent_id' => 9, // Jules Verne ], 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) ], @@ -271,7 +271,7 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead 'contents' => [ 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACmSURBVChTfZHRDYMwDESzQ2fqhHx3C3ao+MkW/WlnaFxfzk7sEnE6JHJ+NgaKZN2zLHVN2ssfkae0Da7FQ5PRk/ve4Hcx19Ie6CEGuh/6vMgNhwanHVUNbt73lUDbYJ+6pg8b3+m2RehsVPdMXyvQY+OVkB+Rrv64lUjb3nq+aCA6v4leRqtfaIgimr53atBy9PlfUhoh3fFCNDmErv9FWR6ylBL5AREbmHBnFj5lAAAAAElFTkSuQmCC', 'filename' => 'myself.png', - 'mimetype' => 'image/png' + 'mimetype' => 'image/png', ], ], ], @@ -280,7 +280,7 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead 'class' => 'Attachment', 'key' => 'SELECT Attachment', 'output_fields' => '*', - ] + ], ]; $aOperations = [ [ @@ -306,7 +306,7 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead 'org_id' => "SELECT Organization WHERE name = 'Demo'", 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], 'title' => 'issue blah', - 'description' => 'something happened' + 'description' => 'something happened', ], ], ]; diff --git a/webservices/soapserver.php b/webservices/soapserver.php index 3c45c11653..055ceb0ebe 100644 --- a/webservices/soapserver.php +++ b/webservices/soapserver.php @@ -38,7 +38,7 @@ $oSoapServer = new SoapServer( $sWsdlUri, [ - 'classmap' => $aSOAPMapping + 'classmap' => $aSOAPMapping, ] ); // $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION); From b72fdb4c81ff207fb21da6fd0d2c412f186a6d59 Mon Sep 17 00:00:00 2001 From: odain Date: Mon, 20 Oct 2025 12:01:52 +0200 Subject: [PATCH 14/15] adapt composer.json requirements --- tests/php-code-style/composer.json | 4 ++-- tests/php-code-style/composer.lock | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/php-code-style/composer.json b/tests/php-code-style/composer.json index 1d7e9ec0c7..b5c397bde1 100644 --- a/tests/php-code-style/composer.json +++ b/tests/php-code-style/composer.json @@ -1,7 +1,7 @@ { "require-dev": { - "php": "^7.4 || ^8.0", - "friendsofphp/php-cs-fixer": "^3.88", + "php": "^7.0 || ^8.0", + "friendsofphp/php-cs-fixer": "^3.89", "phpstan/phpstan": "^2.1" } } diff --git a/tests/php-code-style/composer.lock b/tests/php-code-style/composer.lock index 0b38438dd8..0584bac3a1 100644 --- a/tests/php-code-style/composer.lock +++ b/tests/php-code-style/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "afbcf4a8cd7e954326e354a54bd2a1dc", + "content-hash": "316ab7e70be8ef55aca6979d178f56d5", "packages": [], "packages-dev": [ { @@ -403,16 +403,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.88.2", + "version": "v3.89.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99" + "reference": "4dd6768cb7558440d27d18f54909eee417317ce9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99", - "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4dd6768cb7558440d27d18f54909eee417317ce9", + "reference": "4dd6768cb7558440d27d18f54909eee417317ce9", "shasum": "" }, "require": { @@ -427,7 +427,6 @@ "php": "^7.4 || ^8.0", "react/child-process": "^0.6.6", "react/event-loop": "^1.5", - "react/promise": "^3.3", "react/socket": "^1.16", "react/stream": "^1.4", "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", @@ -495,7 +494,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.0" }, "funding": [ { @@ -503,7 +502,7 @@ "type": "github" } ], - "time": "2025-09-27T00:24:15+00:00" + "time": "2025-10-18T19:30:16+00:00" }, { "name": "phpstan/phpstan", @@ -2787,7 +2786,7 @@ "prefer-lowest": false, "platform": {}, "platform-dev": { - "php": "^7.4 || ^8.0" + "php": "^7.0 || ^8.0" }, "plugin-api-version": "2.6.0" } From 5af9123ef93228212f6ce13075be7df2a05213fb Mon Sep 17 00:00:00 2001 From: odain Date: Fri, 7 Nov 2025 15:10:19 +0100 Subject: [PATCH 15/15] Revert changes on webservices folder --- webservices/backoffice.dataloader.php | 50 +- webservices/createfrommail.php | 61 ++- webservices/cron.php | 275 ++++++---- webservices/export-v2.php | 141 ++++-- webservices/export.php | 248 +++++---- webservices/import.php | 648 ++++++++++++++---------- webservices/itop.wsdl.php | 11 +- webservices/itoprest.examples.php | 273 +++++----- webservices/itopsoap.examples.php | 77 +-- webservices/itopsoaptypes.class.inc.php | 29 +- webservices/rest.php | 192 ++++--- webservices/soapserver.php | 55 +- webservices/status.php | 19 +- webservices/webservices.basic.php | 121 +++-- webservices/webservices.class.inc.php | 288 ++++++----- 15 files changed, 1511 insertions(+), 977 deletions(-) diff --git a/webservices/backoffice.dataloader.php b/webservices/backoffice.dataloader.php index b4479974c9..c73ca047f0 100644 --- a/webservices/backoffice.dataloader.php +++ b/webservices/backoffice.dataloader.php @@ -1,5 +1,4 @@ p("No memory limit has been defined in this instance of PHP"); - } else { + $oP->p("No memory limit has been defined in this instance of PHP"); + } + else + { // Check that the limit will allow us to load the data // $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); - if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) { - if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === false) { - $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); - } else { - $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); + if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) + { + if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE) + { + $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); + } + else + { + $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); } } } } + //////////////////////////////////////////////////////////////////////////////// // // Main @@ -79,7 +87,9 @@ function SetMemoryLimit($oP) $oP = new WebPage("iTop - Backoffice data loader"); -try { + +try +{ // Note: the data model must be loaded first $oDataLoader = new XMLDataLoader(); @@ -92,7 +102,8 @@ function SetMemoryLimit($oP) SetMemoryLimit($oP); - // The XMLDataLoader constructor has initialized the DB, let's start a transaction + + // The XMLDataLoader constructor has initialized the DB, let's start a transaction CMDBSource::Query('START TRANSACTION'); $oP->p("Starting data load."); @@ -117,21 +128,26 @@ function SetMemoryLimit($oP) } } $aWarnings = $oDataLoader->GetWarnings(); - if (count($aWarnings) > 0) { + if (count($aWarnings) > 0) + { $oP->p('Warnings ('.count($aWarnings).')'); - foreach ($aWarnings as $sMsg) { + foreach ($aWarnings as $sMsg) + { $oP->p(' * '.$sMsg); } } } -} catch (Exception $e) { - $oP->p("An error happened while loading the data: ".$e->getMessage()); +} +catch(Exception $e) +{ + $oP->p("An error happened while loading the data: ".$e->getMessage()); $oP->p("Aborting (no data written)..."); CMDBSource::Query('ROLLBACK'); } -if (function_exists('memory_get_peak_usage')) { +if (function_exists('memory_get_peak_usage')) +{ $oP->p("Information: memory peak usage: ".memory_get_peak_usage()); } diff --git a/webservices/createfrommail.php b/webservices/createfrommail.php index 4ed93177c0..a83488754a 100644 --- a/webservices/createfrommail.php +++ b/webservices/createfrommail.php @@ -41,11 +41,12 @@ function GetSender($aHeaders) { - $aResult = ['name' => '', 'email' => '']; + $aResult = array('name' => '', 'email' => ''); $aResult['name'] = $aHeaders['From']; - $aMatches = []; - if (preg_match('/\(([0-9a-zA-Z\._]+)@(.+)@(.+)\)/U', array_pop($aHeaders['Received']), $aMatches)) { - $aResult['email'] = $aMatches[1].'@'.$aMatches[2]; + $aMatches = array(); + if (preg_match('/\(([0-9a-zA-Z\._]+)@(.+)@(.+)\)/U', array_pop($aHeaders['Received']), $aMatches)) + { + $aResult['email'] = $aMatches[1].'@'.$aMatches[2]; } return $aResult; } @@ -60,14 +61,16 @@ function GetSender($aHeaders) function CreateTicket($sSenderEmail, $sSubject, $sBody) { $oTicket = null; - try { + try + { $oContactSearch = new DBObjectSearch('Contact'); // Can be either a Person or a Team, but must be a valid Contact $oContactSearch->AddCondition('email', $sSenderEmail, '='); $oSet = new DBObjectSet($oContactSearch); - if ($oSet->Count() == 1) { + if ($oSet->Count() == 1) + { $oContact = $oSet->Fetch(); $oOrganization = MetaModel::GetObject('Organization', $oContact->Get('org_id')); - $oTicket = new UserRequest(); + $oTicket = new UserRequest; $oTicket->Set('title', $sSubject); $oTicket->Set('description', $sBody); $oTicket->Set('org_id', $oOrganization->GetKey()); @@ -83,10 +86,14 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) $sUserString = $oContact->GetName().', submitted by email'; CMDBObject::SetTrackInfo($sUserString); $oTicket->DBInsert(); - } else { + } + else + { echo "No contact found in iTop having the email: $sSenderEmail, email message ignored.\n"; } - } catch (Exception $e) { + } + catch(Exception $e) + { echo "Error: exception ".$e->getMessage(); $oTicket = null; } @@ -104,14 +111,15 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) // Note: it is expected that the sender of the email exists a valid contact as a 'Contact' // in iTop (identified by her/his email address), otherwise the ticket creation will fail $iNbMessages = $oPop3->numMsg(); -for ($index = 1; $index <= $iNbMessages; $index++) { +for($index = 1; $index <= $iNbMessages; $index++) +{ $params['include_bodies'] = true; $params['decode_bodies'] = true; $params['decode_headers'] = true; $params['crlf'] = "\r\n"; $aHeaders = $oPop3->getParsedHeaders($index); $aSender = GetSender($aHeaders); - $oDecoder = new Mail_mimeDecode($oPop3->getRawHeaders($index).$params['crlf'].$oPop3->getBody($index)); + $oDecoder = new Mail_mimeDecode( $oPop3->getRawHeaders($index).$params['crlf'].$oPop3->getBody($index) ); $oStructure = $oDecoder->decode($params); $sSubject = $aHeaders['Subject']; // Search for the text/plain body part @@ -121,14 +129,19 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) //echo "
\n";
 	//print_r($oStructure);
 	//echo "
\n"; - if (!isset($oStructure->parts) || count($oStructure->parts) == 0) { + if (!isset($oStructure->parts) || count($oStructure->parts) == 0) + { $sTextBody = $oStructure->body; - } else { + } + else + { // Find the first "part" of the body which is in text/plain - while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { + while( ($iPartIndex < count($oStructure->parts)) && (!$bFound) ) + { //echo "

Reading part $iPartIndex

\n"; - if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && - ($oStructure->parts[$iPartIndex]->ctype_secondary == 'plain')) { + if ( ($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && + ($oStructure->parts[$iPartIndex]->ctype_secondary == 'plain') ) + { $sTextBody = $oStructure->parts[$iPartIndex]->body; $bFound = true; //echo "

Plain text found ! ($sTextBody)

\n"; @@ -136,11 +149,14 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) $iPartIndex++; } // Try again but this time look for an HTML part - if (!$bFound) { - while (($iPartIndex < count($oStructure->parts)) && (!$bFound)) { + if (!$bFound) + { + while( ($iPartIndex < count($oStructure->parts)) && (!$bFound) ) + { //echo "

Reading part $iPartIndex

\n"; - if (($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && - ($oStructure->parts[$iPartIndex]->ctype_secondary == 'html')) { + if ( ($oStructure->parts[$iPartIndex]->ctype_primary == 'text') && + ($oStructure->parts[$iPartIndex]->ctype_secondary == 'html') ) + { $sTextBody = $oStructure->parts[$iPartIndex]->body; $bFound = true; //echo "

HTML text found ! (".htmlentities($sTextBody, ENT_QUOTES, 'UTF-8').")

\n"; @@ -155,10 +171,11 @@ function CreateTicket($sSenderEmail, $sSubject, $sBody) // name => 'john foo $oTicket = CreateTicket($aSender['email'], $sSubject, $sTextBody); - if ($oTicket != null) { + if ($oTicket != null) + { // Ticket created, delete the email $oPop3->deleteMsg($index); - echo "Ticket: ".$oTicket->GetName()." created.\n"; + echo "Ticket: ".$oTicket->GetName()." created.\n"; } } $oPop3->disconnect(); diff --git a/webservices/cron.php b/webservices/cron.php index 1d788cca64..f51a46f64c 100644 --- a/webservices/cron.php +++ b/webservices/cron.php @@ -1,5 +1,4 @@ p("ERROR: Missing argument '$sParam'\n"); UsageAndExit($oP); } @@ -61,10 +63,13 @@ function UsageAndExit($oP) { $bModeCLI = ($oP instanceof CLIPage); - if ($bModeCLI) { + if ($bModeCLI) + { $oP->p("USAGE:\n"); $oP->p("php cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1] [--debug=1] [--status_only=1]\n"); - } else { + } + else + { $oP->p("Optional parameters: verbose, param_file, status_only\n"); } $oP->output(); @@ -88,7 +93,7 @@ function UsageAndExit($oP) function RunTask(BackgroundTask $oTask, $iTimeLimit) { $TaskClass = $oTask->Get('class_name'); - $oProcess = new $TaskClass(); + $oProcess = new $TaskClass; $oRefClass = new ReflectionClass(get_class($oProcess)); $oDateStarted = new DateTime(); $oDatePlanned = new DateTime($oTask->Get('next_run_date')); @@ -97,7 +102,8 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) $sMessage = ''; $oExceptionToThrow = null; - try { + try + { // Record (when starting) that this task was started, just in case it crashes during the execution $oTask->Set('latest_run_date', $oDateStarted->format('Y-m-d H:i:s')); // Record the current user running the cron @@ -107,30 +113,42 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) // Time in seconds allowed to the task $iCurrTimeLimit = $iTimeLimit; // Compute allowed time - if ($oRefClass->implementsInterface('iScheduledProcess') === false) { + if ($oRefClass->implementsInterface('iScheduledProcess') === false) + { // Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity()) $iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time'); $iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime; // If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0 - if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) { + if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) + { $iCurrTimeLimit = $iTaskLimit; } } $sMessage = $oProcess->Process($iCurrTimeLimit); $oTask->Set('running', 0); - } catch (MySQLHasGoneAwayException $e) { + } + catch (MySQLHasGoneAwayException $e) + { throw $e; - } catch (ProcessFatalException $e) { + } + catch (ProcessFatalException $e) + { $oExceptionToThrow = $e; - } catch (Exception $e) { // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running - if ($oTask->IsDebug()) { - $sMessage = 'Processing failed with message: '.$e->getMessage().'. '.$e->getTraceAsString(); - } else { - $sMessage = 'Processing failed with message: '.$e->getMessage(); + } + catch (Exception $e) // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running + { + if ($oTask->IsDebug()) + { + $sMessage = 'Processing failed with message: '. $e->getMessage() . '. ' . $e->getTraceAsString(); + } + else + { + $sMessage = 'Processing failed with message: '. $e->getMessage(); } } $fDuration = microtime(true) - $fStart; - if ($oTask->Get('total_exec_count') == 0) { + if ($oTask->Get('total_exec_count') == 0) + { // First execution $oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s')); } @@ -140,16 +158,20 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) $oDateEnded = new DateTime(); $oTask->Set('latest_run_date', $oDateEnded->format('Y-m-d H:i:s')); - if ($oRefClass->implementsInterface('iScheduledProcess')) { + if ($oRefClass->implementsInterface('iScheduledProcess')) + { // Schedules process do repeat at specific moments $oPlannedStart = $oProcess->GetNextOccurrence(); - } else { + } + else + { // Background processes do repeat periodically $oPlannedStart = clone $oDatePlanned; // Let's schedule from the previous planned date of execution to avoid shift $oPlannedStart->modify($oProcess->GetPeriodicity().' seconds'); $oEnd = new DateTime(); - while ($oPlannedStart->format('U') < $oEnd->format('U')) { + while ($oPlannedStart->format('U') < $oEnd->format('U')) + { // Next planned start is already in the past, increase it again by a period $oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds'); } @@ -158,7 +180,8 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) $oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s')); $oTask->DBUpdate(); - if ($oExceptionToThrow) { + if ($oExceptionToThrow) + { throw $oExceptionToThrow; } @@ -184,21 +207,23 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) * @throws \OQLException * @throws \ReflectionException */ -function CronExec($oP, $bVerbose, $bDebug = false) +function CronExec($oP, $bVerbose, $bDebug=false) { $iStarted = time(); $iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time'); $iTimeLimit = $iStarted + $iMaxDuration; $iCronSleep = MetaModel::GetConfig()->Get('cron_sleep'); - if ($bVerbose) { + if ($bVerbose) + { $oP->p("Planned duration = $iMaxDuration seconds"); $oP->p("Loop pause = $iCronSleep seconds"); } ReSyncProcesses($oP, $bVerbose, $bDebug); - while (time() < $iTimeLimit) { + while (time() < $iTimeLimit) + { CheckMaintenanceMode($oP); $oNow = new DateTime(); @@ -209,19 +234,23 @@ function CronExec($oP, $bVerbose, $bDebug = false) $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); $bWorkDone = false; - if ($oTasks->CountExceeds(0)) { + if ($oTasks->CountExceeds(0)) + { $bWorkDone = true; - $aTasks = []; - if ($bVerbose) { + $aTasks = array(); + if ($bVerbose) + { $sCount = $oTasks->Count(); $oP->p("$sCount Tasks planned to run now ($sNow):"); $oP->p('+---------------------------+---------+---------------------+---------------------+'); $oP->p('| Task Class | Status | Last Run | Next Run |'); $oP->p('+---------------------------+---------+---------------------+---------------------+'); } - while ($oTask = $oTasks->Fetch()) { + while ($oTask = $oTasks->Fetch()) + { $aTasks[$oTask->Get('class_name')] = $oTask; - if ($bVerbose) { + if ($bVerbose) + { $sTaskName = $oTask->Get('class_name'); $sStatus = $oTask->Get('status'); $sLastRunDate = $oTask->Get('latest_run_date'); @@ -229,11 +258,13 @@ function CronExec($oP, $bVerbose, $bDebug = false) $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate)); } } - if ($bVerbose) { + if ($bVerbose) + { $oP->p('+---------------------------+---------+---------------------+---------------------+'); } $aRunTasks = []; - foreach ($aTasks as $oTask) { + foreach ($aTasks as $oTask) + { $sTaskClass = $oTask->Get('class_name'); $aRunTasks[] = $sTaskClass; @@ -242,54 +273,66 @@ function CronExec($oP, $bVerbose, $bDebug = false) CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)"); // Run the task and record its next run time - if ($bVerbose) { + if ($bVerbose) + { $oNow = new DateTime(); $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=49s", ' '.$sTaskClass.' ')); } - try { + try + { $sMessage = RunTask($aTasks[$sTaskClass], $iTimeLimit); - } catch (MySQLHasGoneAwayException $e) { + } catch (MySQLHasGoneAwayException $e) + { $oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")"); exit(EXIT_CODE_FATAL); - } catch (ProcessFatalException $e) { + } catch (ProcessFatalException $e) + { $oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")"); IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')'); } - if ($bVerbose) { - if (!empty($sMessage)) { + if ($bVerbose) + { + if (!empty($sMessage)) + { $oP->p("$sTaskClass: $sMessage"); } $oEnd = new DateTime(); $sNextRunDate = $oTask->Get('next_run_date'); $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=42s", ' '.$sTaskClass.' ')." Next: $sNextRunDate"); } - if (time() > $iTimeLimit) { + if (time() > $iTimeLimit) + { break 2; } CheckMaintenanceMode($oP); } // Tasks to run later - if ($bVerbose) { + if ($bVerbose) + { $oP->p('--'); $oSearch = new DBObjectSearch('BackgroundTask'); $oSearch->AddCondition('next_run_date', $sNow, '>'); $oSearch->AddCondition('status', 'active'); $oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]); - while ($oTask = $oTasks->Fetch()) { - if (!in_array($oTask->Get('class_name'), $aRunTasks)) { + while ($oTask = $oTasks->Fetch()) + { + if (!in_array($oTask->Get('class_name'), $aRunTasks)) + { $oP->p(sprintf("-- Skipping task: %-'-40s", $oTask->Get('class_name').' ')." until: ".$oTask->Get('next_run_date')); } } } } - if ($bVerbose && $bWorkDone) { + if ($bVerbose && $bWorkDone) + { $oP->p("Sleeping...\n"); } sleep($iCronSleep); } - if ($bVerbose) { + if ($bVerbose) + { $oP->p(''); DisplayStatus($oP, ['next_run_date' => true]); $oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)"); @@ -299,9 +342,8 @@ function CronExec($oP, $bVerbose, $bDebug = false) /** * @param WebPage $oP */ -function CheckMaintenanceMode(Page $oP) -{ - // Verify files instead of reloading the full config each time +function CheckMaintenanceMode(Page $oP) { +// Verify files instead of reloading the full config each time if (file_exists(MAINTENANCE_MODE_FILE) || file_exists(READONLY_MODE_FILE)) { $oP->p("Maintenance detected, exiting"); exit(EXIT_CODE_ERROR); @@ -325,22 +367,16 @@ function DisplayStatus($oP, $aTaskOrderBy = []) $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); $oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |'); $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); - while ($oTask = $oTasks->Fetch()) { + while ($oTask = $oTasks->Fetch()) + { $sTaskName = $oTask->Get('class_name'); $sStatus = $oTask->Get('status'); $sLastRunDate = $oTask->Get('latest_run_date'); $sNextRunDate = $oTask->Get('next_run_date'); $iNbRun = (int)$oTask->Get('total_exec_count'); $sAverageRunTime = $oTask->Get('average_run_duration'); - $oP->p(sprintf( - '| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', - $sTaskName, - $sStatus, - $sLastRunDate, - $sNextRunDate, - $iNbRun, - $sAverageRunTime - )); + $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', $sTaskName, $sStatus, + $sLastRunDate, $sNextRunDate, $iNbRun, $sAverageRunTime)); } $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+'); } @@ -365,19 +401,22 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) // $oSearch = new DBObjectSearch('BackgroundTask'); $oTasks = new DBObjectSet($oSearch); - $aTasks = []; - while ($oTask = $oTasks->Fetch()) { + $aTasks = array(); + while ($oTask = $oTasks->Fetch()) + { $aTasks[$oTask->Get('class_name')] = $oTask; } $oNow = new DateTime(); - $aProcesses = []; - foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProcess::class) as $sTaskClass) { - $oProcess = new $sTaskClass(); + $aProcesses = array(); + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iProcess::class) as $sTaskClass) + { + $oProcess = new $sTaskClass; $aProcesses[$sTaskClass] = $oProcess; // Create missing entry if needed - if (!array_key_exists($sTaskClass, $aTasks)) { + if (!array_key_exists($sTaskClass, $aTasks)) + { // New entry, let's create a new BackgroundTask record, and plan the first execution $oTask = new BackgroundTask(); $oTask->SetDebug($bDebug); @@ -387,32 +426,41 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) $oTask->Set('max_run_duration', 0); $oTask->Set('average_run_duration', 0); $oRefClass = new ReflectionClass($sTaskClass); - if ($oRefClass->implementsInterface('iScheduledProcess')) { + if ($oRefClass->implementsInterface('iScheduledProcess')) + { $oNextOcc = $oProcess->GetNextOccurrence(); $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); - } else { + } + else + { // Background processes do start asap, i.e. "now" $oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); } - if ($bVerbose) { + if ($bVerbose) + { $oP->p('Creating record for: '.$sTaskClass); $oP->p('First execution planned at: '.$oTask->Get('next_run_date')); } $oTask->DBInsert(); - } else { + } + else + { /** @var \BackgroundTask $oTask */ $oTask = $aTasks[$sTaskClass]; - if ($oTask->Get('next_run_date') == '3000-01-01 00:00:00') { + if ($oTask->Get('next_run_date') == '3000-01-01 00:00:00') + { // check for rescheduled tasks $oRefClass = new ReflectionClass($sTaskClass); - if ($oRefClass->implementsInterface('iScheduledProcess')) { + if ($oRefClass->implementsInterface('iScheduledProcess')) + { $oNextOcc = $oProcess->GetNextOccurrence(); $oTask->Set('next_run_date', $oNextOcc->format('Y-m-d H:i:s')); $oTask->DBUpdate(); } } // Reactivate task if necessary - if ($oTask->Get('status') == 'removed') { + if ($oTask->Get('status') == 'removed') + { $oTask->Set('status', 'active'); $oTask->DBUpdate(); } @@ -422,17 +470,21 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) } // Remove all the tasks not having a valid class - foreach ($aTasks as $oTask) { + foreach ($aTasks as $oTask) + { $sTaskClass = $oTask->Get('class_name'); - if (!class_exists($sTaskClass)) { + if (!class_exists($sTaskClass)) + { $oTask->Set('status', 'removed'); $oTask->DBUpdate(); } } - if ($bVerbose) { - $aDisplayProcesses = []; - foreach ($aProcesses as $oExecInstance) { + if ($bVerbose) + { + $aDisplayProcesses = array(); + foreach ($aProcesses as $oExecInstance) + { $aDisplayProcesses[] = get_class($oExecInstance); } $sDisplayProcesses = implode(', ', $aDisplayProcesses); @@ -448,45 +500,58 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) set_time_limit(0); // Some background actions may really take long to finish (like backup) $bIsModeCLI = utils::IsModeCLI(); -if ($bIsModeCLI) { +if ($bIsModeCLI) +{ $oP = new CLIPage("iTop - cron"); SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); -} else { +} +else +{ $oP = new WebPage("iTop - cron"); } -try { +try +{ utils::UseParamFile(); $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */); $bDebug = utils::ReadParam('debug', false, true /* Allow CLI */); - if ($bIsModeCLI) { + if ($bIsModeCLI) + { // Next steps: // specific arguments: 'csv file' // $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) + { UserRights::Login($sAuthUser); // Login & set the user's language - } else { + } + else + { $oP->p("Access wrong credentials ('$sAuthUser')"); $oP->output(); exit(EXIT_CODE_ERROR); } - } else { + } + else + { require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed } - if (!UserRights::IsAdministrator()) { + if (!UserRights::IsAdministrator()) + { $oP->p("Access restricted to administrators"); $oP->Output(); exit(EXIT_CODE_ERROR); } - if (utils::ReadParam('status_only', false, true /* Allow CLI */)) { + + if (utils::ReadParam('status_only', false, true /* Allow CLI */)) + { // Display status and exit DisplayStatus($oP); exit(0); @@ -494,36 +559,54 @@ function ReSyncProcesses($oP, $bVerbose, $bDebug) require_once(APPROOT.'core/mutex.class.inc.php'); $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')'); -} catch (Exception $e) { +} +catch (Exception $e) +{ $oP->p("Error: ".$e->GetMessage()); $oP->output(); exit(EXIT_CODE_FATAL); } -try { +try +{ $oMutex = new iTopMutex('cron'); - if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) + { $oP->p("A maintenance is ongoing"); - } else { - if ($oMutex->TryLock()) { + } + else + { + if ($oMutex->TryLock()) + { CronExec($oP, $bVerbose, $bDebug); - } else { + } + else + { // Exit silently $oP->p("Already running..."); } } -} catch (Exception $e) { +} +catch (Exception $e) +{ $oP->p("ERROR: '".$e->getMessage()."'"); - if ($bDebug) { + if ($bDebug) + { // Might contain verb parameters such a password... $oP->p($e->getTraceAsString()); } -} finally { - try { +} +finally +{ + try + { $oMutex->Unlock(); - } catch (Exception $e) { + } + catch (Exception $e) + { $oP->p("ERROR: '".$e->getMessage()."'"); - if ($bDebug) { + if ($bDebug) + { // Might contain verb parameters such a password... $oP->p($e->getTraceAsString()); } diff --git a/webservices/export-v2.php b/webservices/export-v2.php index ad268232ce..a902bf0442 100644 --- a/webservices/export-v2.php +++ b/webservices/export-v2.php @@ -1,5 +1,4 @@ p('ERROR: '.utils::HtmlEntities($sErrorMessage)); $oP->output(); exit(EXIT_CODE_ERROR); - } else { + } + else + { $oP = new WebPage("iTop - Export"); $oP->add_http_headers(); $oP->p('ERROR: '.utils::HtmlEntities($sErrorMessage)); @@ -52,13 +54,15 @@ function ReportErrorAndExit($sErrorMessage) function ReportErrorAndUsage($sErrorMessage) { - if (utils::IsModeCLI()) { + if (utils::IsModeCLI()) + { $oP = new CLIPage("iTop - Export"); $oP->p('ERROR: '.$sErrorMessage); Usage($oP); $oP->output(); exit(EXIT_CODE_ERROR); - } else { + } + else { $oP = new WebPage("iTop - Export"); $oP->add_http_headers(); $oP->p('ERROR: '.$sErrorMessage); @@ -70,32 +74,42 @@ function ReportErrorAndUsage($sErrorMessage) function Usage(Page $oP) { - if (Utils::IsModeCLI()) { + if (Utils::IsModeCLI()) + { $oP->p('Usage: php '.basename(__FILE__).' --auth_user= --auth_pwd= --expression= --query= [--arg_xxx=] [--no_localize=0|1] [--format=] [--format-options...]'); $oP->p("Parameters:"); $oP->p(" * auth_user: the iTop user account for authentication"); $oP->p(" * auth_pwd: the password of the iTop user account"); - } else { + } + else + { $oP->p("Parameters:"); } $oP->p(" * expression: an OQL expression (e.g. SELECT Contact WHERE name LIKE 'm%')"); $oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook"); - if (Utils::IsModeCLI()) { + if (Utils::IsModeCLI()) + { $oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects"); - } else { + } + else + { $oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects"); } $oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'"); $aSupportedFormats = BulkExport::FindSupportedFormats(); $oP->p(" * format: (optional, default is html) the desired output format. Can be one of '".implode("', '", array_keys($aSupportedFormats))."'"); - foreach ($aSupportedFormats as $sFormatCode => $sLabel) { + foreach($aSupportedFormats as $sFormatCode => $sLabel) + { $oExporter = BulkExport::FindExporter($sFormatCode); - if ($oExporter !== null) { - if (!Utils::IsModeCLI()) { + if ($oExporter !== null) + { + if (!Utils::IsModeCLI()) + { $oP->add('
'); } $oExporter->DisplayUsage($oP); - if (!Utils::IsModeCLI()) { + if (!Utils::IsModeCLI()) + { $oP->add(''); } } @@ -234,18 +248,19 @@ function FormatDatesInPreview(sRadioSelector, sPreviewSelector) $sExpressionError = ''; if (($sExpression === null) && ($sQueryId === null)) { $bExpressionIsValid = false; - } elseif ($sExpression !== '') { + } else if ($sExpression !== '') { try { $oExportSearch = DBObjectSearch::FromOQL($sExpression); $oExportSearch->UpdateContextFromUser(); - } catch (OQLException $e) { + } + catch (OQLException $e) { $bExpressionIsValid = false; $sExpressionError = $e->getMessage(); } } if (!$bExpressionIsValid) { - DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError, $oForm); + DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError,$oForm); return; } @@ -260,12 +275,13 @@ function FormatDatesInPreview(sRadioSelector, sPreviewSelector) $oExportSearch->UpdateContextFromUser(); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", $sQueryId)); } - $aFormPartsByFormat = []; - $aAllFormParts = []; + $aFormPartsByFormat = array(); + $aAllFormParts = array(); if ($sFormat == null) { // No specific format chosen $sDefaultFormat = utils::ReadParam('format', 'xlsx'); + $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel("format", Dict::S('Core:BulkExport:ExportFormatPrompt'), "format_selector"); $oSelect->SetIsLabelBefore(true); $oForm->AddSubBlock($oSelect); @@ -374,7 +390,7 @@ function InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode) if ($sExpression === null) { // No expression supplied, let's check if phrasebook entry is given if ($sQueryId !== null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); $oSearch->UpdateContextFromUser(); $oQueries = new DBObjectSet($oSearch); if ($oQueries->Count() > 0) { @@ -396,6 +412,7 @@ function InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode) } } + if ($sFormat !== null) { $oExporter = BulkExport::FindExporter($sFormat); if ($oExporter === null) { @@ -435,7 +452,7 @@ function CheckParameters($sExpression, $sQueryId, $sFormat) // Either $sExpression or $sQueryId must be specified if ($sExpression === null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); $oSearch->UpdateContextFromUser(); $oQueries = new DBObjectSet($oSearch); if ($oQueries->Count() > 0) { @@ -453,7 +470,7 @@ function CheckParameters($sExpression, $sQueryId, $sFormat) try { $oSearch = DBObjectSearch::FromOQL($sExpression); $oSearch->UpdateContextFromUser(); - $aArgs = []; + $aArgs = array(); foreach ($oSearch->GetQueryParams() as $sParam => $foo) { $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); if (!is_null($value)) { @@ -466,23 +483,30 @@ function CheckParameters($sExpression, $sQueryId, $sFormat) $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); $oExporter = BulkExport::FindExporter($sFormat, $oSearch); - if ($oExporter == null) { + if ($oExporter == null) + { $aSupportedFormats = BulkExport::FindSupportedFormats(); ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); } - } catch (MissingQueryArgument $e) { + } + catch(MissingQueryArgument $e) + { $oSearch = null; ReportErrorAndUsage("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); - } catch (OQLException $e) { + } + catch(OQLException $e) + { $oSearch = null; ReportErrorAndExit("Invalid OQL query: '".utils::HtmlEntities($sExpression)."'.\n".utils::HtmlEntities($e->getMessage())); - } catch (Exception $e) { + } + catch(Exception $e) + { $oSearch = null; ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); } // update last export information if check parameters ok - if ($oQuery != null) { + if($oQuery != null){ $oQuery->UpdateLastExportInformation(); } @@ -498,18 +522,24 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) { $oExporter->SetHttpHeaders($oP); $exportResult = $oExporter->GetHeader(); - $aStatus = []; - do { + $aStatus = array(); + do + { $exportResult .= $oExporter->GetNextChunk($aStatus); - } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + } + while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - if ($aStatus['code'] == 'error') { + if ($aStatus['code'] == 'error') + { $oExporter->Cleanup(); ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); - } else { + } + else + { $exportResult .= $oExporter->GetFooter(); $sMimeType = $oExporter->GetMimeType(); - if (substr($sMimeType, 0, 5) == 'text/') { + if (substr($sMimeType, 0, 5) == 'text/') + { $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); } $oP->SetContentType($sMimeType); @@ -519,6 +549,7 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) } } + ///////////////////////////////////////////////////////////////////////////// // // Command Line mode @@ -536,7 +567,8 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) try { // Do this before loging, in order to allow setting user credentials from within the file utils::UseParamFile(); - } catch (Exception $e) { + } + catch (Exception $e) { echo "Error: ".utils::HtmlEntities($e->getMessage())."
\n"; exit(EXIT_CODE_FATAL); } @@ -568,7 +600,7 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) } if ($sExpression === null) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); $oSearch->UpdateContextFromUser(); $oQueries = new DBObjectSet($oSearch); if ($oQueries->Count() > 0) { @@ -581,7 +613,7 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) try { $oSearch = DBObjectSearch::FromOQL($sExpression); $oSearch->UpdateContextFromUser(); - $aArgs = []; + $aArgs = array(); foreach ($oSearch->GetQueryParams() as $sParam => $foo) { $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); if (!is_null($value)) { @@ -594,7 +626,8 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) $sFormat = utils::ReadParam('format', 'html', true /* Allow CLI */, 'raw_data'); $oExporter = BulkExport::FindExporter($sFormat); - if ($oExporter == null) { + if ($oExporter == null) + { $aSupportedFormats = BulkExport::FindSupportedFormats(); ReportErrorAndExit("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); } @@ -605,25 +638,36 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) $oExporter->ReadParameters(); $exportResult = $oExporter->GetHeader(); - $aStatus = []; + $aStatus = array(); - do { + do + { $exportResult .= $oExporter->GetNextChunk($aStatus); - } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + } + while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - if ($aStatus['code'] == 'error') { + if ($aStatus['code'] == 'error') + { ReportErrorAndExit("Export failed: '{$aStatus['message']}'"); - } else { + } + else + { $exportResult .= $oExporter->GetFooter(); echo $exportResult; } $oExporter->Cleanup(); - } catch (MissingQueryArgument $e) { + } + catch(MissingQueryArgument $e) + { ReportErrorAndUsage("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); - } catch (OQLException $e) { + } + catch(OQLException $e) + { ReportErrorAndExit("Invalid OQL query: '$sExpression'.\n".utils::HtmlEntities($e->getMessage())); - } catch (Exception $e) { + } + catch(Exception $e) + { ReportErrorAndExit(utils::HtmlEntities($e->getMessage())); } @@ -636,7 +680,8 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) // ///////////////////////////////////////////////////////////////////////////// -try { +try +{ require_once(APPROOT.'/application/loginwebpage.class.inc.php'); // Main parameters @@ -677,12 +722,14 @@ function DoExport(WebPage $oP, BulkExport $oExporter, $bInteractive = false) DoExport($oP, $oExporter, false); $oP->output(); } -} catch (BulkExportMissingParameterException $e) { +} +catch (BulkExportMissingParameterException $e) { $oP = new AjaxPage('iTop Export'); $oP->add(utils::HtmlEntities($e->getMessage())); Usage($oP); $oP->output(); -} catch (Exception $e) { +} +catch (Exception $e) { $oP = new WebPage('iTop Export'); $oP->add_http_headers(); $oP->add('Error: '.utils::HtmlEntities($e->getMessage())); diff --git a/webservices/export.php b/webservices/export.php index 18fa2b441e..3a43ce4089 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -1,5 +1,4 @@ GetMessage()."
\n"; exit(EXIT_CODE_FATAL); } @@ -54,31 +59,39 @@ * @since 3.1.0 N°6047 */ $oCtx = new ContextTag(ContextTag::TAG_EXPORT); -if (utils::IsModeCLI()) { +if (utils::IsModeCLI()) +{ $oP = new CLIPage("iTop - Export"); SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL); $sAuthUser = utils::ReadParam('auth_user', null, true /* Allow CLI */, 'raw_data'); $sAuthPwd = utils::ReadParam('auth_pwd', null, true /* Allow CLI */, 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) + { UserRights::Login($sAuthUser); // Login & set the user's language - } else { + } + else + { $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); $oP->output(); exit(EXIT_CODE_ERROR); } -} else { +} +else +{ require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed } ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); + $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); -if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) { +if (utils::IsArchiveMode() && !UserRights::CanBrowseArchive()) +{ $oP = new CLIPage("iTop - Export"); $oP->p("The user account is not authorized to access the archives"); $oP->output(); @@ -95,15 +108,19 @@ $oQuery = null; -if (strlen($sExpression) == 0) { +if (strlen($sExpression) == 0) +{ $sQueryId = trim(utils::ReadParam('query', '', true /* Allow CLI */, 'raw_data')); - if (strlen($sQueryId) > 0) { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); + if (strlen($sQueryId) > 0) + { + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) { + if ($oQueries->Count() > 0) + { $oQuery = $oQueries->Fetch(); $sExpression = $oQuery->Get('oql'); - if (strlen($sFields) == 0) { + if (strlen($sFields) == 0) + { $sFields = trim($oQuery->Get('fields')); } } @@ -112,29 +129,38 @@ $sFormat = strtolower(utils::ReadParam('format', 'html', true /* Allow CLI */)); + $aFields = explode(',', $sFields); // Clean the list of columns (empty it if every string is empty) -foreach ($aFields as $index => $sField) { +foreach($aFields as $index => $sField) +{ $aFields[$index] = trim($sField); - if (strlen($aFields[$index]) == 0) { + if(strlen($aFields[$index]) == 0) + { unset($aFields[$index]); } } $oP = null; -if (!empty($sExpression)) { - try { +if (!empty($sExpression)) +{ + try + { $oFilter = DBObjectSearch::FromOQL($sExpression); // Check and adjust column names // - $aAliasToFields = []; - foreach ($aFields as $index => $sField) { - if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches)) { + $aAliasToFields = array(); + foreach($aFields as $index => $sField) + { + if (preg_match('/^(.*)\.(.*)$/', $sField, $aMatches)) + { $sClassAlias = $aMatches[1]; $sAttCode = $aMatches[2]; - } else { + } + else + { $sClassAlias = $oFilter->GetClassAlias(); $sAttCode = $sField; // Disambiguate the class alias @@ -143,13 +169,17 @@ $aAliasToFields[$sClassAlias][] = $sAttCode; $sClass = $oFilter->GetClassName($sClassAlias); - if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) { + if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) + { throw new CoreException("Invalid field specification $sField: $sAttCode is not a valid attribute for $sClass"); } $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef instanceof AttributeSubItem) { + if ($oAttDef instanceof AttributeSubItem) + { $aAliasToFields[$sClassAlias][] = $oAttDef->GetParentAttCode(); - } elseif ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) { + } + else if($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) + { $sKeyAttCode = $oAttDef->GetKeyAttCode(); $aAliasToFields[$sClassAlias][] = $sKeyAttCode; } @@ -157,29 +187,35 @@ // Read query parameters // - $aArgs = []; - foreach ($oFilter->GetQueryParams() as $sParam => $foo) { + $aArgs = array(); + foreach($oFilter->GetQueryParams() as $sParam => $foo) + { $value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data'); - if (!is_null($value)) { + if (!is_null($value)) + { $aArgs[$sParam] = $value; } } $oFilter->SetInternalParams($aArgs); - foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass) { - if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0) { + foreach ($oFilter->GetSelectedClasses() as $sAlias => $sClass) + { + if ((UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_READ) && UR_ALLOWED_YES) == 0) + { throw new Exception("The current user does not have permission for exporting data of class $sClass"); } } // update last export information if check parameters ok - if ($oQuery != null) { + if($oQuery != null){ $oQuery->UpdateLastExportInformation(); } - if ($oFilter) { - $oSet = new CMDBObjectSet($oFilter, [], $aArgs); + if ($oFilter) + { + $oSet = new CMDBObjectSet($oFilter, array(), $aArgs); $oSet->OptimizeColumnLoad($aAliasToFields); - switch ($sFormat) { + switch($sFormat) + { case 'html': $oP = new NiceWebPage("iTop - Export"); $oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed @@ -207,7 +243,7 @@ $bViewLink = false; } $sFields = implode(',', $aFields); - $aExtraParams = [ + $aExtraParams = array( 'menu' => false, 'toolkit_menu' => false, 'display_limit' => false, @@ -215,15 +251,15 @@ 'zlist' => false, 'extra_fields' => $sFields, 'view_link' => $bViewLink, - ]; + ); } else { - $aExtraParams = [ + $aExtraParams = array( 'menu' => false, 'toolkit_menu' => false, 'display_limit' => false, 'localize_values' => $bLocalize, 'zlist' => 'details', - ]; + ); } $oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams); @@ -231,85 +267,105 @@ break; case 'csv': - $oP = new CSVPage("iTop - Export"); - $sFields = implode(',', $aFields); - $sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data'); - $sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize], $sCharset); - if ($sCharset == 'UTF-8') { - $sOutputData = UTF8_BOM.$sCSVData; - } else { - $sOutputData = $sCSVData; - } - if ($sFileName == '') { - // Plain text => Firefox will NOT propose to download the file - $oP->add_header("Content-type: text/plain; charset=$sCharset"); - } else { - $oP->add_header("Content-type: text/csv; charset=$sCharset"); - } - $oP->add($sOutputData); - break; + $oP = new CSVPage("iTop - Export"); + $sFields = implode(',', $aFields); + $sCharset = utils::ReadParam('charset', MetaModel::GetConfig()->Get('csv_file_default_charset'), true /* Allow CLI */, 'raw_data'); + $sCSVData = cmdbAbstractObject::GetSetAsCSV($oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize), $sCharset); + if ($sCharset == 'UTF-8') + { + $sOutputData = UTF8_BOM.$sCSVData; + } + else + { + $sOutputData = $sCSVData; + } + if ($sFileName == '') + { + // Plain text => Firefox will NOT propose to download the file + $oP->add_header("Content-type: text/plain; charset=$sCharset"); + } + else + { + $oP->add_header("Content-type: text/csv; charset=$sCharset"); + } + $oP->add($sOutputData); + break; case 'spreadsheet': - $oP = new WebPage("iTop - Export for spreadsheet"); + $oP = new WebPage("iTop - Export for spreadsheet"); - // Integration within MS-Excel web queries + HTTPS + IIS: - // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS - // Then the fix is to force the reset of header values Pragma and Cache-control - header("Pragma:", true); - header("Cache-control:", true); + // Integration within MS-Excel web queries + HTTPS + IIS: + // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS + // Then the fix is to force the reset of header values Pragma and Cache-control + header("Pragma:", true); + header("Cache-control:", true); - $sFields = implode(',', $aFields); - $oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell ! - cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, ['fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize]); - break; + $sFields = implode(',', $aFields); + $oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell ! + cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize)); + break; case 'xml': - $oP = new XMLPage("iTop - Export", true /* passthrough */); - cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, ['localize_values' => $bLocalize]); - break; + $oP = new XMLPage("iTop - Export", true /* passthrough */); + cmdbAbstractObject::DisplaySetAsXML($oP, $oSet, array('localize_values' => $bLocalize)); + break; case 'xlsx': - $oP = new AjaxPage(''); - $oExporter = new ExcelExporter(); - $oExporter->SetObjectList($oFilter); - - // Run the export by chunk of 1000 objects to limit memory usage - $oExporter->SetChunkSize(1000); - do { - $aStatus = $oExporter->Run(); // process one chunk - } while (($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); - - if ($aStatus['code'] == 'done') { - $oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - $oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx'); - $oP->add(file_get_contents($oExporter->GetExcelFilePath())); - $oExporter->Cleanup(); - } else { - $oP->add('Error, xlsx export failed: '.$aStatus['message']); - } - break; + $oP = new AjaxPage(''); + $oExporter = new ExcelExporter(); + $oExporter->SetObjectList($oFilter); + + // Run the export by chunk of 1000 objects to limit memory usage + $oExporter->SetChunkSize(1000); + do + { + $aStatus = $oExporter->Run(); // process one chunk + } + while( ($aStatus['code'] != 'done') && ($aStatus['code'] != 'error')); + + if ($aStatus['code'] == 'done') + { + $oP->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $oP->SetContentDisposition('attachment', $oFilter->GetClass().'.xlsx'); + $oP->add(file_get_contents($oExporter->GetExcelFilePath())); + $oExporter->Cleanup(); + } + else + { + $oP->add('Error, xlsx export failed: '.$aStatus['message']); + } + break; default: - $oP = new WebPage("iTop - Export"); - $oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml."); + $oP = new WebPage("iTop - Export"); + $oP->add("Unsupported format '$sFormat'. Possible values are: html, csv, spreadsheet or xml."); } } - } catch (Exception $e) { + } + catch(Exception $e) + { $oP = new WebPage("iTop - Export"); $oP->p("Error the query can not be executed."); - if ($e instanceof CoreException) { + if ($e instanceof CoreException) + { $oP->p($e->GetHtmlDesc()); - } else { + } + else + { $oP->p($e->getMessage()); } } } -if (!$oP) { +if (!$oP) +{ // Display a short message about how to use this page $bModeCLI = utils::IsModeCLI(); - if ($bModeCLI) { + if ($bModeCLI) + { $oP = new CLIPage("iTop - Export"); - } else { + } + else + { $oP = new WebPage("iTop - Export"); } $oP->p("General purpose export page."); @@ -328,9 +384,11 @@ $oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file"); } -if ($sFileName != '') { +if ($sFileName != '') +{ $oP->add_header('Content-Disposition: attachment; filename="'.$sFileName.'"'); } $oP->TrashUnexpectedOutput(); $oP->output(); +?> diff --git a/webservices/import.php b/webservices/import.php index 807011a6b5..dc009b8409 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -1,5 +1,4 @@ - [ +$aPageParams = array +( + 'auth_user' => array + ( 'mandatory' => true, 'modes' => 'cli', 'default' => null, 'description' => 'login (must have enough rights to create objects of the given class)', - ], - 'auth_pwd' => - [ + ), + 'auth_pwd' => array + ( 'mandatory' => true, 'modes' => 'cli', 'default' => null, 'description' => 'password', - ], - 'class' => - [ + ), + 'class' => array + ( 'mandatory' => true, 'modes' => 'http,cli', 'default' => null, 'description' => 'class of loaded objects', - ], - 'csvdata' => - [ + ), + 'csvdata' => array + ( 'mandatory' => true, 'modes' => 'http', 'default' => null, 'description' => 'data', - ], - 'csvfile' => - [ + ), + 'csvfile' => array + ( 'mandatory' => true, 'modes' => 'cli', 'default' => '', 'description' => 'local data file, replaces csvdata if specified', - ], - 'charset' => - [ + ), + 'charset' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '', 'description' => 'Character set encoding of the CSV data: UTF-8, ISO-8859-1, WINDOWS-1251, WINDOWS-1252, ISO-8859-15, If blank, then the charset is set to config(csv_file_default_charset)', - ], - 'date_format' => - [ + ), + 'date_format' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '', 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)', - ], - 'separator' => - [ + ), + 'separator' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => ',', 'description' => 'column separator in CSV data (1 char, or \'tab\')', - ], - 'qualifier' => - [ + ), + 'qualifier' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '"', 'description' => 'test qualifier in CSV data', - ], - 'output' => - [ + ), + 'output' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => 'summary', 'description' => '[retcode] to return the count of lines in error, [summary] to return a concise report, [details] to get a detailed report (each line listed)', - ], + ), /* 'reportlevel' => array ( @@ -121,35 +121,35 @@ class BulkLoadException extends Exception 'description' => 'combination of flags to limit the detailed output', ), */ - 'reconciliationkeys' => - [ + 'reconciliationkeys' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '', 'description' => 'name of the columns used to identify existing objects and update them, or create a new one', - ], - 'simulate' => - [ + ), + 'simulate' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '0', 'description' => 'If set to 1, then the load will not be executed, but the expected report will be produced', - ], - 'comment' => - [ + ), + 'comment' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '', 'description' => 'Comment to be added into the change log', - ], - 'no_localize' => - [ + ), + 'no_localize' => array + ( 'mandatory' => false, 'modes' => 'http,cli', 'default' => '0', 'description' => 'If set to 0, then header and values are supposed to be localized in the language of the logged in user. Set to 1 to use internal attribute codes and values (enums)', - ], -]; + ), +); function UsageAndExit($oP) { @@ -157,15 +157,21 @@ function UsageAndExit($oP) $bModeCLI = utils::IsModeCLI(); $oP->p("USAGE:\n"); - foreach ($aPageParams as $sParam => $aParamData) { + foreach($aPageParams as $sParam => $aParamData) + { $aModes = explode(',', $aParamData['modes']); - if ($bModeCLI) { - if (in_array('cli', $aModes)) { + if ($bModeCLI) + { + if (in_array('cli', $aModes)) + { $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); $oP->p("$sParam = $sDesc"); } - } else { - if (in_array('http', $aModes)) { + } + else + { + if (in_array('http', $aModes)) + { $sDesc = $aParamData['description'].', '.($aParamData['mandatory'] ? 'mandatory' : 'optional, defaults to ['.$aParamData['default'].']'); $oP->p("$sParam = $sDesc"); } @@ -175,6 +181,7 @@ function UsageAndExit($oP) exit; } + function ReadParam($oP, $sParam, $sSanitizationFilter = 'parameter') { global $aPageParams; @@ -191,7 +198,8 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) assert($aPageParams[$sParam]['mandatory']); $sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter); - if (is_null($sValue)) { + if (is_null($sValue)) + { $oP->p("ERROR: Missing argument '$sParam'\n"); UsageAndExit($oP); } @@ -205,81 +213,96 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) * @since 3.1.0 N°6047 */ $oCtx = new ContextTag(ContextTag::TAG_IMPORT); -if (utils::IsModeCLI()) { +if (utils::IsModeCLI()) +{ $oP = new CLIPage("iTop - Bulk import"); SetupUtils::CheckPhpAndExtensionsForCli($oP, -2); -} else { +} +else +{ $oP = new CSVPage("iTop - Bulk import"); } -try { +try +{ utils::UseParamFile(); -} catch (Exception $e) { +} +catch(Exception $e) +{ $oP->p("Error: ".$e->GetMessage()); $oP->output(); exit(-2); } -if (utils::IsModeCLI()) { +if (utils::IsModeCLI()) +{ // Next steps: // specific arguments: 'csvfile' // $sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data'); $sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data'); $sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data'); - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) + { UserRights::Login($sAuthUser); // Login & set the user's language - } else { + } + else + { $oP->p("Access restricted or wrong credentials ('$sAuthUser')"); $oP->output(); exit(-1); } - if (!is_readable($sCsvFile)) { + if (!is_readable($sCsvFile)) + { $oP->p("Input file could not be found or could not be read: '$sCsvFile'"); $oP->output(); exit(-1); } $sCSVData = file_get_contents($sCsvFile); -} else { +} +else +{ require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::ResetSession(true); + LoginWebPage::ResetSession(true); $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); - if ($iRet !== LoginWebPage::EXIT_CODE_OK) { - switch ($iRet) { - case LoginWebPage::EXIT_CODE_MISSINGLOGIN: - $oP->p("Missing parameter 'auth_user'"); - break; - - case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: - $oP->p("Missing parameter 'auth_pwd'"); - break; - - case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: - $oP->p('Invalid login'); - break; - - case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: - $oP->p('Portal user is not allowed'); - break; - - case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: - $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); - break; - - default: - $oP->p("Unknown authentication error (retCode=$iRet)"); - } - $oP->output(); - exit -1; - } + if ($iRet !== LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + $oP->p("Missing parameter 'auth_user'"); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + $oP->p("Missing parameter 'auth_pwd'"); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + $oP->p('Invalid login'); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + $oP->p('Portal user is not allowed'); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); + break; + + default: + $oP->p("Unknown authentication error (retCode=$iRet)"); + } + $oP->output(); + exit -1; + } $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); } -try { - $aWarnings = []; + +try +{ + $aWarnings = array(); ////////////////////////////////////////////////// // @@ -290,7 +313,8 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $sQualifier = ReadParam($oP, 'qualifier', 'raw_data'); $sCharSet = ReadParam($oP, 'charset', 'raw_data'); $sDateFormat = ReadParam($oP, 'date_format', 'raw_data'); - if (strpos($sDateFormat, '%') !== false) { + if (strpos($sDateFormat, '%') !== false) + { $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat); } $sOutput = ReadParam($oP, 'output', 'string'); @@ -299,7 +323,8 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $sComment = ReadParam($oP, 'comment', 'raw_data'); $bLocalize = (ReadParam($oP, 'no_localize') != 1); - if (strtolower(trim($sSep)) == 'tab') { + if (strtolower(trim($sSep)) == 'tab') + { $sSep = "\t"; } @@ -307,62 +332,77 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // // Check parameters format/consistency // - if (strlen($sCSVData) == 0) { + if (strlen($sCSVData) == 0) + { throw new BulkLoadException("Missing data - at least one line is expected"); } - if (!MetaModel::IsValidClass($sClass)) { + if (!MetaModel::IsValidClass($sClass)) + { throw new BulkLoadException("Unknown class: '$sClass'"); } - if (strlen($sSep) > 1) { + if (strlen($sSep) > 1) + { throw new BulkLoadException("Separator is limited to one character, found '$sSep'"); } - if (strlen($sQualifier) > 1) { + if (strlen($sQualifier) > 1) + { throw new BulkLoadException("Text qualifier is limited to one character, found '$sQualifier'"); } - if (!in_array($sOutput, ['retcode', 'summary', 'details'])) { + if (!in_array($sOutput, array('retcode', 'summary', 'details'))) + { throw new BulkLoadException("Unknown output format: '$sOutput'"); } - if (strlen($sDateFormat) == 0) { + if (strlen($sDateFormat) == 0) + { $sDateFormat = null; } - if ($sCharSet == '') { + if ($sCharSet == '') + { $sCharSet = MetaModel::GetConfig()->Get('csv_file_default_charset'); } - if ($sSimulate == '1') { + if ($sSimulate == '1') + { $bSimulate = true; - } else { + } + else + { $bSimulate = false; } - if (($sOutput == "summary") || ($sOutput == 'details')) { + if (($sOutput == "summary") || ($sOutput == 'details')) + { $oP->add_comment("Output format: ".$sOutput); $oP->add_comment("Class: ".$sClass); $oP->add_comment("Separator: ".$sSep); $oP->add_comment("Qualifier: ".$sQualifier); $oP->add_comment("Charset Encoding:".$sCharSet); - if (($sDateFormat !== null) && (strlen($sDateFormat) > 0)) { + if (($sDateFormat !== null) && (strlen($sDateFormat) > 0)) + { $oP->add_comment("Date and time format: '$sDateFormat'"); $oDateTimeFormat = new DateTimeFormat($sDateFormat); $sDateOnlyFormat = $oDateTimeFormat->ToDateFormat(); $oP->add_comment("Date format: '$sDateOnlyFormat'"); - } else { + } + else + { $oP->add_comment("Date format: "); } - $oP->add_comment("Localize: ".($bLocalize ? 'yes' : 'no')); + $oP->add_comment("Localize: ".($bLocalize?'yes':'no')); $oP->add_comment("Data Size: ".strlen($sCSVData)); } ////////////////////////////////////////////////// // // Security // - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY)) { + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY)) + { throw new SecurityException(Dict::Format('UI:Error:BulkModifyNotAllowedOn_Class', $sClass)); } @@ -379,31 +419,42 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // Note: it may happen that an external field has the same label as the external key // in that case, we consider that the external key has precedence // - $aKnownColumnNames = ['id' => ['id']]; - foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { - if ($bLocalize) { - $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCode)); - } else { - $sColName = strtolower($sAttCode); + $aKnownColumnNames = ['id'=>['id']]; + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if ($bLocalize) + { + $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCode)); + } + else + { + $sColName = strtolower($sAttCode); + } + if (!$oAttDef->IsExternalField() || !array_key_exists($sColName, $aKnownColumnNames)) + { + $aKnownColumnNames[$sColName][] = $sAttCode; } - if (!$oAttDef->IsExternalField() || !array_key_exists($sColName, $aKnownColumnNames)) { - $aKnownColumnNames[$sColName][] = $sAttCode; - } - if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { - $sRemoteClass = $oAttDef->GetTargetClass(); - foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) { - $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; - if ($bLocalize) { - $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCodeEx)); - } else { - $sColName = strtolower($sAttCodeEx); - } - if (!array_key_exists($sColName, $aKnownColumnNames)) { - $aKnownColumnNames[$sColName][] = $sAttCodeEx; - } - } + if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) + { + $sRemoteClass = $oAttDef->GetTargetClass(); + foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) + { + $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; + if ($bLocalize) + { + $sColName = strtolower(MetaModel::GetLabel($sClass, $sAttCodeEx)); + } + else + { + $sColName = strtolower($sAttCodeEx); + } + if (!array_key_exists($sColName, $aKnownColumnNames)) + { + $aKnownColumnNames[$sColName][] = $sAttCodeEx; + } + } } - } + } //print_r($aKnownColumnNames); //print_r(array_keys($aKnownColumnNames)); @@ -413,15 +464,19 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // // Parse first line, check attributes, analyse the request // - if ($sCharSet == 'UTF-8') { + if ($sCharSet == 'UTF-8') + { // Remove the BOM if any - if (substr($sCSVData, 0, 3) == UTF8_BOM) { + if (substr($sCSVData, 0, 3) == UTF8_BOM) + { $sCSVData = substr($sCSVData, 3); } // Clean the input // Todo: warn the user if some characters are lost/substituted $sUTF8Data = iconv('UTF-8', 'UTF-8//IGNORE//TRANSLIT', $sCSVData); - } else { + } + else + { $sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); } $oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier); @@ -432,77 +487,101 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // Translate into internal names $aFieldList = []; - foreach ($aRawFieldList as $iFieldId => $sFieldName) { + foreach($aRawFieldList as $iFieldId => $sFieldName) + { $sFieldName = trim($sFieldName); - $aMatches = []; - if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches)) { + $aMatches = array(); + if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches)) + { // Ignore any trailing "star" (*) that simply indicates a mandatory field $sFieldName = $aMatches[1]; - } elseif (preg_match('/^(.+)\*->(.+)$/', $sFieldName, $aMatches)) { + } + else if (preg_match('/^(.+)\*->(.+)$/', $sFieldName, $aMatches)) + { // Remove any trailing "star" character before the arrow (->) // A star character at the end can be used to indicate a mandatory field $sFieldName = $aMatches[1].'->'.$aMatches[2]; } - if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames)) { + if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames)) + { $aColumns = $aKnownColumnNames[strtolower($sFieldName)]; - if (count($aColumns) > 1) { - $aCompetitors = []; - foreach ($aColumns as $sAttCodeEx) { + if (count($aColumns) > 1) + { + $aCompetitors = array(); + foreach ($aColumns as $sAttCodeEx) + { $aCompetitors[] = $sAttCodeEx; } - $aWarnings[] = "Input column '$sFieldName' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; + $aWarnings[] = "Input column '$sFieldName' is ambiguous. Could be related to ".implode (' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; } $aFieldList[$iFieldId] = $aColumns[0]; - } else { + } + else + { // Protect against XSS injection - $sSafeName = str_replace(['"', '<', '>'], '', $sFieldName); + $sSafeName = str_replace(array('"', '<', '>'), '', $sFieldName); throw new BulkLoadException("Unknown column: '$sSafeName'. Possible columns: ".implode(', ', array_keys($aKnownColumnNames))); } } // Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->') - $aAttList = []; - $aExtKeys = []; - foreach ($aFieldList as $iFieldId => $sFieldName) { - $aMatches = []; - if (preg_match('/^(.+)->(.+)$/', trim($sFieldName), $aMatches)) { + $aAttList = array(); + $aExtKeys = array(); + foreach($aFieldList as $iFieldId => $sFieldName) + { + $aMatches = array(); + if (preg_match('/^(.+)->(.+)$/', trim($sFieldName), $aMatches)) + { // The column has been specified as "extkey->attcode" // $sExtKeyAttCode = $aMatches[1]; $sRemoteAttCode = $aMatches[2]; - if (!MetaModel::IsValidAttCode($sClass, $sExtKeyAttCode)) { + if (!MetaModel::IsValidAttCode($sClass, $sExtKeyAttCode)) + { // Safety net - should not happen now that column names are checked against known names throw new BulkLoadException("Unknown attribute '$sExtKeyAttCode' (class: '$sClass')"); } $oAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); - if (!$oAtt->IsExternalKey()) { + if (!$oAtt->IsExternalKey()) + { // Safety net - should not happen now that column names are checked against known names throw new BulkLoadException("Not an external key '$sExtKeyAttCode' (class: '$sClass')"); } $sTargetClass = $oAtt->GetTargetClass(); - if (!MetaModel::IsValidAttCode($sTargetClass, $sRemoteAttCode)) { + if (!MetaModel::IsValidAttCode($sTargetClass, $sRemoteAttCode)) + { // Safety net - should not happen now that column names are checked against known names throw new BulkLoadException("Unknown attribute '$sRemoteAttCode' (key: '$sExtKeyAttCode', class: '$sTargetClass')"); } $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; - } elseif ($sFieldName == 'id') { + } + elseif ($sFieldName == 'id') + { $aAttList[$sFieldName] = $iFieldId; - } else { + } + else + { // The column has been specified as "attcode" // - if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) { + if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) + { // Safety net - should not happen now that column names are checked against known names throw new BulkLoadException("Unknown attribute '$sFieldName' (class: '$sClass')"); } $oAtt = MetaModel::GetAttributeDef($sClass, $sFieldName); - if ($oAtt->IsExternalKey()) { + if ($oAtt->IsExternalKey()) + { $aExtKeys[$sFieldName]['id'] = $iFieldId; $aAttList[$sFieldName] = $iFieldId; - } elseif ($oAtt->IsExternalField()) { + } + elseif ($oAtt->IsExternalField()) + { $sExtKeyAttCode = $oAtt->GetKeyAttCode(); $sRemoteAttCode = $oAtt->GetExtAttCode(); $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; - } else { + } + else + { $aAttList[$sFieldName] = $iFieldId; } } @@ -510,20 +589,27 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // Make sure there are some reconciliation keys // - if (empty($sReconcKeys)) { - $aReconcSpec = []; + if (empty($sReconcKeys)) + { + $aReconcSpec = array(); // Base reconciliation scheme on the default one // The reconciliation attributes not present in the data will be ignored - foreach (MetaModel::GetReconcKeys($sClass) as $sReconcKeyAttCode) { - if (in_array($sReconcKeyAttCode, $aFieldList)) { - if ($bLocalize) { + foreach(MetaModel::GetReconcKeys($sClass) as $sReconcKeyAttCode) + { + if (in_array($sReconcKeyAttCode, $aFieldList)) + { + if ($bLocalize) + { $aReconcSpec[] = MetaModel::GetLabel($sClass, $sReconcKeyAttCode); - } else { + } + else + { $aReconcSpec[] = $sReconcKeyAttCode; } } } - if (count($aReconcSpec) == 0) { + if (count($aReconcSpec) == 0) + { throw new BulkLoadException("No reconciliation scheme could be defined, please add a column corresponding to one defined reconciliation key (class: '$sClass', reconciliation:".implode(',', MetaModel::GetReconcKeys($sClass)).")"); } $sReconcKeys = implode(',', $aReconcSpec); @@ -531,41 +617,48 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // Interpret the list of reconciliation keys // - $aFinalReconcilKeys = []; - $aReconcilKeysReport = []; - foreach (explode(',', $sReconcKeys) as $sReconcKey) { + $aFinalReconcilKeys = array(); + $aReconcilKeysReport = array(); + foreach (explode(',', $sReconcKeys) as $sReconcKey) + { $sReconcKey = trim($sReconcKey); - if (empty($sReconcKey)) { - continue; - } // skip empty spec + if (empty($sReconcKey)) continue; // skip empty spec - if (array_key_exists(strtolower($sReconcKey), $aKnownColumnNames)) { + if (array_key_exists(strtolower($sReconcKey), $aKnownColumnNames)) + { // Translate from a translated name to codes $aColumns = $aKnownColumnNames[strtolower($sReconcKey)]; - if (count($aColumns) > 1) { - $aCompetitors = []; - foreach ($aColumns as $sAttCodeEx) { + if (count($aColumns) > 1) + { + $aCompetitors = array(); + foreach ($aColumns as $sAttCodeEx) + { $aCompetitors[] = $sAttCodeEx; } - $aWarnings[] = "Reconciliation key '$sReconcKey' is ambiguous. Could be related to ".implode(' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; + $aWarnings[] = "Reconciliation key '$sReconcKey' is ambiguous. Could be related to ".implode (' or ', $aCompetitors).". The first one will be used: ".$aColumns[0]; } $sReconcKey = $aColumns[0]; - } else { + } + else + { // Protect against XSS injection - $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); + $sSafeName = str_replace(array('"', '<', '>'), '', $sReconcKey); throw new BulkLoadException("Unknown reconciliation key: '$sSafeName'"); } // Check that the reconciliation key is either a given column, or an external key - if (!in_array($sReconcKey, $aFieldList)) { - if (!array_key_exists($sReconcKey, $aExtKeys)) { + if (!in_array($sReconcKey, $aFieldList)) + { + if (!array_key_exists($sReconcKey, $aExtKeys)) + { // Protect against XSS injection - $sSafeName = str_replace(['"', '<', '>'], '', $sReconcKey); + $sSafeName = str_replace(array('"', '<', '>'), '', $sReconcKey); throw new BulkLoadException("Reconciliation key not found in the input columns: '$sSafeName'"); } } - if (preg_match('/^(.+)->(.+)$/', trim($sReconcKey), $aMatches)) { + if (preg_match('/^(.+)->(.+)$/', trim($sReconcKey), $aMatches)) + { // The column has been specified as "extkey->attcode" // $sExtKeyAttCode = $aMatches[1]; @@ -573,14 +666,17 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $aFinalReconcilKeys[] = $sExtKeyAttCode; $aReconcilKeysReport[$sExtKeyAttCode][] = $sRemoteAttCode; - } else { - if (!MetaModel::IsValidAttCode($sClass, $sReconcKey) && $sReconcKey != 'id') { + } + else + { + if (!MetaModel::IsValidAttCode($sClass, $sReconcKey) && $sReconcKey != 'id') + { // Safety net - should not happen now that column names are checked against known names throw new BulkLoadException("Unknown reconciliation attribute '$sReconcKey' (class: '$sClass')"); } if ($sReconcKey == 'id') { $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey] = []; + $aReconcilKeysReport[$sReconcKey] = array(); } else { $oAtt = MetaModel::GetAttributeDef($sClass, $sReconcKey); if ($oAtt->IsExternalKey()) { @@ -594,7 +690,7 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $aReconcilKeysReport[$sReconcAttCode][] = $sReconcKeyReport; } else { $aFinalReconcilKeys[] = $sReconcKey; - $aReconcilKeysReport[$sReconcKey] = []; + $aReconcilKeysReport[$sReconcKey] = array(); } } } @@ -608,22 +704,28 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $aData = $oCSVParser->ToArray(); $iLineCount = count($aData); - if (($sOutput == "summary") || ($sOutput == 'details')) { + if (($sOutput == "summary") || ($sOutput == 'details')) + { $oP->add_comment("Data Lines: ".$iLineCount); $oP->add_comment("Simulate: ".($bSimulate ? '1' : '0')); $oP->add_comment("Columns: ".implode(', ', $aFieldList)); - $aReconciliationReport = []; - foreach ($aReconcilKeysReport as $sKey => $aKeyDetails) { - if (count($aKeyDetails) > 0) { + $aReconciliationReport = array(); + foreach($aReconcilKeysReport as $sKey => $aKeyDetails) + { + if (count($aKeyDetails) > 0) + { $aReconciliationReport[] = $sKey.' ('.implode(',', $aKeyDetails).')'; - } else { + } + else + { $aReconciliationReport[] = $sKey; } } $oP->add_comment("Reconciliation Keys: ".implode(', ', $aReconciliationReport)); - foreach ($aWarnings as $sWarning) { + foreach ($aWarnings as $sWarning) + { $oP->add_comment("Warning: ".$sWarning); } } @@ -640,9 +742,12 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $bLocalize ); - if ($bSimulate) { + if ($bSimulate) + { $oMyChange = null; - } else { + } + else + { if (strlen($sComment) > 0) { $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; } else { @@ -663,35 +768,38 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $iCountCreations = 0; $iCountUpdates = 0; $iCountUnchanged = 0; - foreach ($aRes as $iRow => $aRowData) { + foreach($aRes as $iRow => $aRowData) + { $bWritten = false; $oStatus = $aRowData["__STATUS__"]; - switch (get_class($oStatus)) { - case 'RowStatus_NoChange': - $iCountUnchanged++; - break; - case 'RowStatus_Modify': - $iCountUpdates++; - $bWritten = true; - break; - case 'RowStatus_NewObj': - $iCountCreations++; - $bWritten = true; - break; - case 'RowStatus_Issue': - $iCountErrors++; - break; + switch(get_class($oStatus)) + { + case 'RowStatus_NoChange': + $iCountUnchanged++; + break; + case 'RowStatus_Modify': + $iCountUpdates++; + $bWritten = true; + break; + case 'RowStatus_NewObj': + $iCountCreations++; + $bWritten = true; + break; + case 'RowStatus_Issue': + $iCountErrors++; + break; } - if ($bWritten) { + if ($bWritten) + { // Something has been done, still there may be some issues to report - foreach ($aRowData as $key => $value) { - if (!is_object($value)) { - continue; - } + foreach($aRowData as $key => $value) + { + if (!is_object($value)) continue; - switch (get_class($value)) { + switch (get_class($value)) + { case 'CellStatus_Void': case 'CellStatus_Modify': break; @@ -710,11 +818,13 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) // // Summary of settings and results // - if ($sOutput == 'retcode') { + if ($sOutput == 'retcode') + { $oP->add($iCountErrors); } - if (($sOutput == "summary") || ($sOutput == 'details')) { + if (($sOutput == "summary") || ($sOutput == 'details')) + { $oP->add_comment("Change tracking comment: ".$sComment); $oP->add_comment("Issues: ".$iCountErrors); $oP->add_comment("Warnings: ".$iCountWarnings); @@ -723,69 +833,80 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) $oP->add_comment("Unchanged: ".$iCountUnchanged); } - if ($sOutput == 'details') { + + if ($sOutput == 'details') + { // Setup result presentation // - $aDisplayConfig = []; - $aDisplayConfig["__LINE__"] = ["label" => "Line", "description" => ""]; - $aDisplayConfig["__STATUS__"] = ["label" => "Status", "description" => ""]; - $aDisplayConfig["__OBJECT_CLASS__"] = ["label" => "Object Class", "description" => ""]; - $aDisplayConfig["__OBJECT_ID__"] = ["label" => "Object Id", "description" => ""]; - foreach ($aExtKeys as $sExtKeyAttCode => $aRemoteAtt) { + $aDisplayConfig = array(); + $aDisplayConfig["__LINE__"] = array("label"=>"Line", "description"=>""); + $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); + $aDisplayConfig["__OBJECT_CLASS__"] = array("label"=>"Object Class", "description"=>""); + $aDisplayConfig["__OBJECT_ID__"] = array("label"=>"Object Id", "description"=>""); + foreach($aExtKeys as $sExtKeyAttCode => $aRemoteAtt) + { $sLabel = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode)->GetLabel(); - $aDisplayConfig["$sExtKeyAttCode"] = ["label" => $sExtKeyAttCode, "description" => $sLabel." - ext key"]; + $aDisplayConfig["$sExtKeyAttCode"] = array("label"=>$sExtKeyAttCode, "description"=>$sLabel." - ext key"); } - foreach ($aFinalReconcilKeys as $iCol => $sAttCode) { - // $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - // $aDisplayConfig["$iCol"] = array("label"=>"$sLabel", "description"=>""); + foreach($aFinalReconcilKeys as $iCol => $sAttCode) + { + // $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + // $aDisplayConfig["$iCol"] = array("label"=>"$sLabel", "description"=>""); } - foreach ($aAttList as $sAttCode => $iCol) { - if ($sAttCode == 'id') { + foreach ($aAttList as $sAttCode => $iCol) + { + if ($sAttCode == 'id') + { $sLabel = Dict::S('UI:CSVImport:idField'); - $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; - } else { + $aDisplayConfig["$iCol"] = array("label"=>$sAttCode, "description"=>$sLabel); + } + else + { $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - $aDisplayConfig["$iCol"] = ["label" => $sAttCode, "description" => $sLabel]; + $aDisplayConfig["$iCol"] = array("label"=>$sAttCode, "description"=>$sLabel); } } - $aResultDisp = []; // to be displayed - foreach ($aRes as $iRow => $aRowData) { - $aRowDisp = []; + $aResultDisp = array(); // to be displayed + foreach($aRes as $iRow => $aRowData) + { + $aRowDisp = array(); $aRowDisp["__LINE__"] = $iRow; - if (is_object($aRowData["__STATUS__"])) { + if (is_object($aRowData["__STATUS__"])) + { $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); - } else { + } + else + { $aRowDisp["__STATUS__"] = "*No status available*"; } - if (isset($aRowData["finalclass"]) && isset($aRowData["id"])) { + if (isset($aRowData["finalclass"]) && isset($aRowData["id"])) + { $aRowDisp["__OBJECT_CLASS__"] = $aRowData["finalclass"]; $aRowDisp["__OBJECT_ID__"] = $aRowData["id"]->GetCLIValue(); - } else { + } + else + { $aRowDisp["__OBJECT_CLASS__"] = "n/a"; $aRowDisp["__OBJECT_ID__"] = "n/a"; } - foreach ($aRowData as $key => $value) { + foreach($aRowData as $key => $value) + { $sKey = (string) $key; - if ($sKey == '__STATUS__') { - continue; - } + if ($sKey == '__STATUS__') continue; //__ERRORS__ used by tests only - if ($sKey == '__ERRORS__') { - continue; - } - if ($sKey == 'finalclass') { - continue; - } - if ($sKey == 'id') { - continue; - } + if ($sKey == '__ERRORS__') continue; + if ($sKey == 'finalclass') continue; + if ($sKey == 'id') continue; - if (is_object($value)) { + if (is_object($value)) + { $aRowDisp["$sKey"] = $value->GetCLIValueAndDescription(); - } else { + } + else + { $aRowDisp["$sKey"] = $value; } } @@ -793,12 +914,19 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter) } $oP->table($aDisplayConfig, $aResultDisp); } -} catch (BulkLoadException $e) { +} +catch(BulkLoadException $e) +{ $oP->add_comment($e->getMessage()); -} catch (SecurityException $e) { +} +catch(SecurityException $e) +{ $oP->add_comment($e->getMessage()); -} catch (Exception $e) { +} +catch(Exception $e) +{ $oP->add_comment((string)$e); } $oP->output(); +?> diff --git a/webservices/itop.wsdl.php b/webservices/itop.wsdl.php index 03aaed90d7..5b9b37b1f9 100644 --- a/webservices/itop.wsdl.php +++ b/webservices/itop.wsdl.php @@ -1,5 +1,4 @@ diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index 6408a1ff8f..e14988c0c3 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -1,10 +1,9 @@ + /** - * Shows a usage of the SOAP queries + * Shows a usage of the SOAP queries * * @copyright Copyright (C) 2010-2024 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 @@ -36,26 +36,29 @@ * @return bool|false|string * @throws \Exception */ -function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = []) +function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array()) { // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. - if (function_exists('curl_init')) { + if (function_exists('curl_init')) + { // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 // by setting the SSLVERSION to 3 as done below. - $aHTTPHeaders = []; - if ($sOptionnalHeaders !== null) { + $aHTTPHeaders = array(); + if ($sOptionnalHeaders !== null) + { $aHeaders = explode("\n", $sOptionnalHeaders); // N°3267 - Webservices: Fix optional headers not being taken into account // See https://www.php.net/curl_setopt CURLOPT_HTTPHEADER - $aHTTPHeaders = []; - foreach ($aHeaders as $sHeaderString) { + $aHTTPHeaders = array(); + foreach($aHeaders as $sHeaderString) + { $aHTTPHeaders[] = trim($sHeaderString); } } // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions - $aOptions = [ + $aOptions = array( CURLOPT_RETURNTRANSFER => true, // return the content of the request CURLOPT_HEADER => false, // don't return the headers in the output CURLOPT_FOLLOWLOCATION => true, // follow redirects @@ -73,58 +76,74 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead CURLOPT_POST => count($aData), CURLOPT_POSTFIELDS => http_build_query($aData), CURLOPT_HTTPHEADER => $aHTTPHeaders, - ]; + ); $aAllOptions = $aCurlOptions + $aOptions; $ch = curl_init($sUrl); curl_setopt_array($ch, $aAllOptions); $response = curl_exec($ch); $iErr = curl_errno($ch); - $sErrMsg = curl_error($ch); - if ($iErr !== 0) { + $sErrMsg = curl_error( $ch ); + if ($iErr !== 0) + { throw new Exception("Problem opening URL: $sUrl, $sErrMsg"); } - if (is_array($aResponseHeaders)) { + if (is_array($aResponseHeaders)) + { $aHeaders = curl_getinfo($ch); - foreach ($aHeaders as $sCode => $sValue) { - $sName = str_replace(' ', '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" + foreach($aHeaders as $sCode => $sValue) + { + $sName = str_replace(' ' , '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" $aResponseHeaders[$sName] = $sValue; } } - curl_close($ch); - } else { + curl_close( $ch ); + } + else + { // cURL is not available let's try with streams and fopen... $sData = http_build_query($aData); - $aParams = ['http' => [ + $aParams = array('http' => array( 'method' => 'POST', 'content' => $sData, - 'header' => "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", - ]]; - if ($sOptionnalHeaders !== null) { + 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", + )); + if ($sOptionnalHeaders !== null) + { $aParams['http']['header'] .= $sOptionnalHeaders; } $ctx = stream_context_create($aParams); $fp = @fopen($sUrl, 'rb', false, $ctx); - if (!$fp) { + if (!$fp) + { global $php_errormsg; - if (isset($php_errormsg)) { + if (isset($php_errormsg)) + { throw new Exception("Wrong URL: $sUrl, $php_errormsg"); - } elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) { + } + elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) + { throw new Exception("Cannot connect to $sUrl: missing module 'openssl'"); - } else { + } + else + { throw new Exception("Wrong URL: $sUrl"); } } $response = @stream_get_contents($fp); - if ($response === false) { + if ($response === false) + { throw new Exception("Problem reading data from $sUrl, $php_errormsg"); } - if (is_array($aResponseHeaders)) { + if (is_array($aResponseHeaders)) + { $aMeta = stream_get_meta_data($fp); $aHeaders = $aMeta['wrapper_data']; - foreach ($aHeaders as $sHeaderString) { - if (preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) { + foreach($aHeaders as $sHeaderString) + { + if(preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) + { $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]); } } @@ -142,203 +161,208 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead // Define the operations to perform (one operation per call the rest service) // -$aOperations = [ - [ +$aOperations = array( + array( 'operation' => 'list_operations', // operation code - ], - [ + ), + array( 'operation' => 'core/create', // operation code 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log 'class' => 'UserRequest', 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) // Values for the object to create - 'fields' => [ + 'fields' => array( 'org_id' => "SELECT Organization WHERE name = 'Demo'", - 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], + 'caller_id' => array('name' => 'monet', 'first_name' => 'claude'), 'title' => 'issue blah', - 'description' => 'something happened', - ], - ], - [ + 'description' => 'something happened' + ), + ), + array( 'operation' => 'core/update', // operation code 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log 'class' => 'UserRequest', 'key' => 'SELECT UserRequest WHERE id=1', 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) // Values for the object to create - 'fields' => [ + 'fields' => array( 'title' => 'Issue #'.rand(0, 100), - 'contacts_list' => [ - [ + 'contacts_list' => array( + array( 'role' => 'fireman #'.rand(0, 100), - 'contact_id' => ['finalclass' => 'Person', 'name' => 'monet', 'first_name' => 'claude'], - ], - ], - ], - ], + 'contact_id' => array('finalclass' => 'Person', 'name' => 'monet', 'first_name' => 'claude'), + ), + ), + ), + ), // Rewrite the full CaseLog on an existing UserRequest with id=1, setting date and user (optional) - [ + array( 'operation' => 'core/update', 'comment' => 'Synchronization from Client A', // comment recorded in the change tracking log 'class' => 'UserRequest', 'key' => 'SELECT UserRequest WHERE id=1', 'output_fields' => 'id, friendlyname, title', - 'fields' => [ - 'public_log' => [ - 'items' => [ - 0 => [ + 'fields' => array( + 'public_log' => array( + 'items' => array( + 0 => array( 'date' => '2001-02-01 23:59:59', //Allow to set the date of a true event, an alarm for eg. 'user_login' => 'Alarm monitoring', //Free text 'user_id' => 0, //0 is required for the user_login to be taken into account 'message' => 'This is 1st entry as an HTML formatted
text', - ], - 1 => [ + ), + 1 => array( 'date' => '2001-02-02 00:00:00', //If ommitted set automatically. 'user_login' => 'Alarm monitoring', //user=id=0 is missing so will be ignored 'message' => 'Second entry in text format: with new line, but format not specified, so treated as HTML!, user_id=0 missing, so user_login ignored', - ], - ], - ], - ], - ], + ), + ), + ), + ), + ), // Add a Text entry in the HTML CaseLog of the UserRequest with id=1, setting date and user (optional) - [ + array( 'operation' => 'core/update', // operation code 'comment' => 'Synchronization from Alarm Monitoring', // comment recorded in the change tracking log 'class' => 'UserRequest', 'key' => 1, // object id or OQL 'output_fields' => 'id, friendlyname, title', // list of fields to show in the results (* or a,b,c) // Example of adding an entry into a CaseLog on an existing UserRequest - 'fields' => [ - 'public_log' => [ - 'add_item' => [ + 'fields' => array( + 'public_log' => array( + 'add_item' => array( 'user_login' => 'New Entry', //Free text 'user_id' => 0, //0 is required for the user_login to be taken into account 'format' => 'text', //If ommitted, source is expected to be HTML 'message' => 'This text is not HTML formatted with 3 lines: new line 3rd and last line', - ], - ], - ], - ], - [ + ), + ), + ), + ), + array( 'operation' => 'core/get', // operation code 'class' => 'UserRequest', 'key' => 'SELECT UserRequest', 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) - ], - [ + ), + array( 'operation' => 'core/delete', // operation code 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log 'class' => 'UserRequest', 'key' => 'SELECT UserRequest WHERE org_id = 2', 'simulate' => true, - ], - [ + ), + array( 'operation' => 'core/apply_stimulus', // operation code 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log 'class' => 'UserRequest', 'key' => 1, 'stimulus' => 'ev_assign', // Values to set - 'fields' => [ + 'fields' => array( 'team_id' => 15, // Helpdesk - 'agent_id' => 9, // Jules Verne - ], + 'agent_id' => 9 // Jules Verne + ), 'output_fields' => 'id, friendlyname, title, contacts_list', // list of fields to show in the results (* or a,b,c) - ], - [ + ), + array( 'operation' => 'core/get_related', // operation code 'class' => 'Server', 'key' => 'SELECT Server', 'relation' => 'impacts', // relation code 'depth' => 4, // max recursion depth - ], -]; -$aOperations = [ - [ + ), +); +$aOperations = array( + array( 'operation' => 'core/create', // operation code 'comment' => 'Automatic creation of attachment blah blah...', // comment recorded in the change tracking log 'class' => 'Attachment', 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) // Values for the object to create - 'fields' => [ + 'fields' => array( 'item_class' => 'UserRequest', 'item_id' => 1, 'item_org_id' => 3, - 'contents' => [ + 'contents' => array( 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACmSURBVChTfZHRDYMwDESzQ2fqhHx3C3ao+MkW/WlnaFxfzk7sEnE6JHJ+NgaKZN2zLHVN2ssfkae0Da7FQ5PRk/ve4Hcx19Ie6CEGuh/6vMgNhwanHVUNbt73lUDbYJ+6pg8b3+m2RehsVPdMXyvQY+OVkB+Rrv64lUjb3nq+aCA6v4leRqtfaIgimr53atBy9PlfUhoh3fFCNDmErv9FWR6ylBL5AREbmHBnFj5lAAAAAElFTkSuQmCC', 'filename' => 'myself.png', - 'mimetype' => 'image/png', - ], - ], - ], - [ + 'mimetype' => 'image/png' + ), + ), + ), + array( 'operation' => 'core/get', // operation code 'class' => 'Attachment', 'key' => 'SELECT Attachment', 'output_fields' => '*', - ], -]; -$aOperations = [ - [ + ) +); +$aOperations = array( + array( 'operation' => 'core/update', // operation code 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log 'class' => 'Server', 'key' => 'SELECT Server WHERE name="Server1"', 'output_fields' => 'id, friendlyname, description', // list of fields to show in the results (* or a,b,c) // Values for the object to create - 'fields' => [ + 'fields' => array( 'description' => 'Issue #'.time(), - ], - ], -]; -$aOperations = [ - [ + ), + ), +); +$aOperations = array( + array( 'operation' => 'core/create', // operation code 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log 'class' => 'UserRequest', 'output_fields' => 'id, friendlyname', // list of fields to show in the results (* or a,b,c) // Values for the object to create - 'fields' => [ + 'fields' => array( 'org_id' => "SELECT Organization WHERE name = 'Demo'", - 'caller_id' => ['name' => 'monet', 'first_name' => 'claude'], + 'caller_id' => array('name' => 'monet', 'first_name' => 'claude'), 'title' => 'issue blah', - 'description' => 'something happened', - ], - ], -]; -$aXXXOperations = [ - [ + 'description' => 'something happened' + ), + ), +); +$aXXXOperations = array( + array( 'operation' => 'core/check_credentials', // operation code 'user' => 'admin', 'password' => 'admin', - ], -]; -$aDeleteOperations = [ - [ + ), +); +$aDeleteOperations = array( + array( 'operation' => 'core/delete', // operation code 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log 'class' => 'Server', 'key' => 'SELECT Server', 'simulate' => false, - ], -]; + ), +); -if (false) { +if (false) +{ echo "Please edit the sample script and configure the server URL"; exit; -} else { +} +else +{ $sUrl = "https://localhost/itop/webservices/rest.php?version=1.3"; } -$aData = []; +$aData = array(); $aData['auth_user'] = 'rest'; $aData['auth_pwd'] = 'rest'; -foreach ($aOperations as $iOp => $aOperation) { + +foreach ($aOperations as $iOp => $aOperation) +{ echo "======================================\n"; echo "Operation #$iOp: ".$aOperation['operation']."\n"; $aData['json_data'] = json_encode($aOperation); @@ -347,18 +371,25 @@ function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHead echo "Input:\n"; print_r($aOperation); $aResults = null; - try { + try + { $response = DoPostRequest($sUrl, $aData); $aResults = json_decode($response); - } catch (Exception $e) { + } + catch (Exception $e) + { $response = $e->getMessage(); } - if ($aResults) { + if ($aResults) + { echo "--------------------------------------\n"; echo "Reply:\n"; print_r($aResults); - } else { + } + else + { echo "ERROR rest.php replied:\n"; echo $response; } } + diff --git a/webservices/itopsoap.examples.php b/webservices/itopsoap.examples.php index 6427a02843..5ed1bdc24c 100644 --- a/webservices/itopsoap.examples.php +++ b/webservices/itopsoap.examples.php @@ -1,10 +1,9 @@ + /** - * Shows a usage of the SOAP queries + * Shows a usage of the SOAP queries * * @copyright Copyright (C) 2010-2024 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 @@ -31,16 +31,17 @@ $aSOAPMapping = SOAPMapping::GetMapping(); -ini_set("soap.wsdl_cache_enabled", "0"); +ini_set("soap.wsdl_cache_enabled","0"); $oSoapClient = new SoapClient( $sWsdlUri, - [ + array( 'trace' => 1, 'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php - ] + ) ); -try { +try +{ // The most simple service, returning a string // $sServerVersion = $oSoapClient->GetVersion(); @@ -49,29 +50,30 @@ // More complex ones, returning a SOAPResult structure // (run the page to know more about the returned data) // - $oRes = $oSoapClient->CreateIncidentTicket( + $oRes = $oSoapClient->CreateIncidentTicket + ( 'admin', /* login */ 'admin', /* password */ 'Email server down', /* title */ 'HW found shutdown', /* description */ null, /* caller */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* customer */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW Management')]), /* service */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Troubleshooting')]), /* service subcategory */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* customer */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'NW Management'))), /* service */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Troubleshooting'))), /* service subcategory */ '', /* product */ - new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'NW support')]), /* workgroup */ - [ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'NW support'))), /* workgroup */ + array( new SOAPLinkCreationSpec( 'Device', - [new SOAPSearchCondition('name', 'switch01')], - [] + array(new SOAPSearchCondition('name', 'switch01')), + array() ), new SOAPLinkCreationSpec( 'Server', - [new SOAPSearchCondition('name', 'dbserver1.demo.com')], - [] + array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), + array() ), - ], /* impacted cis */ + ), /* impacted cis */ '1', /* impact */ '1' /* urgency */ ); @@ -82,36 +84,44 @@ echo "\n"; echo "

\n"; - $oRes = $oSoapClient->SearchObjects( + $oRes = $oSoapClient->SearchObjects + ( 'admin', /* login */ 'admin', /* password */ 'SELECT URP_Profiles' /* oql */ ); echo "

SearchObjects() returned:\n"; - if ($oRes->status) { + if ($oRes->status) + { $aResults = $oRes->result; echo "\n"; // Header made after the first line echo "\n"; - foreach ($aResults[0]->values as $aKeyValuePair) { + foreach ($aResults[0]->values as $aKeyValuePair) + { echo " \n"; } echo "\n"; - foreach ($aResults as $iRow => $aData) { + foreach ($aResults as $iRow => $aData) + { echo "\n"; - foreach ($aData->values as $aKeyValuePair) { + foreach ($aData->values as $aKeyValuePair) + { echo " \n"; } echo "\n"; } echo "
".$aKeyValuePair->key."
".$aKeyValuePair->value."
\n"; - } else { - $aErrors = []; - foreach ($oRes->errors->messages as $oMessage) { + } + else + { + $aErrors = array(); + foreach ($oRes->errors->messages as $oMessage) + { $aErrors[] = $oMessage->text; } $sErrorMsg = implode(', ', $aErrors); @@ -121,12 +131,15 @@ //echo "\n"; } echo "

\n"; -} catch (SoapFault $e) { - echo "

SoapFault Exception: {$e->getMessage()}

\n"; - echo "

Request

\n"; - echo "
\n";
-	echo htmlspecialchars($oSoapClient->__getLastRequest())."\n";
-	echo "
"; +} +catch(SoapFault $e) +{ + echo "

SoapFault Exception: {$e->getMessage()}

\n"; + echo "

Request

\n"; + echo "
\n"; 
+	echo htmlspecialchars($oSoapClient->__getLastRequest())."\n"; 
+	echo "
"; echo "

Response

"; echo $oSoapClient->__getLastResponse()."\n"; } +?> diff --git a/webservices/itopsoaptypes.class.inc.php b/webservices/itopsoaptypes.class.inc.php index 679dc49961..6145d85fae 100644 --- a/webservices/itopsoaptypes.class.inc.php +++ b/webservices/itopsoaptypes.class.inc.php @@ -1,10 +1,9 @@ + /** * Declarations required for the WSDL * @@ -24,6 +24,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ + // Note: the attributes must have the same names (case sensitive) as in the WSDL specification // @@ -39,6 +40,7 @@ public function __construct($sAttCode, $value) } } + class SOAPExternalKeySearch { public $conditions; // array of SOAPSearchCondition @@ -50,15 +52,12 @@ public function __construct($aConditions = null) public function IsVoid() { - if (is_null($this->conditions)) { - return true; - } - if (count($this->conditions) == 0) { - return true; - } + if (is_null($this->conditions)) return true; + if (count($this->conditions) == 0) return true; } } + class SOAPAttributeValue { public $attcode; // string @@ -71,6 +70,7 @@ public function __construct($sAttCode, $value) } } + class SOAPLinkCreationSpec { public $class; @@ -85,6 +85,7 @@ public function __construct($sClass, $aConditions, $aAttributes) } } + class SOAPLogMessage { public $text; // string @@ -95,6 +96,7 @@ public function __construct($sText) } } + class SOAPResultLog { public $messages; // array of SOAPLogMessage @@ -105,6 +107,7 @@ public function __construct($aMessages) } } + class SOAPKeyValue { public $key; // string @@ -129,6 +132,7 @@ public function __construct($sLabel, $aValues) } } + class SOAPResult { public $status; // boolean @@ -159,11 +163,12 @@ public function __construct($bStatus, $sMessage) } } + class SOAPMapping { - public static function GetMapping() + static function GetMapping() { - $aSOAPMapping = [ + $aSOAPMapping = array( 'SearchCondition' => 'SOAPSearchCondition', 'ExternalKeySearch' => 'SOAPExternalKeySearch', 'AttributeValue' => 'SOAPAttributeValue', @@ -175,7 +180,9 @@ public static function GetMapping() 'ResultMessage' => 'SOAPResultMessage', 'Result' => 'SOAPResult', 'SimpleResult' => 'SOAPSimpleResult', - ]; + ); return $aSOAPMapping; } } + +?> diff --git a/webservices/rest.php b/webservices/rest.php index 878a3e9faf..70f08cb0e2 100644 --- a/webservices/rest.php +++ b/webservices/rest.php @@ -1,5 +1,4 @@ operations[] = [ + $this->operations[] = array( 'verb' => $sVerb, 'description' => $sDescription, 'extension' => $sServiceProviderClass, - ]; + ); } } if (!function_exists('json_last_error_msg')) { - function json_last_error_msg() - { - static $ERRORS = [ + function json_last_error_msg() { + static $ERRORS = array( JSON_ERROR_NONE => 'No error', JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', JSON_ERROR_SYNTAX => 'Syntax error', JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', - ]; + ); $error = json_last_error(); return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; @@ -75,14 +74,15 @@ function json_last_error_msg() //read json_data parameter via as a string (standard behaviour) $sJsonString = utils::ReadParam('json_data', null, false, 'raw_data'); -if (empty($sJsonString)) { +if (empty($sJsonString)){ //N °3455: read json_data parameter via a file passed by http protocol - if (isset($_FILES['json_data']['tmp_name'])) { + if(isset($_FILES['json_data']['tmp_name'])) + { $sTmpFilePath = $_FILES['json_data']['tmp_name']; - if (is_file($sTmpFilePath)) { + if (is_file($sTmpFilePath)){ $sValue = file_get_contents($sTmpFilePath); unlink($sTmpFilePath); - if (! empty($sValue)) { + if (! empty($sValue)){ $sJsonString = utils::Sanitize($sValue, null, 'raw_data'); } } @@ -92,111 +92,136 @@ function json_last_error_msg() $sProvider = ''; $oKPI = new ExecutionKPI(); -try { +try +{ utils::UseParamFile(); - + $oKPI->ComputeAndReport('Data model loaded'); - // N°6358 - force credentials for REST calls - LoginWebPage::ResetSession(true); + // N°6358 - force credentials for REST calls + LoginWebPage::ResetSession(true); $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); - $oKPI->ComputeAndReport('User login'); + $oKPI->ComputeAndReport('User login'); - if ($iRet == LoginWebPage::EXIT_CODE_OK) { + if ($iRet == LoginWebPage::EXIT_CODE_OK) + { // Extra validation of the profile - if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) { + if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) + { // Web services access is limited to the users with the profile REST Web Services $iRet = LoginWebPage::EXIT_CODE_NOTAUTHORIZED; } } - if ($iRet != LoginWebPage::EXIT_CODE_OK) { - switch ($iRet) { + if ($iRet != LoginWebPage::EXIT_CODE_OK) + { + switch($iRet) + { case LoginWebPage::EXIT_CODE_MISSINGLOGIN: - throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER); - break; - + throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER); + break; + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: - throw new Exception("Missing parameter 'auth_pwd'", RestResult::MISSING_AUTH_PWD); - break; - + throw new Exception("Missing parameter 'auth_pwd'", RestResult::MISSING_AUTH_PWD); + break; + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: - throw new Exception("Invalid login", RestResult::UNAUTHORIZED); - break; - + throw new Exception("Invalid login", RestResult::UNAUTHORIZED); + break; + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: - throw new Exception("Portal user is not allowed", RestResult::UNAUTHORIZED); - break; - + throw new Exception("Portal user is not allowed", RestResult::UNAUTHORIZED); + break; + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: - throw new Exception("This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)", RestResult::UNAUTHORIZED); - break; - + throw new Exception("This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)", RestResult::UNAUTHORIZED); + break; + default: - throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED); + throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED); } } - if ($sVersion == null) { + if ($sVersion == null) + { throw new Exception("Missing parameter 'version' (e.g. '1.0')", RestResult::MISSING_VERSION); } - if ($sJsonString == null) { + if ($sJsonString == null) + { throw new Exception("Missing parameter 'json_data'", RestResult::MISSING_JSON); } - if (is_string($sJsonString)) { - $aJsonData = @json_decode($sJsonString); - } elseif (is_array($sJsonString)) { - $aJsonData = (object) $sJsonString; - $sJsonString = json_encode($aJsonData); - } else { - $aJsonData = null; - } - - if ($aJsonData == null) { - throw new Exception('Parameter json_data is not a valid JSON structure', RestResult::INVALID_JSON); - } + if (is_string($sJsonString)) + { + $aJsonData = @json_decode($sJsonString); + } + elseif(is_array($sJsonString)) + { + $aJsonData = (object) $sJsonString; + $sJsonString = json_encode($aJsonData); + } + else + { + $aJsonData = null; + } + + if ($aJsonData == null) + { + throw new Exception('Parameter json_data is not a valid JSON structure', RestResult::INVALID_JSON); + } $oKPI->ComputeAndReport('Parameters validated'); + /** @var iRestServiceProvider[] $aProviders */ $oKPI = new ExecutionKPI(); - $aProviders = []; - foreach (get_declared_classes() as $sPHPClass) { + $aProviders = array(); + foreach(get_declared_classes() as $sPHPClass) + { $oRefClass = new ReflectionClass($sPHPClass); - if ($oRefClass->implementsInterface('iRestServiceProvider')) { - $aProviders[] = new $sPHPClass(); + if ($oRefClass->implementsInterface('iRestServiceProvider')) + { + $aProviders[] = new $sPHPClass; } } - $aOpToRestService = []; // verb => $oRestServiceProvider + $aOpToRestService = array(); // verb => $oRestServiceProvider /** @var iRestServiceProvider $oRestSP */ - foreach ($aProviders as $oRestSP) { + foreach ($aProviders as $oRestSP) + { $aOperations = $oRestSP->ListOperations($sVersion); - foreach ($aOperations as $aOpData) { - $aOpToRestService[$aOpData['verb']] = - [ + foreach ($aOperations as $aOpData) + { + $aOpToRestService[$aOpData['verb']] = array + ( 'service_provider' => $oRestSP, 'description' => $aOpData['description'], - ]; + ); } } $oKPI->ComputeAndReport('iRestServiceProvider loaded with operations'); - if (count($aOpToRestService) == 0) { + if (count($aOpToRestService) == 0) + { throw new Exception("There is no service available for version '$sVersion'", RestResult::UNSUPPORTED_VERSION); } + $sOperation = RestUtils::GetMandatoryParam($aJsonData, 'operation'); - if ($sOperation == 'list_operations') { + if ($sOperation == 'list_operations') + { $oResult = new RestResultListOperations(); $oResult->message = "Operations: ".count($aOpToRestService); $oResult->version = $sVersion; - foreach ($aOpToRestService as $sVerb => $aOpData) { + foreach ($aOpToRestService as $sVerb => $aOpData) + { $oResult->AddOperation($sVerb, $aOpData['description'], get_class($aOpData['service_provider'])); } - } else { - if (!array_key_exists($sOperation, $aOpToRestService)) { + } + else + { + if (!array_key_exists($sOperation, $aOpToRestService)) + { throw new Exception("Unknown verb '$sOperation' in version '$sVersion'", RestResult::UNKNOWN_OPERATION); } /** @var iRestServiceProvider $oRS */ @@ -211,11 +236,16 @@ function json_last_error_msg() $oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData); } $oKPI->ComputeAndReport('Operation finished'); -} catch (Exception $e) { +} +catch(Exception $e) +{ $oResult = new RestResult(); - if ($e->GetCode() == 0) { + if ($e->GetCode() == 0) + { $oResult->code = RestResult::INTERNAL_ERROR; - } else { + } + else + { $oResult->code = $e->GetCode(); } $oResult->message = "Error: ".$e->GetMessage(); @@ -226,17 +256,23 @@ function json_last_error_msg() // $sResponse = json_encode($oResult); -if ($sResponse === false) { + +if ($sResponse === false) +{ $oJsonIssue = new RestResult(); $oJsonIssue->code = RestResult::INTERNAL_ERROR; $oJsonIssue->message = 'json encoding failed with message: '.json_last_error_msg().'. Full response structure for debugging purposes (print_r+bin2hex): '.bin2hex(print_r($oResult, true)); $sResponse = json_encode($oJsonIssue); } + $sCallback = utils::ReadParam('callback', null); -if ($sCallback == null) { +if ($sCallback == null) +{ $oP = new JsonPage(); -} else { +} +else +{ $oP = new JsonPPage($sCallback); } $oP->add_header('Access-Control-Allow-Origin: *'); @@ -247,16 +283,18 @@ function json_last_error_msg() // Log usage // -if (MetaModel::GetConfig()->Get('log_rest_service')) { +if (MetaModel::GetConfig()->Get('log_rest_service')) +{ $oLog = new EventRestService(); $oLog->SetTrim('userinfo', UserRights::GetUser()); $oLog->Set('version', $sVersion); $oLog->Set('operation', $sOperation); - $oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString); + $oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString); $oLog->Set('provider', $sProvider); $sMessage = $oResult->message; - if (empty($oResult->message)) { + if (empty($oResult->message)) + { $sMessage = 'Ok'; } $oLog->SetTrim('message', $sMessage); @@ -265,7 +303,7 @@ function json_last_error_msg() $iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; $sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT); $sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode); - !StringFitsInLogField($sJsonOuputWithPrettyPrinting) ? + !StringFitsInLogField( $sJsonOuputWithPrettyPrinting) ? $oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty $oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting); @@ -278,4 +316,4 @@ function json_last_error_msg() function StringFitsInLogField(string $sLog): bool { return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260 -} +} \ No newline at end of file diff --git a/webservices/soapserver.php b/webservices/soapserver.php index 055ceb0ebe..2d9b49ec46 100644 --- a/webservices/soapserver.php +++ b/webservices/soapserver.php @@ -1,5 +1,4 @@ $aSOAPMapping, - ] + array( + 'classmap' => $aSOAPMapping + ) ); // $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION); -if (!empty($sServiceCategory)) { +if (!empty($sServiceCategory)) +{ $sServiceClass = $sServiceCategory; - if (!class_exists($sServiceClass)) { + if (!class_exists($sServiceClass)) + { // not a valid class name (not a PHP class at all) throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not a PHP class"); - } elseif (!is_subclass_of($sServiceClass, 'WebServicesBase')) { + } + elseif (!is_subclass_of($sServiceClass, 'WebServicesBase')) + { // not a valid class name (not deriving from WebServicesBase) throw new SoapFault("iTop SOAP server", "Invalid argument service_category: '$sServiceClass' is not derived from WebServicesBase"); - } else { + } + else + { $oSoapServer->setClass($sServiceClass, null); } -} else { +} +else +{ $oSoapServer->setClass('BasicServices', null); } -if ($_SERVER["REQUEST_METHOD"] == "POST") { +if ($_SERVER["REQUEST_METHOD"] == "POST") +{ CMDBObject::SetTrackOrigin('webservice-soap'); $oSoapServer->handle(); -} else { +} +else +{ echo "This SOAP server can handle the following functions: "; $aFunctions = $oSoapServer->getFunctions(); echo "
    \n"; - foreach ($aFunctions as $sFunc) { - if ($sFunc == 'GetWSDLContents') { - continue; - } + foreach($aFunctions as $sFunc) + { + if ($sFunc == 'GetWSDLContents') continue; echo "
  • $sFunc
  • \n"; } @@ -76,8 +88,10 @@ echo "You may also want to try the following service categories: "; echo "
      \n"; - foreach (get_declared_classes() as $sPHPClass) { - if (is_subclass_of($sPHPClass, 'WebServicesBase')) { + foreach(get_declared_classes() as $sPHPClass) + { + if (is_subclass_of($sPHPClass, 'WebServicesBase')) + { $sServiceCategory = $sPHPClass; $sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php'; $sSoapServerUri .= "?service_category=$sServiceCategory"; @@ -86,3 +100,4 @@ } echo "
    \n"; } +?> diff --git a/webservices/status.php b/webservices/status.php index 7ef91b2c0a..636ab4ec64 100644 --- a/webservices/status.php +++ b/webservices/status.php @@ -6,18 +6,21 @@ use Combodo\iTop\Application\Status\Status; //Do check Status -try { - new Status(); - $aResult = ['status' => STATUS_RUNNING, 'code' => RestResult::OK, 'message' => '']; -} catch (Exception $e) { - $iCode = (defined('\RestResult::INTERNAL_ERROR')) ? RestResult::INTERNAL_ERROR : 100; - $aResult = ['status' => STATUS_ERROR, 'code' => $iCode, 'message' => $e->getMessage()]; - http_response_code(500); +try +{ + new Status(); + $aResult = ['status' => STATUS_RUNNING, 'code' => RestResult::OK, 'message' => '']; +} +catch (Exception $e) +{ + $iCode = (defined('\RestResult::INTERNAL_ERROR')) ? RestResult::INTERNAL_ERROR : 100; + $aResult = ['status' => STATUS_ERROR, 'code' => $iCode, 'message' => $e->getMessage()]; + http_response_code(500); } //Set headers, based on webservices/rest.php $sContentType = 'application/json'; -header('Content-type: '.$sContentType); +header('Content-type: ' . $sContentType); header('Access-Control-Allow-Origin: *'); //Output result diff --git a/webservices/webservices.basic.php b/webservices/webservices.basic.php index e322aee18a..bf8c364474 100644 --- a/webservices/webservices.basic.php +++ b/webservices/webservices.basic.php @@ -1,10 +1,9 @@ + /** * Implementation of iTop SOAP services * @@ -26,9 +26,10 @@ require_once(APPROOT.'/webservices/webservices.class.inc.php'); + class BasicServices extends WebServicesBase { - protected static function GetWSDLFilePath() + static protected function GetWSDLFilePath() { return APPROOT.'/webservices/itop.wsdl.tpl'; } @@ -38,11 +39,14 @@ protected static function GetWSDLFilePath() * * @return string WebServiceResult */ - public static function GetVersion() + static public function GetVersion() { - if (ITOP_REVISION == 'svn') { + if (ITOP_REVISION == 'svn') + { $sVersionString = ITOP_VERSION.' [dev]'; - } else { + } + else + { // This is a build made from SVN, let display the full information $sVersionString = ITOP_VERSION_FULL." ".ITOP_BUILD_DATE; } @@ -52,7 +56,8 @@ public static function GetVersion() public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) + { $oRes = new WebServiceResultFailedLogin($sLogin); $this->LogUsage(__FUNCTION__, $oRes); @@ -66,15 +71,15 @@ public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); - $aImpactedCIs = []; - if (is_null($aSOAPImpactedCIs)) { - $aSOAPImpactedCIs = []; - } - foreach ($aSOAPImpactedCIs as $oImpactedCIs) { + $aImpactedCIs = array(); + if (is_null($aSOAPImpactedCIs)) $aSOAPImpactedCIs = array(); + foreach($aSOAPImpactedCIs as $oImpactedCIs) + { $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); } - $oRes = $this->_CreateResponseTicket( + $oRes = $this->_CreateResponseTicket + ( 'UserRequest', $sTitle, $sDescription, @@ -93,7 +98,8 @@ public function CreateRequestTicket($sLogin, $sPassword, $sTitle, $sDescription, public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) + { $oRes = new WebServiceResultFailedLogin($sLogin); $this->LogUsage(__FUNCTION__, $oRes); @@ -101,27 +107,29 @@ public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription } UserRights::Login($sLogin); - if (!class_exists('Incident')) { + + if (!class_exists('Incident')) + { $oRes = new WebServiceResult(); $oRes->LogError("The class Incident does not exist. Did you install the Incident Management (ITIL) module ?"); return $oRes->ToSoapStructure(); } - + $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); $aServiceDesc = self::SoapStructToExternalKeySearch($oServiceDesc); $aServiceSubcategoryDesc = self::SoapStructToExternalKeySearch($oServiceSubcategoryDesc); $aWorkgroupDesc = self::SoapStructToExternalKeySearch($oWorkgroupDesc); - $aImpactedCIs = []; - if (is_null($aSOAPImpactedCIs)) { - $aSOAPImpactedCIs = []; - } - foreach ($aSOAPImpactedCIs as $oImpactedCIs) { + $aImpactedCIs = array(); + if (is_null($aSOAPImpactedCIs)) $aSOAPImpactedCIs = array(); + foreach($aSOAPImpactedCIs as $oImpactedCIs) + { $aImpactedCIs[] = self::SoapStructToLinkCreationSpec($oImpactedCIs); } - $oRes = $this->_CreateResponseTicket( + $oRes = $this->_CreateResponseTicket + ( 'Incident', $sTitle, $sDescription, @@ -137,11 +145,11 @@ public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription ); return $oRes->ToSoapStructure(); } - + /** * Create an ResponseTicket (Incident or UserRequest) from an external system * Some CIs might be specified (by their name/IP) - * + * * @param string sClass The class of the ticket: Incident or UserRequest * @param string sTitle * @param string sDescription @@ -162,7 +170,8 @@ protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCall $oRes = new WebServiceResult(); - try { + try + { CMDBObject::SetTrackInfo('Administrator'); $oNewTicket = MetaModel::NewObject($sClass); @@ -171,36 +180,48 @@ protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCall $this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes); $this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes); - + $this->MyObjectSetExternalKey('service_id', 'service', $aServiceDesc, $oNewTicket, $oRes); - if (!array_key_exists('service_id', $aServiceSubcategoryDesc)) { + if (!array_key_exists('service_id', $aServiceSubcategoryDesc)) + { $aServiceSubcategoryDesc['service_id'] = $oNewTicket->Get('service_id'); } $this->MyObjectSetExternalKey('servicesubcategory_id', 'servicesubcategory', $aServiceSubcategoryDesc, $oNewTicket, $oRes); - if (MetaModel::IsValidAttCode($sClass, 'product')) { + if (MetaModel::IsValidAttCode($sClass, 'product')) + { // 1.x data models $this->MyObjectSetScalar('product', 'product', $sProduct, $oNewTicket, $oRes); } - if (MetaModel::IsValidAttCode($sClass, 'workgroup_id')) { + if (MetaModel::IsValidAttCode($sClass, 'workgroup_id')) + { // 1.x data models $this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); - } elseif (MetaModel::IsValidAttCode($sClass, 'team_id')) { + } + else if (MetaModel::IsValidAttCode($sClass, 'team_id')) + { // 2.x data models $this->MyObjectSetExternalKey('team_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); } - if (MetaModel::IsValidAttCode($sClass, 'ci_list')) { + + if (MetaModel::IsValidAttCode($sClass, 'ci_list')) + { // 1.x data models $aDevicesNotFound = $this->AddLinkedObjects('ci_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); - } elseif (MetaModel::IsValidAttCode($sClass, 'functionalcis_list')) { + } + else if (MetaModel::IsValidAttCode($sClass, 'functionalcis_list')) + { // 2.x data models $aDevicesNotFound = $this->AddLinkedObjects('functionalcis_list', 'impacted_cis', 'FunctionalCI', $aImpactedCIs, $oNewTicket, $oRes); } - - if (count($aDevicesNotFound) > 0) { + + if (count($aDevicesNotFound) > 0) + { $this->MyObjectSetScalar('description', 'n/a', $sDescription.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes); - } else { + } + else + { $this->MyObjectSetScalar('description', 'n/a', $sDescription, $oNewTicket, $oRes); } @@ -208,9 +229,13 @@ protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCall $this->MyObjectSetScalar('urgency', 'urgency', $sUrgency, $oNewTicket, $oRes); $this->MyObjectInsert($oNewTicket, 'created', $oRes); - } catch (CoreException $e) { + } + catch (CoreException $e) + { $oRes->LogError($e->getMessage()); - } catch (Exception $e) { + } + catch (Exception $e) + { $oRes->LogError($e->getMessage()); } @@ -220,12 +245,13 @@ protected function _CreateResponseTicket($sClass, $sTitle, $sDescription, $aCall /** * Given an OQL, returns a set of objects (several objects could be on the same row) - * + * * @param string sOQL - */ + */ public function SearchObjects($sLogin, $sPassword, $sOQL) { - if (!UserRights::CheckCredentials($sLogin, $sPassword)) { + if (!UserRights::CheckCredentials($sLogin, $sPassword)) + { $oRes = new WebServiceResultFailedLogin($sLogin); $this->LogUsage(__FUNCTION__, $oRes); @@ -240,16 +266,22 @@ public function SearchObjects($sLogin, $sPassword, $sOQL) protected function _SearchObjects($sOQL) { $oRes = new WebServiceResult(); - try { + try + { $oSearch = DBObjectSearch::FromOQL($sOQL); $oSet = new DBObjectSet($oSearch); $aData = $oSet->ToArrayOfValues(); - foreach ($aData as $iRow => $aRow) { + foreach($aData as $iRow => $aRow) + { $oRes->AddResultRow("row_$iRow", $aRow); } - } catch (CoreException $e) { + } + catch (CoreException $e) + { $oRes->LogError($e->getMessage()); - } catch (Exception $e) { + } + catch (Exception $e) + { $oRes->LogError($e->getMessage()); } @@ -257,3 +289,4 @@ protected function _SearchObjects($sOQL) return $oRes; } } +?> diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index a65f56ab19..5df50d4270 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -1,10 +1,9 @@ + /** * Implementation of iTop SOAP services * @@ -24,6 +24,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ + require_once(APPROOT.'/webservices/itopsoaptypes.class.inc.php'); /** @@ -33,6 +34,7 @@ */ class WebServiceResult { + /** * Overall status * @@ -69,32 +71,37 @@ class WebServiceResult public function __construct() { $this->m_bStatus = true; - $this->m_aResult = []; - $this->m_aErrors = []; - $this->m_aWarnings = []; - $this->m_aInfos = []; + $this->m_aResult = array(); + $this->m_aErrors = array(); + $this->m_aWarnings = array(); + $this->m_aInfos = array(); } public function ToSoapStructure() { - $aResults = []; - foreach ($this->m_aResult as $sLabel => $aData) { - $aValues = []; - foreach ($aData as $sKey => $value) { + $aResults = array(); + foreach($this->m_aResult as $sLabel => $aData) + { + $aValues = array(); + foreach($aData as $sKey => $value) + { $aValues[] = new SOAPKeyValue($sKey, $value); } $aResults[] = new SoapResultMessage($sLabel, $aValues); } - $aInfos = []; - foreach ($this->m_aInfos as $sMessage) { + $aInfos = array(); + foreach($this->m_aInfos as $sMessage) + { $aInfos[] = new SoapLogMessage($sMessage); } - $aWarnings = []; - foreach ($this->m_aWarnings as $sMessage) { + $aWarnings = array(); + foreach($this->m_aWarnings as $sMessage) + { $aWarnings[] = new SoapLogMessage($sMessage); } - $aErrors = []; - foreach ($this->m_aErrors as $sMessage) { + $aErrors = array(); + foreach($this->m_aErrors as $sMessage) + { $aErrors[] = new SoapLogMessage($sMessage); } @@ -128,11 +135,11 @@ public function IsOk() public function AddResultObject($sLabel, $oObject) { $oAppContext = new ApplicationContext(); - $this->m_aResult[$sLabel] = [ + $this->m_aResult[$sLabel] = array( 'id' => $oObject->GetKey(), 'name' => $oObject->GetRawName(), 'url' => $oAppContext->MakeObjectUrl(get_class($oObject), $oObject->GetKey(), null, false), // Raw URL without HTML tags - ]; + ); } /** @@ -176,11 +183,8 @@ public function LogWarning($sDescription) */ public function LogIssue($sDescription, $bIsStopper = true) { - if ($bIsStopper) { - $this->LogError($sDescription); - } else { - $this->LogWarning($sDescription); - } + if ($bIsStopper) $this->LogError($sDescription); + else $this->LogWarning($sDescription); } /** @@ -216,7 +220,8 @@ public function GetErrorsAsText() public function GetReturnedDataAsText() { $sRet = ''; - foreach ($this->m_aResult as $sKey => $value) { + foreach ($this->m_aResult as $sKey => $value) + { $sRet .= "===== $sKey =====\n"; $sRet .= print_r($value, true); } @@ -224,6 +229,7 @@ public function GetReturnedDataAsText() } } + /** * Generic response of iTop SOAP services - failed login * @@ -245,12 +251,13 @@ public function __construct($sLogin) */ abstract class WebServicesBase { - public static function GetWSDLContents($sServiceCategory = '') + static public function GetWSDLContents($sServiceCategory = '') { - if ($sServiceCategory == '') { + if ($sServiceCategory == '') + { $sServiceCategory = 'BasicServices'; } - $sWsdlFilePath = call_user_func([$sServiceCategory, 'GetWSDLFilePath']); + $sWsdlFilePath = call_user_func(array($sServiceCategory, 'GetWSDLFilePath')); return file_get_contents($sWsdlFilePath); } @@ -264,14 +271,15 @@ public static function GetWSDLContents($sServiceCategory = '') */ protected function LogUsage($sVerb, $oRes) { - if (!MetaModel::IsLogEnabledWebService()) { - return; - } + if (!MetaModel::IsLogEnabledWebService()) return; $oLog = new EventWebService(); - if ($oRes->IsOk()) { + if ($oRes->IsOk()) + { $oLog->Set('message', $sVerb.' was successfully invoked'); - } else { + } + else + { $oLog->Set('message', $sVerb.' returned errors'); } $oLog->Set('userinfo', UserRights::GetUser()); @@ -283,11 +291,12 @@ protected function LogUsage($sVerb, $oRes) $this->TrimAndSetValue($oLog, 'data', (string)$oRes->GetReturnedDataAsText()); $oLog->DBInsertNoReload(); } - + protected function TrimAndSetValue($oLog, $sAttCode, $sValue) { $oAttDef = MetaModel::GetAttributeDef(get_class($oLog), $sAttCode); - if (is_object($oAttDef)) { + if (is_object($oAttDef)) + { $iMaxSize = $oAttDef->GetMaxSize(); if ($iMaxSize && (mb_strlen($sValue) > $iMaxSize)) { $sValue = mb_substr($sValue, 0, $iMaxSize); @@ -308,9 +317,12 @@ protected function TrimAndSetValue($oLog, $sAttCode, $sValue) protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) { $res = $oTargetObj->CheckValue($sAttCode, $value); - if ($res === true) { + if ($res === true) + { $oTargetObj->Set($sAttCode, $value); - } else { + } + else + { // $res contains the error description $oRes->LogError("Unexpected value for parameter $sParamName: $res"); } @@ -331,24 +343,31 @@ protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, $bIsMandatory = !$oExtKey->IsNullAllowed(); - if (is_null($aExtKeyDesc)) { - if ($bIsMandatory) { + if (is_null($aExtKeyDesc)) + { + if ($bIsMandatory) + { $oRes->LogError("Parameter $sParamName: found null for a mandatory key"); - } else { + } + else + { // skip silently return; } } - if (count($aExtKeyDesc) == 0) { + if (count($aExtKeyDesc) == 0) + { $oRes->LogIssue("Parameter $sParamName: no search condition has been specified", $bIsMandatory); return; } $sKeyClass = $oExtKey->GetTargetClass(); $oReconFilter = new DBObjectSearch($sKeyClass); - foreach ($aExtKeyDesc as $sForeignAttCode => $value) { - if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) { + foreach ($aExtKeyDesc as $sForeignAttCode => $value) + { + if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) + { $aCodes = MetaModel::GetFiltersList($sKeyClass); $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; $oRes->LogIssue($sMsg, $bIsMandatory); @@ -357,24 +376,26 @@ protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, $oReconFilter->AddCondition($sForeignAttCode, $value, '='); } $oExtObjects = new CMDBObjectSet($oReconFilter); - switch ($oExtObjects->Count()) { - case 0: - $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL(true)."')"; - $oRes->LogIssue($sMsg, $bIsMandatory); - break; - case 1: - // Do change the external key attribute - $oForeignObj = $oExtObjects->Fetch(); - $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); - - // Report it (no need to report if the object already had this value - if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { - $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); - } - break; - default: - $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL(true)."')"; - $oRes->LogIssue($sMsg, $bIsMandatory); + switch($oExtObjects->Count()) + { + case 0: + $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL(true)."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + break; + case 1: + // Do change the external key attribute + $oForeignObj = $oExtObjects->Fetch(); + $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); + + // Report it (no need to report if the object already had this value + if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) + { + $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); + } + break; + default: + $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL(true)."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); } } @@ -395,29 +416,34 @@ protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $ $sLinkClass = $oLinkAtt->GetLinkedClass(); $sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote(); - $aItemsFound = []; - $aItemsNotFound = []; - - if (is_null($aLinkList)) { + $aItemsFound = array(); + $aItemsNotFound = array(); + + if (is_null($aLinkList)) + { return $aItemsNotFound; } - foreach ($aLinkList as $aItemData) { - if (!array_key_exists('class', $aItemData)) { + foreach ($aLinkList as $aItemData) + { + if (!array_key_exists('class', $aItemData)) + { $oRes->LogWarning("Parameter $sParamName: missing 'class' specification"); continue; // skip } $sTargetClass = $aItemData['class']; - if (!MetaModel::IsValidClass($sTargetClass)) { + if (!MetaModel::IsValidClass($sTargetClass)) + { $oRes->LogError("Parameter $sParamName: invalid class '$sTargetClass'"); continue; // skip } - if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) { + if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) + { $oRes->LogError("Parameter $sParamName: '$sTargetClass' is not a child class of '$sLinkedClass'"); continue; // skip } $oReconFilter = new DBObjectSearch($sTargetClass); - $aCIStringDesc = []; + $aCIStringDesc = array(); foreach ($aItemData['search'] as $sAttCode => $value) { if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { $aCodes = MetaModel::GetFiltersList($sTargetClass); @@ -429,42 +455,52 @@ protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $ // The attribute is one of our reconciliation key $oReconFilter->AddCondition($sAttCode, $value, '='); } - if (count($aCIStringDesc) == 1) { + if (count($aCIStringDesc) == 1) + { // take the last and unique value to describe the object $sItemDesc = $value; - } else { + } + else + { // describe the object by the given keys $sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')'; } $oExtObjects = new CMDBObjectSet($oReconFilter); - switch ($oExtObjects->Count()) { - case 0: - $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL(true)."')"); - $aItemsNotFound[] = $sItemDesc; - break; - case 1: - $aItemsFound[] = [ - 'object' => $oExtObjects->Fetch(), - 'link_values' => @$aItemData['link_values'], - 'desc' => $sItemDesc, - ]; - break; - default: - $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL(true)."')"); - $aItemsNotFound[] = $sItemDesc; + switch($oExtObjects->Count()) + { + case 0: + $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL(true)."')"); + $aItemsNotFound[] = $sItemDesc; + break; + case 1: + $aItemsFound[] = array ( + 'object' => $oExtObjects->Fetch(), + 'link_values' => @$aItemData['link_values'], + 'desc' => $sItemDesc, + ); + break; + default: + $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL(true)."')"); + $aItemsNotFound[] = $sItemDesc; } } - if (count($aItemsFound) > 0) { - $aLinks = []; - foreach ($aItemsFound as $aItemData) { + if (count($aItemsFound) > 0) + { + $aLinks = array(); + foreach($aItemsFound as $aItemData) + { $oLink = MetaModel::NewObject($sLinkClass); $oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey()); - foreach ($aItemData['link_values'] as $sKey => $value) { - if (!MetaModel::IsValidAttCode($sLinkClass, $sKey)) { + foreach($aItemData['link_values'] as $sKey => $value) + { + if(!MetaModel::IsValidAttCode($sLinkClass, $sKey)) + { $oRes->LogWarning("Parameter $sParamName: Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); - } else { + } + else + { $oLink->Set($sKey, $value); } } @@ -493,71 +529,77 @@ protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $ */ protected function MyObjectInsert($oTargetObj, $sResultLabel, &$oRes) { - if ($oRes->IsOk()) { + if ($oRes->IsOk()) + { list($bRes, $aIssues) = $oTargetObj->CheckToWrite(); - if ($bRes) { + if ($bRes) + { $iId = $oTargetObj->DBInsertNoReload(); $oRes->LogInfo("Created object ".get_class($oTargetObj)."::$iId"); $oRes->AddResultObject($sResultLabel, $oTargetObj); - } else { + } + else + { $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); - foreach ($aIssues as $iIssue => $sIssue) { + foreach($aIssues as $iIssue => $sIssue) + { $oRes->LogError("Issue #$iIssue: $sIssue"); } } } } - protected static function SoapStructToExternalKeySearch($oExternalKeySearch) + + static protected function SoapStructToExternalKeySearch($oExternalKeySearch) { - if (is_null($oExternalKeySearch)) { - return null; - } - if ($oExternalKeySearch->IsVoid()) { - return null; - } + if (is_null($oExternalKeySearch)) return null; + if ($oExternalKeySearch->IsVoid()) return null; - $aRes = []; - foreach ($oExternalKeySearch->conditions as $oSearchCondition) { + $aRes = array(); + foreach($oExternalKeySearch->conditions as $oSearchCondition) + { $aRes[$oSearchCondition->attcode] = $oSearchCondition->value; } return $aRes; } - protected static function SoapStructToLinkCreationSpec(SoapLinkCreationSpec $oLinkCreationSpec) + static protected function SoapStructToLinkCreationSpec(SoapLinkCreationSpec $oLinkCreationSpec) { - $aRes = - [ + $aRes = array + ( 'class' => $oLinkCreationSpec->class, - 'search' => [], - 'link_values' => [], - ]; + 'search' => array(), + 'link_values' => array(), + ); - foreach ($oLinkCreationSpec->conditions as $oSearchCondition) { + foreach($oLinkCreationSpec->conditions as $oSearchCondition) + { $aRes['search'][$oSearchCondition->attcode] = $oSearchCondition->value; } - foreach ($oLinkCreationSpec->attributes as $oAttributeValue) { + foreach($oLinkCreationSpec->attributes as $oAttributeValue) + { $aRes['link_values'][$oAttributeValue->attcode] = $oAttributeValue->value; } return $aRes; } - protected static function SoapStructToAssociativeArray($aArrayOfAssocArray) + static protected function SoapStructToAssociativeArray($aArrayOfAssocArray) { - if (is_null($aArrayOfAssocArray)) { - return []; - } - - $aRes = []; - foreach ($aArrayOfAssocArray as $aAssocArray) { - $aRow = []; - foreach ($aAssocArray as $oKeyValuePair) { + if (is_null($aArrayOfAssocArray)) return array(); + + $aRes = array(); + foreach($aArrayOfAssocArray as $aAssocArray) + { + $aRow = array(); + foreach ($aAssocArray as $oKeyValuePair) + { $aRow[$oKeyValuePair->key] = $oKeyValuePair->value; } - $aRes[] = $aRow; + $aRes[] = $aRow; } return $aRes; } } +?>