diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c3ee2342fa..b7781f12714 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,7 @@ jobs: name: "Test on PHP ${{ matrix.php-version }} using ${{ matrix.db-image }}" needs: "generate-tests-matrix" runs-on: "ubuntu-latest" + timeout-minutes: 60 strategy: fail-fast: false matrix: ${{ fromJson(needs.generate-tests-matrix.outputs.matrix) }} diff --git a/composer.json b/composer.json index 822a899730e..86e96d0ee07 100644 --- a/composer.json +++ b/composer.json @@ -114,7 +114,7 @@ "phpstan/phpstan": "^2.0", "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^11.2", - "squizlabs/php_codesniffer": "^3.10", + "squizlabs/php_codesniffer": "^3.11", "symfony/browser-kit": "^6.4", "symfony/http-client": "^6.4", "symfony/stopwatch": "^6.4", diff --git a/composer.lock b/composer.lock index 98712c794ee..abc1d48f967 100644 --- a/composer.lock +++ b/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": "f879a138212417a701150103005d386e", + "content-hash": "9f2d91a2e8eadd35f60401ccd22b8ee4", "packages": [ { "name": "apereo/phpcas", @@ -9639,16 +9639,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ab4e9b4415a5fc9e4d27f7fe16c8bc9d067dcd6d" + "reference": "46b4d3529b12178112d9008337beda0cc2a1a6b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ab4e9b4415a5fc9e4d27f7fe16c8bc9d067dcd6d", - "reference": "ab4e9b4415a5fc9e4d27f7fe16c8bc9d067dcd6d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46b4d3529b12178112d9008337beda0cc2a1a6b4", + "reference": "46b4d3529b12178112d9008337beda0cc2a1a6b4", "shasum": "" }, "require": { @@ -9693,20 +9693,20 @@ "type": "github" } ], - "time": "2024-11-11T15:43:04+00:00" + "time": "2024-11-28T22:19:37+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "81833b5787e2e8f451b31218875e29e4ed600ab2" + "reference": "1cc1259cb91ee4cfbb5c39bca9f635f067c910b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/81833b5787e2e8f451b31218875e29e4ed600ab2", - "reference": "81833b5787e2e8f451b31218875e29e4ed600ab2", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/1cc1259cb91ee4cfbb5c39bca9f635f067c910b4", + "reference": "1cc1259cb91ee4cfbb5c39bca9f635f067c910b4", "shasum": "" }, "require": { @@ -9738,9 +9738,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.1" }, - "time": "2024-10-26T16:04:11+00:00" + "time": "2024-11-28T21:56:36+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11023,16 +11023,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "3.11.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", "shasum": "" }, "require": { @@ -11099,7 +11099,7 @@ "type": "open_collective" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2024-11-16T12:02:36+00:00" }, { "name": "symfony/browser-kit", diff --git a/css/includes/components/_tabs.scss b/css/includes/components/_tabs.scss index ab538a940be..88bcb2a6dbb 100644 --- a/css/includes/components/_tabs.scss +++ b/css/includes/components/_tabs.scss @@ -69,6 +69,7 @@ &.vertical { .nav-link { border-bottom: 1px solid var(--glpi-tabs-border-color); + border-top-right-radius: 0; &.active { border-left-width: 5px; diff --git a/js/common.js b/js/common.js index 19dd5f69464..77fea4cedad 100644 --- a/js/common.js +++ b/js/common.js @@ -752,11 +752,11 @@ var initMap = function(parent_elt, map_id, height, initial_view = {position: [0, //add map, set a default arbitrary location parent_elt.append($('
')); - var map = L.map(map_id, {fullscreenControl: true}).setView(initial_view.position, initial_view.zoom); + var map = L.map(map_id, {fullscreenControl: true, minZoom: 2}).setView(initial_view.position, initial_view.zoom); //setup tiles and © messages L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap contributors' + attribution: '© OpenStreetMap contributors', }).addTo(map); return map; }; @@ -772,10 +772,10 @@ var showMapForLocation = function(elt) { glpi_html_dialog({ title: __("Display on map"), body: "
", - dialogclass: "modal-lg", + dialogclass: "modal-xl", show: function() { //add map, set a default arbitrary location - var map_elt = initMap($('#location_map_dialog'), 'location_map'); + var map_elt = initMap($('#location_map_dialog'), 'location_map', '500px'); map_elt.spin(true); $.ajax({ diff --git a/js/modules/Form/GeolocationField.js b/js/modules/Form/GeolocationField.js index fec0d89d593..47e35b612d6 100644 --- a/js/modules/Form/GeolocationField.js +++ b/js/modules/Form/GeolocationField.js @@ -49,7 +49,7 @@ class GeolocationField { #init() { // Geolocation may be disabled in the browser (e.g. geo.enabled = false in firefox) if (!navigator.geolocation) { - this.map = initMap($(`#${this.element_id}`), `setlocation_${this.rand}`, '200px'); + this.map = initMap($(`#${this.element_id}`), `setlocation_${this.rand}`, '400px'); this.#finalizeMap(); } else { navigator.geolocation.getCurrentPosition((pos) => { @@ -69,13 +69,13 @@ class GeolocationField { // High accuracy zoom = 20; } - this.map = initMap($(`#${this.element_id}`), `setlocation_${this.rand}`, '200px', { + this.map = initMap($(`#${this.element_id}`), `setlocation_${this.rand}`, '400px', { position: [pos.coords.latitude, pos.coords.longitude], zoom: zoom }); this.#finalizeMap(); }, () => { - this.map = initMap($(`#${this.element_id}`), `setlocation_${this.rand}`, '200px'); + this.map = initMap($(`#${this.element_id}`), `setlocation_${this.rand}`, '400px'); this.#finalizeMap(); }, {enableHighAccuracy: true}); } diff --git a/package-lock.json b/package-lock.json index 906b1261659..c7c43a15568 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,8 +102,8 @@ "script-loader": "^0.7.2", "strip-sourcemap-loader": "^0.0.1", "style-loader": "^3.3.3", - "stylelint": "^16.10.0", - "stylelint-config-standard-scss": "^13.1.0", + "stylelint": "^16.11.0", + "stylelint-config-standard-scss": "^14.0.0", "terser": "^5.36.0", "vue-loader": "^17.2.2", "webpack": "^5.96.1", @@ -1987,9 +1987,9 @@ "integrity": "sha512-EEivNsyV6BtL496m4Q/IeAC6FGlyKjKIT1qMtwaxtkR+2ZlKnf9O7AdcGpClemIBA+TbwWAzp0UyIvYFtKUZ1Q==" }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz", - "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, "funding": [ { @@ -2005,13 +2005,13 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.1" + "@csstools/css-tokenizer": "^3.0.3" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz", - "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", "dev": true, "funding": [ { @@ -2028,32 +2028,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", - "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.1", - "@csstools/css-tokenizer": "^3.0.1" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz", - "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", "dev": true, "funding": [ { @@ -2069,7 +2046,8 @@ "node": ">=18" }, "peerDependencies": { - "postcss-selector-parser": "^6.1.0" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, "node_modules/@cypress/request": { @@ -11092,9 +11070,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", "dev": true }, "node_modules/lazy-ass": { @@ -13468,9 +13446,9 @@ } }, "node_modules/stylelint": { - "version": "16.10.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz", - "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.11.0.tgz", + "integrity": "sha512-zrl4IrKmjJQ+h9FoMp69UMCq5SxeHk0URhxUBj4d3ISzo/DplOFBJZc7t7Dr6otB+1bfbbKNLOmCDpzKSlW+Nw==", "dev": true, "funding": [ { @@ -13484,16 +13462,16 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.1", - "@csstools/css-tokenizer": "^3.0.1", - "@csstools/media-query-list-parser": "^3.0.1", - "@csstools/selector-specificity": "^4.0.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", - "css-tree": "^3.0.0", + "css-tree": "^3.0.1", "debug": "^4.3.7", "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", @@ -13505,16 +13483,16 @@ "ignore": "^6.0.2", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.34.0", + "known-css-properties": "^0.35.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.1", - "postcss": "^8.4.47", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^6.1.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", @@ -13531,33 +13509,43 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz", - "integrity": "sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "engines": { "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.0.0" + "stylelint": "^16.1.0" } }, "node_modules/stylelint-config-recommended-scss": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.0.0.tgz", - "integrity": "sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", + "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", "dev": true, "dependencies": { "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^14.0.0", - "stylelint-scss": "^6.0.0" + "stylelint-config-recommended": "^14.0.1", + "stylelint-scss": "^6.4.0" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^16.0.2" + "stylelint": "^16.6.1" }, "peerDependenciesMeta": { "postcss": { @@ -13566,12 +13554,22 @@ } }, "node_modules/stylelint-config-standard": { - "version": "36.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.0.tgz", - "integrity": "sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug==", + "version": "36.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz", + "integrity": "sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "dependencies": { - "stylelint-config-recommended": "^14.0.0" + "stylelint-config-recommended": "^14.0.1" }, "engines": { "node": ">=18.12.0" @@ -13581,20 +13579,20 @@ } }, "node_modules/stylelint-config-standard-scss": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-13.1.0.tgz", - "integrity": "sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-14.0.0.tgz", + "integrity": "sha512-6Pa26D9mHyi4LauJ83ls3ELqCglU6VfCXchovbEqQUiEkezvKdv6VgsIoMy58i00c854wVmOw0k8W5FTpuaVqg==", "dev": true, "dependencies": { - "stylelint-config-recommended-scss": "^14.0.0", - "stylelint-config-standard": "^36.0.0" + "stylelint-config-recommended-scss": "^14.1.0", + "stylelint-config-standard": "^36.0.1" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^16.3.1" + "stylelint": "^16.11.0" }, "peerDependenciesMeta": { "postcss": { @@ -13603,15 +13601,18 @@ } }, "node_modules/stylelint-scss": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.0.0.tgz", - "integrity": "sha512-N1xV/Ef5PNRQQt9E45unzGvBUN1KZxCI8B4FgN/pMfmyRYbZGVN4y9qWlvOMdScU17c8VVCnjIHTVn38Bb6qSA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.10.0.tgz", + "integrity": "sha512-y03if6Qw9xBMoVaf7tzp5BbnYhYvudIKzURkhSHzcHG0bW0fAYvQpTUVJOe7DyhHaxeThBil4ObEMvGbV7+M+w==", "dev": true, "dependencies": { - "known-css-properties": "^0.29.0", + "css-tree": "^3.0.1", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.35.0", + "mdn-data": "^2.12.2", "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.13", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -13621,6 +13622,66 @@ "stylelint": "^16.0.2" } }, + "node_modules/stylelint-scss/node_modules/css-tree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz", + "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.12.1", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/stylelint-scss/node_modules/css-tree/node_modules/mdn-data": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz", + "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==", + "dev": true + }, + "node_modules/stylelint-scss/node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true + }, + "node_modules/stylelint-scss/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, "node_modules/stylelint/node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -13637,13 +13698,13 @@ "dev": true }, "node_modules/stylelint/node_modules/css-tree": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.0.tgz", - "integrity": "sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz", + "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.10.0", + "mdn-data": "2.12.1", "source-map-js": "^1.0.1" }, "engines": { @@ -13717,18 +13778,24 @@ "node": ">= 4" } }, - "node_modules/stylelint/node_modules/known-css-properties": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", - "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", + "node_modules/stylelint/node_modules/mdn-data": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz", + "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==", "dev": true }, - "node_modules/stylelint/node_modules/mdn-data": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.10.0.tgz", - "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==", + "node_modules/stylelint/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dev": true, - "license": "CC0-1.0" + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } }, "node_modules/stylelint/node_modules/signal-exit": { "version": "4.0.2", diff --git a/package.json b/package.json index 2919a1d02b7..f0183f9d95b 100644 --- a/package.json +++ b/package.json @@ -114,8 +114,8 @@ "script-loader": "^0.7.2", "strip-sourcemap-loader": "^0.0.1", "style-loader": "^3.3.3", - "stylelint": "^16.10.0", - "stylelint-config-standard-scss": "^13.1.0", + "stylelint": "^16.11.0", + "stylelint-config-standard-scss": "^14.0.0", "terser": "^5.36.0", "vue-loader": "^17.2.2", "webpack": "^5.96.1", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3b02da9c2a2..68a4d81b4cf 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -13,6 +13,53 @@ parameters: - src universalObjectCratesClasses: - Sabre\VObject\Node + dynamicConstantNames: + - GLPI_AJAX_DASHBOARD + - GLPI_ALLOW_IFRAME_IN_RICH_TEXT + - GLPI_CACHE_DIR + - GLPI_CALDAV_IMPORT_STATE + - GLPI_CENTRAL_WARNINGS + - GLPI_CONFIG_DIR + - GLPI_CRON_DIR + - GLPI_DISABLE_ONLY_FULL_GROUP_BY_SQL_MODE + - GLPI_DOC_DIR + - GLPI_DOCUMENTATION_ROOT_URL + - GLPI_DUMP_DIR + - GLPI_FORCE_MAIL + - GLPI_GRAPH_DIR + - GLPI_INSTALL_MODE + - GLPI_INVENTORY_DIR + - GLPI_LOCAL_I18N_DIR + - GLPI_LOCK_DIR + - GLPI_LOG_DIR + - GLPI_LOG_LVL + - GLPI_MARKETPLACE_ALLOW_OVERRIDE + - GLPI_MARKETPLACE_DIR + - GLPI_MARKETPLACE_ENABLE + - GLPI_MARKETPLACE_MANUAL_DOWNLOADS + - GLPI_MARKETPLACE_PLUGINS_API_URI + - GLPI_MARKETPLACE_PRERELEASES + - GLPI_NETWORK_REGISTRATION_API_URL + - GLPI_NETWORK_MAIL + - GLPI_NETWORK_SERVICES + - GLPI_PICTURE_DIR + - GLPI_PLUGIN_DOC_DIR + - GLPI_RSS_DIR + - GLPI_SERVERSIDE_URL_ALLOWLIST + - GLPI_SESSION_DIR + - GLPI_SQL_DEBUG + - GLPI_STRICT_DEPRECATED + - GLPI_TELEMETRY_URI + - GLPI_TEXT_MAXSIZE + - GLPI_THEMES_DIR + - GLPI_TMP_DIR + - GLPI_UPLOAD_DIR + - GLPI_USE_CSRF_CHECK + - GLPI_USE_IDOR_CHECK + - GLPI_USER_AGENT_EXTRA_COMMENTS + - GLPI_VAR_DIR + - PLUGINS_DIRECTORIES + - TU_USER ignoreErrors: - '/Instantiated class (DB|DBSlave) not found/' - '/Instantiated class XHProfRuns_Default not found/' diff --git a/phpunit/functional/ChangeTest.php b/phpunit/functional/ChangeTest.php index 1d56f95ba51..dd5afd9980f 100644 --- a/phpunit/functional/ChangeTest.php +++ b/phpunit/functional/ChangeTest.php @@ -318,4 +318,117 @@ public function testStatusWhenSolutionIsRefused() $item = $change->getById($changes_id); $this->assertSame(\CommonITILObject::INCOMING, $item->fields['status']); } + + public function testSearchOptions() + { + $this->login(); + + $last_followup_date = '2016-01-01 00:00:00'; + $last_task_date = '2017-01-01 00:00:00'; + $last_solution_date = '2018-01-01 00:00:00'; + + $change = new \Change(); + $change_id = $change->add( + [ + 'name' => 'ticket title', + 'content' => 'a description', + 'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true), + ] + ); + + $followup = new \ITILFollowup(); + $followup->add([ + 'itemtype' => $change::getType(), + 'items_id' => $change_id, + 'content' => 'followup content', + 'date' => '2015-01-01 00:00:00', + ]); + + $followup->add([ + 'itemtype' => $change::getType(), + 'items_id' => $change_id, + 'content' => 'followup content', + 'date' => '2015-02-01 00:00:00', + ]); + + $task = new \ChangeTask(); + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'changes_id' => $change_id, + 'content' => 'A simple Task', + 'date' => '2015-01-01 00:00:00', + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'changes_id' => $change_id, + 'content' => 'A simple Task', + 'date' => $last_task_date, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'changes_id' => $change_id, + 'content' => 'A simple Task', + 'date' => '2016-01-01 00:00:00', + ]) + ); + + $solution = new \ITILSolution(); + $this->assertGreaterThan( + 0, + (int)$solution->add([ + 'itemtype' => $change::getType(), + 'items_id' => $change_id, + 'content' => 'solution content', + 'date_creation' => '2017-01-01 00:00:00', + 'status' => 2, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$followup->add([ + 'itemtype' => $change::getType(), + 'items_id' => $change_id, + 'add_reopen' => '1', + 'content' => 'This is required', + 'date' => $last_followup_date, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$solution->add([ + 'itemtype' => $change::getType(), + 'items_id' => $change_id, + 'content' => 'solution content', + 'date_creation' => $last_solution_date, + ]) + ); + + $criteria = [ + [ + 'link' => 'AND', + 'field' => 2, + 'searchtype' => 'contains', + 'value' => $change_id, + ] + ]; + $data = \Search::getDatas($change->getType(), ["criteria" => $criteria], [72,73,74]); + $this->assertSame(1, $data['data']['totalcount']); + $change_with_so = $data['data']['rows'][0]['raw']; + $this->assertEquals($change_id, $change_with_so['id']); + $this->assertTrue(array_key_exists('ITEM_Change_72', $change_with_so)); + $this->assertEquals($last_followup_date, $change_with_so['ITEM_Change_72']); + $this->assertTrue(array_key_exists('ITEM_Change_73', $change_with_so)); + $this->assertEquals($last_task_date, $change_with_so['ITEM_Change_73']); + $this->assertTrue(array_key_exists('ITEM_Change_74', $change_with_so)); + $this->assertEquals($last_solution_date, $change_with_so['ITEM_Change_74']); + } } diff --git a/phpunit/functional/Glpi/Features/AssignableItem.php b/phpunit/functional/Glpi/Features/AssignableItem.php index 17e86b05262..4e8e03c2049 100644 --- a/phpunit/functional/Glpi/Features/AssignableItem.php +++ b/phpunit/functional/Glpi/Features/AssignableItem.php @@ -36,6 +36,7 @@ namespace tests\units\Glpi\Features; use Group_Item; +use PHPUnit\Framework\Attributes\DataProvider; class AssignableItem extends \DbTestCase { @@ -53,9 +54,7 @@ protected function itemtypeProvider(): iterable } } - /** - * @dataProvider itemtypeProvider - */ + #[DataProvider('itemtypeProvider')] public function testClassUsesTrait(string $class): void { $this->boolean(in_array(\Glpi\Features\AssignableItem::class, class_uses($class), true))->isTrue(); @@ -64,9 +63,8 @@ public function testClassUsesTrait(string $class): void /** * Test adding an item with the groups_id/groups_id_tech field as an array and null. * Test updating an item with the groups_id/groups_id_tech field as an array and null. - * - * @dataProvider itemtypeProvider */ + #[DataProvider('itemtypeProvider')] public function testAddAndUpdateMultipleGroups(string $class): void { $this->login(); // login to bypass some rights checks (e.g. on domain records) @@ -116,9 +114,8 @@ public function testAddAndUpdateMultipleGroups(string $class): void /** * Test the loading item which still have integer values for groups_id/groups_id_tech (0 for no group). * The value should be automatically normalized to an array. If the group was '0', the array should be empty. - * - * @dataProvider itemtypeProvider */ + #[DataProvider('itemtypeProvider')] public function testLoadGroupsFromDb(string $class): void { /** @var \DBmysql $DB */ @@ -183,9 +180,8 @@ public function testLoadGroupsFromDb(string $class): void /** * An empty item should have the groups_id/groups_id_tech fields initialized as an empty array. - * - * @dataProvider itemtypeProvider */ + #[DataProvider('itemtypeProvider')] public function testGetEmpty(string $class): void { $item = new $class(); @@ -196,9 +192,8 @@ public function testGetEmpty(string $class): void /** * Check that adding and updating an item with groups_id/groups_id_tech as an integer still works (minor BC, mainly for API scripts). - * - * @dataProvider itemtypeProvider */ + #[DataProvider('itemtypeProvider')] public function testAddUpdateWithIntGroups(string $class): void { $this->login(); // login to bypass some rights checks (e.g. on domain records) diff --git a/phpunit/functional/Glpi/Inventory/Assets/DriveTest.php b/phpunit/functional/Glpi/Inventory/Assets/DriveTest.php index 64917451912..eeb43a8565a 100644 --- a/phpunit/functional/Glpi/Inventory/Assets/DriveTest.php +++ b/phpunit/functional/Glpi/Inventory/Assets/DriveTest.php @@ -497,6 +497,7 @@ public function testInventoryUpdateHardDrive() PCI 256060 BXV77D0Q + IDE Samsung PM951 NVMe SAMSUNG 256GB nvme0n1 diff --git a/phpunit/functional/Glpi/Inventory/Assets/MemoryTest.php b/phpunit/functional/Glpi/Inventory/Assets/MemoryTest.php index d1c1350f6d7..63f7395dc94 100644 --- a/phpunit/functional/Glpi/Inventory/Assets/MemoryTest.php +++ b/phpunit/functional/Glpi/Inventory/Assets/MemoryTest.php @@ -90,14 +90,14 @@ public function testHandle() { $computer = getItemByTypeName('Computer', '_test_pc01'); - //first, check there are no controller linked to this computer + //first, check there are no controller linked to this computer $idm = new \Item_DeviceMemory(); $this->assertFalse( $idm->getFromDbByCrit(['items_id' => $computer->fields['id'], 'itemtype' => 'Computer']), 'A memory is already linked to computer!' ); - //convert data + //convert data $expected = $this->assetProvider()[0]; $converter = new \Glpi\Inventory\Converter(); @@ -110,7 +110,7 @@ public function testHandle() $result = $asset->prepare(); $this->assertEquals(json_decode($expected['expected']), $result[0]); - //handle + //handle $asset->handleLinks(); $asset->handle(); $this->assertTrue( @@ -240,10 +240,10 @@ public function testInventoryUpdate() $this->assertEquals(0, $memory['is_dynamic']); } - //computer inventory knows only "Bottom-Slot 1(left)" and "Bottom-Slot 2(right)" memories + //computer inventory knows only "Bottom-Slot 1(left)" and "Bottom-Slot 2(right)" memories $this->doInventory($xml_source, true); - //we still have 2 memory devices + //we still have 2 memory devices $memories = $device_mem->find(); $this->assertCount(2, $memories); @@ -253,20 +253,20 @@ public function testInventoryUpdate() countElementsInTable($mem_model->getTable()) ); - //we still have 3 memories items linked to the computer + //we still have 3 memories items linked to the computer $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id]); $this->assertCount(3, $memories); - //memories present in the inventory source are now dynamic + //memories present in the inventory source are now dynamic $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id, 'is_dynamic' => 1]); $this->assertCount(2, $memories); - //memory not present in the inventory is still not dynamic + //memory not present in the inventory is still not dynamic $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id, 'is_dynamic' => 0]); $this->assertCount(1, $memories); - //Redo inventory, but with removed "Bottom-Slot 2(right)" memory - //and a different memory model in Slot 1 + //Redo inventory, but with removed "Bottom-Slot 2(right)" memory + //and a different memory model in Slot 1 $xml_source = " @@ -301,16 +301,97 @@ public function testInventoryUpdate() // 'DDR4 - 2133 - SODIMM' (MODEL-A) $this->assertCount(1, $device_mem->find(['devicememorymodels_id' => $mem_model_id])); - //we now have 2 memories linked to computer only + //we now have 2 memories linked to computer only $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id]); $this->assertCount(2, $memories); - //memory present in the inventory source is still dynamic + //memory present in the inventory source is still dynamic $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id, 'is_dynamic' => 1]); $this->assertCount(1, $memories); - //memory not present in the inventory is still not dynamic + //memory not present in the inventory is still not dynamic $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id, 'is_dynamic' => 0]); $this->assertCount(1, $memories); } + + public function testMemoryOneManufactuerOneNot() + { + $device_mem = new \DeviceMemory(); + $item_mem = new \Item_DeviceMemory(); + + $json_str = <<doInventory($json); + + $computer = new \Computer(); + $this->assertTrue( + $computer->getFromDBByCrit([ + 'name' => 'pc_with_memories' // a computer that remembers + ]) + ); + $computers_id = $computer->fields['id']; + + //we have 2 memories items linked to the computer + $memories = $item_mem->find(['itemtype' => 'Computer', 'items_id' => $computers_id]); + $this->assertCount( + 2, + $memories, + print_r($memories, true) + ); + + $manufacturer = new \Manufacturer(); + $manufacturers = $manufacturer->find(); + //2 manufacturers ine db: "Hynix" from current inv, "My Manufacturer" from bootstrap data + $this->assertCount( + 2, + $manufacturers, + print_r($manufacturers, true) + ); + + //we have 2 memory devices: one for Hynix, one without manufacturer + $memories = $device_mem->find(); + $this->assertCount( + 2, + $memories, + print_r($memories, true) + ); + } } diff --git a/phpunit/functional/Glpi/Inventory/InventoryTest.php b/phpunit/functional/Glpi/Inventory/InventoryTest.php index 19d4d4b893b..6ec570f44d5 100644 --- a/phpunit/functional/Glpi/Inventory/InventoryTest.php +++ b/phpunit/functional/Glpi/Inventory/InventoryTest.php @@ -1463,7 +1463,9 @@ public function testUpdateComputer() //check memory $this->assertCount(2, $components['Item_DeviceMemory']); $mem_component1 = array_pop($components['Item_DeviceMemory']); + $this->assertIsArray($mem_component1); $mem_component2 = array_pop($components['Item_DeviceMemory']); + $this->assertIsArray($mem_component2); $this->assertGreaterThan(0, $mem_component1['devicememories_id']); $expected_mem_component = [ 'items_id' => $mem_component1['items_id'], @@ -1480,11 +1482,14 @@ public function testUpdateComputer() 'locations_id' => 0, 'states_id' => 0 ]; - $this->assertIsArray($mem_component1); + $this->assertSame($expected_mem_component, $mem_component1); - $expected_mem_component['busID'] = "1"; - $this->assertIsArray($mem_component2); - $this->assertSame($expected_mem_component, $mem_component2); + + $expected_mem_component2 = $expected_mem_component; + $expected_mem_component2['busID'] = "1"; + //device is different, because no manufacturer is set on second memory slot + $expected_mem_component2['devicememories_id'] = $mem_component2['devicememories_id']; + $this->assertSame($expected_mem_component2, $mem_component2); //software $isoft = new \Item_SoftwareVersion(); @@ -1626,12 +1631,10 @@ public function testUpdateComputer() $mem_component1 = array_pop($components['Item_DeviceMemory']); $mem_component2 = array_pop($components['Item_DeviceMemory']); $this->assertGreaterThan(0, $mem_component1['devicememories_id']); - $expected_mem_component['busID'] = "2"; $this->assertIsArray($mem_component1); $this->assertSame($expected_mem_component, $mem_component1); - $expected_mem_component['busID'] = "1"; $this->assertIsArray($mem_component2); - $this->assertSame($expected_mem_component, $mem_component2); + $this->assertSame($expected_mem_component2, $mem_component2); //software $isoft = new \Item_SoftwareVersion(); @@ -1820,9 +1823,13 @@ public function testUpdateComputer() ]; $this->assertIsArray($mem_component1); $this->assertSame($expected_mem_component, $mem_component1); - $expected_mem_component['busID'] = "1"; + + $expected_mem_component2 = $expected_mem_component; + $expected_mem_component2['busID'] = "1"; + //device is different, because no manufacturer is set on second memory slot + $expected_mem_component2['devicememories_id'] = $mem_component2['devicememories_id']; $this->assertIsArray($mem_component2); - $this->assertSame($expected_mem_component, $mem_component2); + $this->assertSame($expected_mem_component2, $mem_component2); //software $isoft = new \Item_SoftwareVersion(); diff --git a/phpunit/functional/ProblemTest.php b/phpunit/functional/ProblemTest.php index 95ab1cf94dc..1a4623ec829 100644 --- a/phpunit/functional/ProblemTest.php +++ b/phpunit/functional/ProblemTest.php @@ -194,4 +194,117 @@ public function testGetTeamRoleName(): void $this->assertNotEmpty(\Problem::getTeamRoleName($role)); } } + + public function testSearchOptions() + { + $this->login(); + + $last_followup_date = '2016-01-01 00:00:00'; + $last_task_date = '2017-01-01 00:00:00'; + $last_solution_date = '2018-01-01 00:00:00'; + + $problem = new \Problem(); + $problem_id = $problem->add( + [ + 'name' => 'ticket title', + 'content' => 'a description', + 'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true), + ] + ); + + $followup = new \ITILFollowup(); + $followup->add([ + 'itemtype' => $problem::getType(), + 'items_id' => $problem_id, + 'content' => 'followup content', + 'date' => '2015-01-01 00:00:00', + ]); + + $followup->add([ + 'itemtype' => $problem::getType(), + 'items_id' => $problem_id, + 'content' => 'followup content', + 'date' => '2015-02-01 00:00:00', + ]); + + $task = new \ProblemTask(); + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'problems_id' => $problem_id, + 'content' => 'A simple Task', + 'date' => '2015-01-01 00:00:00', + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'problems_id' => $problem_id, + 'content' => 'A simple Task', + 'date' => $last_task_date, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'problems_id' => $problem_id, + 'content' => 'A simple Task', + 'date' => '2016-01-01 00:00:00', + ]) + ); + + $solution = new \ITILSolution(); + $this->assertGreaterThan( + 0, + (int)$solution->add([ + 'itemtype' => $problem::getType(), + 'items_id' => $problem_id, + 'content' => 'solution content', + 'date_creation' => '2017-01-01 00:00:00', + 'status' => 2, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$followup->add([ + 'itemtype' => $problem::getType(), + 'items_id' => $problem_id, + 'add_reopen' => '1', + 'content' => 'This is required', + 'date' => $last_followup_date, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$solution->add([ + 'itemtype' => $problem::getType(), + 'items_id' => $problem_id, + 'content' => 'solution content', + 'date_creation' => $last_solution_date, + ]) + ); + + $criteria = [ + [ + 'link' => 'AND', + 'field' => 2, + 'searchtype' => 'contains', + 'value' => $problem_id, + ] + ]; + $data = \Search::getDatas($problem->getType(), ["criteria" => $criteria], [72,73,74]); + $this->assertSame(1, $data['data']['totalcount']); + $problem_with_so = $data['data']['rows'][0]['raw']; + $this->assertEquals($problem_id, $problem_with_so['id']); + $this->assertTrue(array_key_exists('ITEM_Problem_72', $problem_with_so)); + $this->assertEquals($last_followup_date, $problem_with_so['ITEM_Problem_72']); + $this->assertTrue(array_key_exists('ITEM_Problem_73', $problem_with_so)); + $this->assertEquals($last_task_date, $problem_with_so['ITEM_Problem_73']); + $this->assertTrue(array_key_exists('ITEM_Problem_74', $problem_with_so)); + $this->assertEquals($last_solution_date, $problem_with_so['ITEM_Problem_74']); + } } diff --git a/phpunit/functional/SearchTest.php b/phpunit/functional/SearchTest.php index fcf1f2293e1..fdb9a40b4a0 100644 --- a/phpunit/functional/SearchTest.php +++ b/phpunit/functional/SearchTest.php @@ -4974,7 +4974,7 @@ public function testCommonITILSatisfactionEndDate() 'value' => __FUNCTION__, ], [ - 'field' => 72, // satisfaction end date + 'field' => 75, // satisfaction end date 'searchtype' => 'contains', 'value' => '', ], @@ -4988,7 +4988,7 @@ public function testCommonITILSatisfactionEndDate() foreach ($data['data']['rows'] as $row) { $items[] = [ $row['raw']['ITEM_Ticket_2'], - $row['raw']['ITEM_Ticket_72'], + $row['raw']['ITEM_Ticket_75'], ]; } $expected = [ diff --git a/phpunit/functional/TicketTest.php b/phpunit/functional/TicketTest.php index 6196a7b5c1d..009ec0d571a 100644 --- a/phpunit/functional/TicketTest.php +++ b/phpunit/functional/TicketTest.php @@ -541,6 +541,119 @@ public function testCreateTicketWithActors(array $actors_input, array $expected_ $this->checkActors($ticket, $expected_actors); } + public function testSearchOptions() + { + $this->login(); + + $last_followup_date = '2016-01-01 00:00:00'; + $last_task_date = '2017-01-01 00:00:00'; + $last_solution_date = '2018-01-01 00:00:00'; + + $ticket = new \Ticket(); + $ticket_id = $ticket->add( + [ + 'name' => 'ticket title', + 'content' => 'a description', + 'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true), + ] + ); + + $followup = new \ITILFollowup(); + $followup->add([ + 'itemtype' => $ticket::getType(), + 'items_id' => $ticket_id, + 'content' => 'followup content', + 'date' => '2015-01-01 00:00:00', + ]); + + $followup->add([ + 'itemtype' => $ticket::getType(), + 'items_id' => $ticket_id, + 'content' => 'followup content', + 'date' => '2015-02-01 00:00:00', + ]); + + $task = new \TicketTask(); + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'tickets_id' => $ticket_id, + 'content' => 'A simple Task', + 'date' => '2015-01-01 00:00:00', + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'tickets_id' => $ticket_id, + 'content' => 'A simple Task', + 'date' => $last_task_date, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$task->add([ + 'tickets_id' => $ticket_id, + 'content' => 'A simple Task', + 'date' => '2016-01-01 00:00:00', + ]) + ); + + $solution = new \ITILSolution(); + $this->assertGreaterThan( + 0, + (int)$solution->add([ + 'itemtype' => $ticket::getType(), + 'items_id' => $ticket_id, + 'content' => 'solution content', + 'date_creation' => '2017-01-01 00:00:00', + 'status' => 2, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$followup->add([ + 'itemtype' => $ticket::getType(), + 'items_id' => $ticket_id, + 'add_reopen' => '1', + 'content' => 'This is required', + 'date' => $last_followup_date, + ]) + ); + + $this->assertGreaterThan( + 0, + (int)$solution->add([ + 'itemtype' => $ticket::getType(), + 'items_id' => $ticket_id, + 'content' => 'solution content', + 'date_creation' => $last_solution_date, + ]) + ); + + $criteria = [ + [ + 'link' => 'AND', + 'field' => 2, + 'searchtype' => 'contains', + 'value' => $ticket_id, + ] + ]; + $data = \Search::getDatas($ticket->getType(), ["criteria" => $criteria], [72,73,74]); + $this->assertSame(1, $data['data']['totalcount']); + $ticket_with_so = $data['data']['rows'][0]['raw']; + $this->assertEquals($ticket_id, $ticket_with_so['id']); + $this->assertTrue(array_key_exists('ITEM_Ticket_72', $ticket_with_so)); + $this->assertEquals($last_followup_date, $ticket_with_so['ITEM_Ticket_72']); + $this->assertTrue(array_key_exists('ITEM_Ticket_73', $ticket_with_so)); + $this->assertEquals($last_task_date, $ticket_with_so['ITEM_Ticket_73']); + $this->assertTrue(array_key_exists('ITEM_Ticket_74', $ticket_with_so)); + $this->assertEquals($last_solution_date, $ticket_with_so['ITEM_Ticket_74']); + } + public static function updateActorsProvider(): iterable { diff --git a/phpunit/functional/TicketValidationTest.php b/phpunit/functional/TicketValidationTest.php index e5369820da2..5a9731e2252 100644 --- a/phpunit/functional/TicketValidationTest.php +++ b/phpunit/functional/TicketValidationTest.php @@ -35,7 +35,9 @@ namespace tests\units; +use CommonITILObject; use Glpi\PHPUnit\Tests\CommonITILValidation; +use PHPUnit\Framework\Attributes\DataProvider; /* Test for src/TicketValidation.php */ class TicketValidationTest extends CommonITILValidation @@ -331,4 +333,47 @@ public function testGroupUserApproval() $this->assertTrue($ticket->getFromDB($tid)); $this->assertEquals(\CommonITILValidation::ACCEPTED, (int)$ticket->getField('global_validation')); } + + public static function testgetNumberToValidateProvider(): array + { + return [ + [ + 'input' => [ + 'name' => 'Ticket_Closed_With_Validation_Request', + 'content' => 'Ticket_Closed_With_Validation_Request', + ], + 'expected' => 1, + 'user_id' => getItemByTypeName('User', 'glpi', true) + ], + [ + 'input' => [ + 'name' => 'Ticket_With_Validation_Request', + 'content' => 'Ticket_With_Validation_Request', + 'status' => CommonITILObject::CLOSED + ], + 'expected' => 0, + 'user_id' => getItemByTypeName('User', 'glpi', true) + ], + ]; + } + + #[DataProvider('testgetNumberToValidateProvider')] + public function testgetNumberToValidate( + array $input, + int $expected, + int $user_id + ): void { + $this->login(); + + /** Create a ticket, approval requested */ + $ticket = $this->createItem('Ticket', $input); + + $this->createItem('TicketValidation', [ + 'tickets_id' => $ticket->getID(), + 'itemtype_target' => 'User', + 'items_id_target' => $user_id, + ]); + + $this->assertEquals($expected, \TicketValidation::getNumberToValidate($user_id)); + } } diff --git a/phpunit/functional/UserTest.php b/phpunit/functional/UserTest.php index 136c61e9423..f72684b2188 100644 --- a/phpunit/functional/UserTest.php +++ b/phpunit/functional/UserTest.php @@ -631,7 +631,18 @@ public function testPrepareInputForUpdateSensitiveFields(): void foreach ($inputs as $key => $value) { $output = $target_user->prepareInputForUpdate(['id' => $target_user->getID(), $key => $value]); - $this->assertEquals($can, \array_key_exists($key, $output)); + if (is_array($output)) { + $this->assertEquals($can, \array_key_exists($key, $output)); + } else { + $this->assertFalse($can); + $this->assertFalse($output); + $this->hasSessionMessages(ERROR, [ + sprintf( + __('You are not allowed to update the following fields: %s'), + $key + ) + ]); + } } } } diff --git a/phpunit/src/CommonITILValidation.php b/phpunit/src/CommonITILValidation.php index 448f867e055..3cdfa2e3433 100644 --- a/phpunit/src/CommonITILValidation.php +++ b/phpunit/src/CommonITILValidation.php @@ -754,8 +754,15 @@ public function testCreateValidation() $this->assertGreaterThan(0, (int) $validations_id); $this->assertEquals(1, $validation_class::getNumberToValidate($user->getID())); + $itil_item_2 = $this->createItem($itil_class, [ + 'name' => __FUNCTION__ . '2', + 'content' => __FUNCTION__ . '2', + 'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true), + 'status' => \CommonITILObject::INCOMING, + ]); + $validations_id = $validation->add([ - $itil_class::getForeignKeyField() => $itil_item->getID(), + $itil_class::getForeignKeyField() => $itil_item_2->getID(), 'itemtype_target' => 'Group', 'items_id_target' => $group->getID(), 'status' => \CommonITILValidation::WAITING, diff --git a/src/CommonITILObject.php b/src/CommonITILObject.php index c85a5bd464e..9635adba926 100644 --- a/src/CommonITILObject.php +++ b/src/CommonITILObject.php @@ -4681,6 +4681,21 @@ public function getSearchOptionsSolution() ] ]; + $tab[] = [ + 'id' => '74', + 'table' => ITILSolution::getTable(), + 'field' => 'date_creation', + 'name' => _n('Latest date', 'Latest dates', 1), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'forcegroupby' => true, + 'joinparams' => [ + 'jointype' => 'itemtype_item', + ], + 'computation' => QueryFunction::max('TABLE.date_creation'), + 'nometa' => true // cannot GROUP_CONCAT a MAX + ]; + return $tab; } @@ -7715,8 +7730,9 @@ class_exists($validation_class) && $params['with_validations'] $validation->post_getFromDB(); $canedit = $validation_obj->can($validations_id, UPDATE); - $cananswer = ($validation_obj->canValidate($this->getID()) - && $validation_row['status'] == CommonITILValidation::WAITING); + $cananswer = $validation_obj->canValidate($this->getID()) + && $validation_row['status'] == CommonITILValidation::WAITING + && !in_array($this->fields['status'], $this->getClosedStatusArray()); $user = new User(); $user->getFromDB($validation_row['users_id_validate']); diff --git a/src/CommonITILSatisfaction.php b/src/CommonITILSatisfaction.php index 22cf31465f5..f4a47be530c 100644 --- a/src/CommonITILSatisfaction.php +++ b/src/CommonITILSatisfaction.php @@ -462,7 +462,7 @@ public static function rawSearchOptionsToAdd() $subquery = new QueryExpression("($sql) AS durations"); $tab[] = [ - 'id' => 72 + $base_id, + 'id' => 75 + $base_id, 'table' => $table, 'field' => 'inquest_duration', 'name' => __('End date'), diff --git a/src/CommonITILTask.php b/src/CommonITILTask.php index 790f4ced4e2..c4bbab23eaa 100644 --- a/src/CommonITILTask.php +++ b/src/CommonITILTask.php @@ -1003,6 +1003,8 @@ public function rawSearchOptions() **/ public static function rawSearchOptionsToAdd($itemtype = null) { + /** @var \DBmysql $DB */ + global $DB; $task = new static(); $tab = []; @@ -1181,6 +1183,22 @@ public static function rawSearchOptionsToAdd($itemtype = null) ] ]; + $tab[] = [ + 'id' => '73', + 'table' => static::getTable(), + 'field' => 'date', + 'name' => _n('Latest date', 'Latest dates', 1), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'forcegroupby' => true, + 'joinparams' => [ + 'jointype' => 'child', + 'condition' => $task_condition + ], + 'computation' => QueryFunction::max('TABLE.date'), + 'nometa' => true // cannot GROUP_CONCAT a MAX + ]; + $tab[] = [ 'id' => '33', 'table' => static::getTable(), diff --git a/src/CommonITILValidation.php b/src/CommonITILValidation.php index 8b17216d07a..2169ef77274 100644 --- a/src/CommonITILValidation.php +++ b/src/CommonITILValidation.php @@ -712,11 +712,22 @@ public static function getNumberToValidate($users_id) global $DB; $row = $DB->request([ - 'FROM' => static::getTable(), + 'FROM' => static::$itemtype::getTable(), 'COUNT' => 'cpt', 'WHERE' => [ - 'status' => self::WAITING, - static::getTargetCriteriaForUser($users_id) + [ + 'id' => new QuerySubQuery([ + 'SELECT' => static::$items_id, + 'FROM' => static::getTable(), + 'WHERE' => [ + 'status' => self::WAITING, + static::getTargetCriteriaForUser($users_id), + ] + ]) + ], + 'NOT' => [ + 'status' => static::$itemtype::getClosedStatusArray(), + ], ] ])->current(); diff --git a/src/DeviceSimcard.php b/src/DeviceSimcard.php index 8946ac92625..5af4c1dc47c 100644 --- a/src/DeviceSimcard.php +++ b/src/DeviceSimcard.php @@ -105,7 +105,7 @@ public function getImportCriteria() return [ 'designation' => 'equal', 'manufacturers_id' => 'equal', - 'devicesensortypes_id' => 'equal', + 'devicesimcardtypes_id' => 'equal', ]; } diff --git a/src/Dropdown.php b/src/Dropdown.php index 6d1c04d6ce2..c894c5c67b9 100644 --- a/src/Dropdown.php +++ b/src/Dropdown.php @@ -2335,7 +2335,11 @@ public static function showFromArray($name, array $elements, $options = []) $to_display[] = htmlescape($elements[$value]); } } - $output .= '' . implode(', ', $to_display) . ''; + $output .= ''; } else { if ($param['multiple']) { // Fix for multiple select not sending any form data when no option is selected diff --git a/src/Glpi/Inventory/Asset/Device.php b/src/Glpi/Inventory/Asset/Device.php index f840481485e..ed68ac3926b 100644 --- a/src/Glpi/Inventory/Asset/Device.php +++ b/src/Glpi/Inventory/Asset/Device.php @@ -101,6 +101,12 @@ public function handle() //create device or get existing device ID $device_input = $this->handleInput($val, $device); + $device_criteria = $device->getImportCriteria(); + foreach (array_keys($device_criteria) as $device_criterion) { + if (!isset($device_input[$device_criterion]) && \isForeignKeyField($device_criterion)) { + $device_input[$device_criterion] = 0; + } + } $device_id = $device->import($device_input + ['with_history' => false]); $i_criteria = $itemdevice->getImportCriteria(); diff --git a/src/Glpi/Inventory/Request.php b/src/Glpi/Inventory/Request.php index c8c216f12fd..d7739fb4221 100644 --- a/src/Glpi/Inventory/Request.php +++ b/src/Glpi/Inventory/Request.php @@ -319,7 +319,24 @@ public function contact(mixed $data): void //For the moment it's the Agent who informs us about the active tasks $raw_data = $this->inventory->getRawData(); if ($raw_data !== null && property_exists($raw_data, 'enabled-tasks')) { - foreach ($raw_data->{'enabled-tasks'} as $task) { + $enabled_tasks = $raw_data->{'enabled-tasks'}; + + // The following tasks depends on inventory. + // When they are enabled, we assume that inventory is enabled. + $taskneededinv = [ + 'esx', + 'netdiscovery', + 'netinventory', + 'remoteinventory', + ]; + if ( + !empty(array_intersect($enabled_tasks, $taskneededinv)) && + !in_array('inventory', $enabled_tasks) + ) { + $enabled_tasks[] = 'inventory'; + } + + foreach ($enabled_tasks as $task) { $handle = $this->handleTask($task); if (count($handle)) { // Insert related task information under tasks list property diff --git a/src/Glpi/Plugin/Hooks.php b/src/Glpi/Plugin/Hooks.php index 655dc77cc5c..f003e73844c 100644 --- a/src/Glpi/Plugin/Hooks.php +++ b/src/Glpi/Plugin/Hooks.php @@ -43,9 +43,10 @@ class Hooks const CSRF_COMPLIANT = 'csrf_compliant'; // File hooks - const ADD_CSS = 'add_css'; - const ADD_JAVASCRIPT = 'add_javascript'; - const ADD_HEADER_TAG = 'add_header_tag'; + const ADD_CSS = 'add_css'; + const ADD_JAVASCRIPT = 'add_javascript'; + const ADD_JAVASCRIPT_MODULE = 'add_javascript_module'; + const ADD_HEADER_TAG = 'add_header_tag'; // Function hooks with no parameters const CHANGE_ENTITY = 'change_entity'; diff --git a/src/Glpi/Search/Output/HTMLSearchOutput.php b/src/Glpi/Search/Output/HTMLSearchOutput.php index c0dc7eee4bb..c72ff61b3e0 100644 --- a/src/Glpi/Search/Output/HTMLSearchOutput.php +++ b/src/Glpi/Search/Output/HTMLSearchOutput.php @@ -189,6 +189,8 @@ public function displayData(array $data, array $params = []) $active_sort = true; } + $count = $data['data']['totalcount'] ?? 0; + $rand = mt_rand(); TemplateRenderer::getInstance()->display('components/search/display_data.html.twig', [ 'search_error' => $search_error, @@ -201,7 +203,7 @@ public function displayData(array $data, array $params = []) 'sort' => $search['sort'] ?? [], 'start' => $search['start'] ?? 0, 'limit' => $_SESSION['glpilist_limit'], - 'count' => $data['data']['totalcount'] ?? 0, + 'count' => $count, 'item' => $item, 'itemtype' => $itemtype, 'href' => $href, @@ -216,8 +218,9 @@ public function displayData(array $data, array $params = []) || count(\MassiveAction::getAllMassiveActions($item, $is_deleted)) ), 'massiveactionparams' => $data['search']['massiveactionparams'] + [ - 'is_deleted' => $is_deleted, - 'container' => "massform$itemtype$rand", + 'num_displayed' => min($_SESSION['glpilist_limit'], $count), + 'is_deleted' => $is_deleted, + 'container' => "massform$itemtype$rand", ], 'can_config' => \Session::haveRightsOr('search_config', [ \DisplayPreference::PERSONAL, diff --git a/src/Html.php b/src/Html.php index c24394bd114..c25bbb08f44 100644 --- a/src/Html.php +++ b/src/Html.php @@ -2518,10 +2518,15 @@ public static function showMassiveActions($options = []) !$p['ontop'] || (isset($p['forcecreate']) && $p['forcecreate']) ) { - $out .= ""; - $out .= __s('Selection too large, massive action disabled.') . ""; + $out .= " + " + . __s('Selection too large, massive action disabled.') . + ""; if ($_SESSION['glpi_use_mode'] === Session::DEBUG_MODE) { - $out .= __s('To increase the limit: change max_input_vars or suhosin.post.max_vars in php configuration.'); + $out .= Html::showToolTip( + __s('To increase the limit: change max_input_vars or suhosin.post.max_vars in php configuration.'), + ['display' => false, 'awesome-class' => 'btn btn-sm border-danger text-danger me-1 fa-info'] + ); } } } else { diff --git a/src/ITILFollowup.php b/src/ITILFollowup.php index b61e71efa68..cacb2417a1f 100644 --- a/src/ITILFollowup.php +++ b/src/ITILFollowup.php @@ -34,6 +34,7 @@ */ use Glpi\Application\View\TemplateRenderer; +use Glpi\DBAL\QueryFunction; use Glpi\DBAL\QuerySubQuery; /** @@ -726,6 +727,8 @@ public function rawSearchOptions() public static function rawSearchOptionsToAdd($itemtype = null) { + /** @var \DBmysql $DB */ + global $DB; $tab = []; @@ -774,6 +777,24 @@ public static function rawSearchOptionsToAdd($itemtype = null) ] ]; + $tab[] = [ + 'id' => '72', + 'table' => static::getTable(), + 'field' => 'date', + 'name' => _n('Latest date', 'Latest dates', 1), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'forcegroupby' => true, + 'usehaving' => true, + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => $followup_condition + ], + 'computation' => QueryFunction::max('TABLE.date'), + 'nometa' => true // cannot GROUP_CONCAT a MAX + ]; + + $tab[] = [ 'id' => '27', 'table' => static::getTable(), diff --git a/src/Location.php b/src/Location.php index 0b56a58fa46..0fe057dac40 100644 --- a/src/Location.php +++ b/src/Location.php @@ -105,11 +105,6 @@ public function getAdditionalFields() 'label' => __('Room number'), 'type' => 'text', 'list' => true - ], [ - 'name' => 'setlocation', - 'type' => 'setlocation', - 'label' => __('Location on map'), - 'list' => false ], [ 'name' => 'latitude', 'label' => __('Latitude'), @@ -125,6 +120,17 @@ public function getAdditionalFields() 'label' => __('Altitude'), 'type' => 'text', 'list' => true + ], [ + 'name' => 'setlocation', + 'type' => 'setlocation', + 'label' => __('Location on map'), + 'list' => false, + 'form_params' => [ + 'full_width' => true, + 'full_width_adapt_column' => false, + 'label_class' => 'col-xxl-2', + 'input_class' => 'col-xxl-10', + ] ] ]; } diff --git a/src/PendingReason.php b/src/PendingReason.php index 8f4236616eb..3ab49053ca2 100644 --- a/src/PendingReason.php +++ b/src/PendingReason.php @@ -66,7 +66,7 @@ public function getAdditionalFields() 'name' => 'is_pending_per_default', 'label' => __('Pending per default'), 'type' => 'bool', - 'params' => [ + 'form_params' => [ 'disabled' => !$this->fields['is_default'], ], ], diff --git a/src/RuleCommonITILObject.php b/src/RuleCommonITILObject.php index ec44002e31b..57440378dd3 100644 --- a/src/RuleCommonITILObject.php +++ b/src/RuleCommonITILObject.php @@ -323,6 +323,18 @@ public function executeActions($output, $params, array $input = []) ) { $output['_groups_id_requester'] = $output['users_default_groups']; } + if ( + ( $action->fields['field'] == '_groups_id_observer') + && isset($output['users_default_groups']) + ) { + $output['_groups_id_observer'] = $output['users_default_groups']; + } + if ( + ( $action->fields['field'] == '_groups_id_assign') + && isset($output['users_default_groups']) + ) { + $output['_groups_id_assign'] = $output['users_default_groups']; + } break; case 'fromitem': @@ -332,6 +344,18 @@ public function executeActions($output, $params, array $input = []) ) { $output['_groups_id_requester'] = $output['_groups_id_of_item']; } + if ( + $action->fields['field'] == '_groups_id_observer' + && isset($output['_groups_id_of_item']) + ) { + $output['_groups_id_observer'] = $output['_groups_id_of_item']; + } + if ( + $action->fields['field'] == '_groups_id_assign' + && isset($output['_groups_id_of_item']) + ) { + $output['_groups_id_assign'] = $output['_groups_id_of_item']; + } break; case 'compute': @@ -797,7 +821,7 @@ public function getActions() $actions['_groups_id_assign']['name'] = __('Technician group'); $actions['_groups_id_assign']['type'] = 'dropdown'; $actions['_groups_id_assign']['condition'] = ['is_assign' => 1]; - $actions['_groups_id_assign']['force_actions'] = ['assign', 'append', 'regex_result']; + $actions['_groups_id_assign']['force_actions'] = ['assign', 'append', 'regex_result', 'fromitem', 'defaultfromuser','regex_result']; $actions['_groups_id_assign']['permitseveral'] = ['append']; $actions['_groups_id_assign']['appendto'] = '_additional_groups_assigns'; @@ -830,7 +854,7 @@ public function getActions() $actions['_groups_id_observer']['name'] = _n('Observer group', 'Observer groups', 1); $actions['_groups_id_observer']['type'] = 'dropdown'; $actions['_groups_id_observer']['condition'] = ['is_watcher' => 1]; - $actions['_groups_id_observer']['force_actions'] = ['assign', 'append', 'regex_result']; + $actions['_groups_id_observer']['force_actions'] = ['assign', 'append', 'regex_result', 'fromitem', 'defaultfromuser','regex_result']; $actions['_groups_id_observer']['permitseveral'] = ['append']; $actions['_groups_id_observer']['appendto'] = '_additional_groups_observers'; diff --git a/src/Ticket.php b/src/Ticket.php index 908bc6674ab..1d3bfd42d5b 100644 --- a/src/Ticket.php +++ b/src/Ticket.php @@ -4320,13 +4320,13 @@ public static function showCentralList($start, $status = "process", bool $showgr 'link' => 'AND', 'criteria' => [ [ - 'field' => 72, // end_date + 'field' => 75, // satisfaction survey end_date 'searchtype' => 'morethan', 'value' => 'NOW', 'link' => 'OR' ], [ - 'field' => 72, // end_date + 'field' => 75, // satisfaction survey end_date 'searchtype' => 'empty', 'value' => 'NULL', 'link' => 'OR' @@ -4657,6 +4657,11 @@ public static function showCentralCount(bool $foruser = false, bool $display = t $opt['criteria'][1]['criteria'][3]['link'] = 'OR'; $opt['criteria'][1]['link'] = 'AND'; + $opt['criteria'][2]['field'] = 12; // ticket status + $opt['criteria'][2]['searchtype'] = 'equals'; + $opt['criteria'][2]['value'] = Ticket::CLOSED; + $opt['criteria'][2]['link'] = 'AND NOT'; + $twig_params['items'][] = [ 'link' => self::getSearchURL() . "?" . Toolbox::append_params($opt), 'text' => __('Tickets waiting for your approval'), diff --git a/src/Transfer.php b/src/Transfer.php index e00e9b1eac6..ca4f5c939c6 100644 --- a/src/Transfer.php +++ b/src/Transfer.php @@ -3830,11 +3830,12 @@ public function showTransferList(): void foreach ($_SESSION['glpitransfer_list'] as $itemtype => $tab) { if (!empty($tab)) { $table = $itemtype::getTable(); - + $name_field = $itemtype::getNameField(); + $table_name_field = sprintf('%1$s.%2$s', $table, $name_field); $iterator = $DB->request([ 'SELECT' => [ "$table.id", - "$table.name", + $table_name_field, 'entities.completename AS entname', 'entities.id AS entID' ], @@ -3848,7 +3849,7 @@ public function showTransferList(): void ] ], 'WHERE' => ["$table.id" => $tab], - 'ORDERBY' => ['entname', "$table.name"] + 'ORDERBY' => ['entname', $table_name_field] ]); foreach ($iterator as $data) { diff --git a/src/User.php b/src/User.php index 9ae77a29470..c3b6e1e6b24 100644 --- a/src/User.php +++ b/src/User.php @@ -1158,9 +1158,28 @@ public function prepareInputForUpdate($input) count(array_intersect($protected_input_keys, array_keys($input))) > 0 && !$this->currentUserHaveMoreRightThan($input['id']) ) { + $ignored_fields = []; foreach ($protected_input_keys as $input_key) { + if ( + isset($input[$input_key]) + && !str_starts_with($input_key, '_') // virtual field + && $input[$input_key] != $this->getField($input_key) + ) { + $ignored_fields[] = $input_key; + } unset($input[$input_key]); } + if (!empty($ignored_fields)) { + Session::addMessageAfterRedirect( + sprintf( + __('You are not allowed to update the following fields: %s'), + implode(', ', $ignored_fields) + ), + false, + ERROR + ); + return false; + } } } @@ -2879,7 +2898,12 @@ public function showForm($ID, array $options = []) echo ""; $activerand = mt_rand(); echo ""; - Dropdown::showYesNo('is_active', $this->fields['is_active'], -1, ['rand' => $activerand]); + $params = ['rand' => $activerand]; + if (!$higherrights) { + $params['readonly'] = true; + $params['tooltip'] = __('Not enough rights to change this field'); + } + Dropdown::showYesNo('is_active', $this->fields['is_active'], -1, $params); echo ""; echo "" . _sn('Email', 'Emails', Session::getPluralNumber()); UserEmail::showAddEmailButton($this); diff --git a/src/UserEmail.php b/src/UserEmail.php index 929a1ed7bfe..1e927d3041d 100644 --- a/src/UserEmail.php +++ b/src/UserEmail.php @@ -255,7 +255,12 @@ public static function showForUser(User $user) ) { return; } - $canedit = ($user->can($users_id, UPDATE) || ($users_id == Session::getLoginUserID())); + $canedit = ( + ( + $user->can($users_id, UPDATE) + && ($user->currentUserHaveMoreRightThan($users_id))) + || ($users_id == Session::getLoginUserID()) + ); parent::showChildsForItemForm($user, '_useremails', $canedit); } @@ -271,7 +276,12 @@ public static function showAddEmailButton(User $user) if (!$user->can($users_id, READ) && ($users_id != Session::getLoginUserID())) { return false; } - $canedit = ($user->can($users_id, UPDATE) || ($users_id == Session::getLoginUserID())); + $canedit = ( + ( + $user->can($users_id, UPDATE) + && ($user->currentUserHaveMoreRightThan($users_id))) + || ($users_id == Session::getLoginUserID()) + ); parent::showAddChildButtonForItemForm($user, '_useremails', $canedit); diff --git a/templates/components/itilobject/timeline/form_followup.html.twig b/templates/components/itilobject/timeline/form_followup.html.twig index f9c78392dc5..d5c15f4218a 100644 --- a/templates/components/itilobject/timeline/form_followup.html.twig +++ b/templates/components/itilobject/timeline/form_followup.html.twig @@ -306,11 +306,13 @@ var newOption = new Option(data.requesttypes_name, requesttypes_id, true, true); $("#dropdown_requesttypes_id{{ rand }}").append(newOption).trigger('change'); - // set is_private - $("#is_private_{{ rand }}") - .prop("checked", data.is_private == "0" - ? false - : true); + if (data.is_private !== undefined) { + // set is_private + $("#is_private_{{ rand }}") + .prop("checked", data.is_private == "0" + ? false + : true); + } // set predefined pending reason $("#enable-pending-reasons-{{ rand }}") diff --git a/templates/dropdown_form.html.twig b/templates/dropdown_form.html.twig index 45c7ce2114b..f60561bd2c7 100644 --- a/templates/dropdown_form.html.twig +++ b/templates/dropdown_form.html.twig @@ -64,7 +64,7 @@ {# Dynamically show additional fields unique to certain dropdowns #} {% for field in additional_fields %} - {% set fields_params = base_fields_params|merge(field['params'] ?? []) %} + {% set fields_params = base_fields_params|merge(field['form_params'] ?? []) %} {% set type = field['type']|default('') %} {% set show_field = true %} {% if field['name'] == 'entities_id' and (type != 'parent' or item.fields['id'] == 0) %} @@ -206,7 +206,7 @@ {% set field_value %} {{ item.displaySpecificTypeField(item.fields['id'], field, fields_params) }} {% endset %} - {{ fields.field(field['name'], field_value, field['label']) }} + {{ fields.field(field['name'], field_value, field['label'], fields_params) }} {% endif %} {% elseif field['name'] in picture_fields %} {% set has_picture_field = true %} diff --git a/templates/generic_show_form.html.twig b/templates/generic_show_form.html.twig index e1ea3ff3306..9811d7eb3ec 100644 --- a/templates/generic_show_form.html.twig +++ b/templates/generic_show_form.html.twig @@ -385,7 +385,7 @@ specific_field_options ) }} {% elseif field is same as 'sysdescr' %} - {{ fields.textareaField('sysdescr', item.fields['sysdescr'], __('Sysdescr')) }} + {{ fields.textareaField('sysdescr', item.fields['sysdescr'], __('Sysdescr'), specific_field_options) }} {% elseif field is same as 'snmpcredentials_id' %} {{ fields.dropdownField( 'SNMPCredential',