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',
|