diff --git a/.github/label-commenter-config.yml b/.github/label-commenter-config.yml index 009edfab24d..84663454976 100644 --- a/.github/label-commenter-config.yml +++ b/.github/label-commenter-config.yml @@ -12,7 +12,7 @@ labels: This issue has been closed as we only track bugs here. You can get community support on [forums](https://forum.glpi-project.org/) or you can consider [taking a subscription](https://glpi-project.org/subscriptions/) to get professional support. - You can also [contact GLPI editor team](https://portal.glpi-network.com/contact-us) directly. + You can also [contact GLPI editor team](https://www.glpi-project.org/contact/) directly. action: close - name: "feature suggestion" labeled: @@ -21,7 +21,7 @@ labels: This issue has been closed as we only track bugs here. You can open a topic to discuss with community about this enhancement on [suggestion website](https://glpi.userecho.com/). - You can also [contact GLPI editor team](https://portal.glpi-network.com/contact-us) directly if you are willing to sponsor this feature. + You can also [contact GLPI editor team](https://www.glpi-project.org/contact/) directly if you are willing to sponsor this feature. action: close - name: "IA only" labeled: @@ -40,4 +40,4 @@ labels: Per [CONTRIBUTING.md](https://github.com/glpi-project/glpi/blob/main/CONTRIBUTING.md), changes other than trivial fixes (typos, broken links) must start with a bug report or feature proposal so we can confirm the change is wanted, belongs in core rather than a plugin, and isn't a duplicate effort. To proceed: open an issue, wait for confirmation, then submit the PR with the issue linked. - action: close \ No newline at end of file + action: close diff --git a/.github/workflows/commit-authorship.yml b/.github/workflows/commit-authorship.yml index 43fac67f24f..88ad7cfd414 100644 --- a/.github/workflows/commit-authorship.yml +++ b/.github/workflows/commit-authorship.yml @@ -3,9 +3,14 @@ name: "Commit authorship gate" # Block PRs whose commits are authored by AI agents or LLM provider accounts. # Per CONTRIBUTING.md, commits must be authored under a human's name and email. # -# Uses pull_request_target so the check fires even when the contributor's fork -# has Actions disabled or is subject to the first-time contributor approval gate. -# Safe because we only read commit metadata; PR code is never executed. +# Uses pull_request_target so the check fires even for first-time contributors +# whose PRs are otherwise gated behind a manual Actions approval step. +# Safe because we only call the GitHub REST API for commit metadata; no fork +# code is ever checked out or executed. +# +# Uses the GitHub REST API rather than git-clone to avoid the pathological case +# where `git log BASE..HEAD` traverses the entire fork history instead of only +# the PR commits, turning a 1-second check into a 15-minute scan. on: pull_request_target: @@ -13,37 +18,20 @@ on: permissions: contents: read + pull-requests: read jobs: check-authorship: name: "Verify commit authorship" runs-on: "ubuntu-latest" steps: - # Checkout points at the fork (head.repo) — pull_request_target defaults - # to the base repo, where the PR's head SHA does not exist. - - name: "Checkout PR (metadata only)" - uses: "actions/checkout@v6" - with: - fetch-depth: 0 - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - # The fork doesn't contain the base SHA; fetch it so the diff range resolves. - - name: "Fetch base ref for diff range" - env: - BASE_REPO: ${{ github.event.pull_request.base.repo.clone_url }} - BASE_SHA: ${{ github.event.pull_request.base.sha }} - run: | - git remote add base "$BASE_REPO" - git fetch --no-tags --depth=1 base "$BASE_SHA" - # DENY list is matched (regex, case-insensitive) against author name and email # of every commit in the PR. Extend conservatively as new agents appear. - name: "Scan commit authors" env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GH_TOKEN: ${{ github.token }} # gh CLI reads this; not injected automatically + REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -e DENY=( @@ -60,6 +48,7 @@ jobs: ) FAIL=0 while IFS=$'\t' read -r sha name email; do + echo "${sha:0:12} - $name <$email>" for pattern in "${DENY[@]}"; do if echo "$name" | grep -iqE "$pattern" || echo "$email" | grep -iqE "$pattern"; then echo "::error title=AI-authored commit::${sha:0:12} — '$name <$email>' matches '$pattern'" @@ -67,7 +56,8 @@ jobs: break fi done - done < <(git log --format='%H%x09%an%x09%ae' "${BASE_SHA}..${HEAD_SHA}") + done < <(gh api --paginate "/repos/$REPO/pulls/$PR_NUMBER/commits" \ + --jq '.[] | [.sha, .commit.author.name, .commit.author.email] | @tsv') if [ "$FAIL" -ne 0 ]; then echo "" echo "PRs must be authored by humans. See CONTRIBUTING.md." diff --git a/.phpstan-baseline.missingType.iterableValue.php b/.phpstan-baseline.missingType.iterableValue.php index bc9a2759bd5..d882fc73cde 100644 --- a/.phpstan-baseline.missingType.iterableValue.php +++ b/.phpstan-baseline.missingType.iterableValue.php @@ -14707,6 +14707,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Form/Destination/CommonITILField/LocationFieldConfig.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Glpi\\\\Form\\\\Destination\\\\CommonITILField\\\\OLAField\\:\\:applyConfiguratedValueToInputUsingAnswers\\(\\) has parameter \\$input with no value type specified in iterable type array\\.$#', + 'identifier' => 'missingType.iterableValue', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Form/Destination/CommonITILField/OLAField.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Glpi\\\\Form\\\\Destination\\\\CommonITILField\\\\OLAField\\:\\:applyConfiguratedValueToInputUsingAnswers\\(\\) return type has no value type specified in iterable type array\\.$#', + 'identifier' => 'missingType.iterableValue', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Form/Destination/CommonITILField/OLAField.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Glpi\\\\Form\\\\Destination\\\\CommonITILField\\\\OLATTOFieldConfig\\:\\:jsonDeserialize\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -15013,12 +15025,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Form\\\\Destination\\\\CommonITILField\\\\UrgencyField\\:\\:getUrgencyLevels\\(\\) return type has no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Glpi\\\\Form\\\\Destination\\\\CommonITILField\\\\UrgencyField\\:\\:getUrgencyQuestionsValuesForDropdown\\(\\) return type has no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -16669,12 +16675,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Form/QuestionType/QuestionTypeUrgency.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Form\\\\QuestionType\\\\QuestionTypeUrgency\\:\\:getUrgencyLevels\\(\\) return type has no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Form/QuestionType/QuestionTypeUrgency.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Glpi\\\\Form\\\\QuestionType\\\\QuestionTypeUserDevice\\:\\:validateExtraDataInput\\(\\) has parameter \\$input with no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -18475,12 +18475,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/UI/ThemeManager.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Urgency\\:\\:getUrgencyValuesForDropdown\\(\\) return type has no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Urgency.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Group\\:\\:cleanCloneInput\\(\\) has parameter \\$input with no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -20419,6 +20413,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Line.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Item_Ola\\:\\:prepareInputForAdd\\(\\) return type has no value type specified in iterable type array\\.$#', + 'identifier' => 'missingType.iterableValue', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_Ola.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Item_OperatingSystem\\:\\:getRelationMassiveActionsSpecificities\\(\\) return type has no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -20821,18 +20821,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/KnowbaseItem_User.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method LevelAgreement\\:\\:getDataForTicket\\(\\) return type has no value type specified in iterable type iterable\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/LevelAgreement.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method LevelAgreement\\:\\:getFieldNames\\(\\) return type has no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/LevelAgreement.php', -]; $ignoreErrors[] = [ 'message' => '#^Method LevelAgreement\\:\\:getTypeDropdown\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -23221,12 +23209,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/OlaLevel_Ticket.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method OlaLevel_Ticket\\:\\:doLevelForTicket\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/OlaLevel_Ticket.php', -]; $ignoreErrors[] = [ 'message' => '#^Method OperatingSystemKernelVersion\\:\\:displaySpecificTypeField\\(\\) has parameter \\$field with no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -29551,12 +29533,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Ticket.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Ticket\\:\\:getDatasToAddOLA\\(\\) return type has no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/Ticket.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Ticket\\:\\:getDatasToAddSLA\\(\\) return type has no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', @@ -29665,18 +29641,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Ticket.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Ticket\\:\\:olaAffect\\(\\) has parameter \\$input with no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/Ticket.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Ticket\\:\\:olaAffect\\(\\) has parameter \\$manual_olas_id with no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/src/Ticket.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Ticket\\:\\:processMassiveActionsForOneItemtype\\(\\) has parameter \\$ids with no value type specified in iterable type array\\.$#', 'identifier' => 'missingType.iterableValue', diff --git a/.phpstan-baseline.php b/.phpstan-baseline.php index 72b4b667c5c..e043aa029ee 100644 --- a/.phpstan-baseline.php +++ b/.phpstan-baseline.php @@ -199,6 +199,12 @@ 'count' => 1, 'path' => __DIR__ . '/ajax/impact.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/ajax/itilfollowup.php', +]; $ignoreErrors[] = [ 'message' => '#^Part \\$parents_itemtype \\(class\\-string\\\\|CommonITILObject\\) of encapsed string cannot be cast to string\\.$#', 'identifier' => 'encapsedStringPart.nonString', @@ -379,12 +385,30 @@ 'count' => 1, 'path' => __DIR__ . '/ajax/ruleaction.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$sub_type of method RuleAction\\:\\:getAlreadyUsedForRuleID\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/ajax/ruleaction.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/ajax/solution.php', +]; $ignoreErrors[] = [ 'message' => '#^Part \\$parents_itemtype \\(class\\-string\\\\|CommonITILObject\\) of encapsed string cannot be cast to string\\.$#', 'identifier' => 'encapsedStringPart.nonString', 'count' => 1, 'path' => __DIR__ . '/ajax/solution.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/ajax/task.php', +]; $ignoreErrors[] = [ 'message' => '#^Part \\$parents_itemtype \\(class\\-string\\\\|CommonITILObject\\) of encapsed string cannot be cast to string\\.$#', 'identifier' => 'encapsedStringPart.nonString', @@ -739,6 +763,30 @@ 'count' => 1, 'path' => __DIR__ . '/front/item_operatingsystem.form.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call static method getFormURLWithID\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'staticMethod.nonObject', + 'count' => 3, + 'path' => __DIR__ . '/front/item_plug.form.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Offset \'items_id\' might not exist on array\\\\|null\\.$#', + 'identifier' => 'offsetAccess.notFound', + 'count' => 1, + 'path' => __DIR__ . '/front/item_plug.form.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Offset \'itemtype\' might not exist on array\\\\|null\\.$#', + 'identifier' => 'offsetAccess.notFound', + 'count' => 1, + 'path' => __DIR__ . '/front/item_plug.form.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$input of method CommonDBTM\\:\\:add\\(\\) expects array\\, array\\\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/front/item_plug.form.php', +]; $ignoreErrors[] = [ 'message' => '#^Offset \'projects_id\' might not exist on array\\\\|null\\.$#', 'identifier' => 'offsetAccess.notFound', @@ -829,6 +877,24 @@ 'count' => 1, 'path' => __DIR__ . '/front/itil_project.form.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$ID of method KnowbaseItem\\:\\:showForm\\(\\) expects int, array\\|float\\|int\\|string\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/front/knowbaseitem.form.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$id of method CommonGLPI\\:\\:getFormURLWithID\\(\\) expects int, int\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/front/knowbaseitem.form.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$items_id of static method Glpi\\\\Event\\:\\:log\\(\\) expects int\\|string, int\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/front/knowbaseitem.form.php', +]; $ignoreErrors[] = [ 'message' => '#^Offset \'knowbaseitems_id\' might not exist on array\\\\|null\\.$#', 'identifier' => 'offsetAccess.notFound', @@ -1279,6 +1345,18 @@ 'count' => 1, 'path' => __DIR__ . '/install/migrations/update_9.5.x_to_10.0.0/domains.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method APIClient\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/APIClient.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method APIClient\\:\\:canPurge\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/APIClient.php', +]; $ignoreErrors[] = [ 'message' => '#^Offset \'dolog_method\' might not exist on array\\\\|string\\.$#', 'identifier' => 'offsetAccess.notFound', @@ -1357,6 +1435,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Appliance.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Appliance.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -1387,6 +1471,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Appliance_Item.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Appliance_Item.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Appliance_Item.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -1669,12 +1765,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Blacklist.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Blacklist.php', +]; $ignoreErrors[] = [ 'message' => '#^Method BlacklistedMailContent\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/BlacklistedMailContent.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/BlacklistedMailContent.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -1693,12 +1801,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Budget.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Budget.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of function getItemForItemtype expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Budget.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Budget.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Cable\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\|false\\.$#', 'identifier' => 'return.type', @@ -1711,6 +1831,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Cable.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Cable.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -1729,6 +1855,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/CableStrand.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CableStrand.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset int on array\\|bool\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -1753,12 +1885,36 @@ 'count' => 1, 'path' => __DIR__ . '/src/Calendar.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Calendar.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CalendarSegment.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Calendar_Holiday.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Cartridge\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/Cartridge.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Cartridge.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CartridgeItem\\:\\:dropdownForPrinter\\(\\) should return bool\\|string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -1771,12 +1927,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/CartridgeItem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CartridgeItem.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/CartridgeItem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CartridgeItem_PrinterModel.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$ID of method CommonDBTM\\:\\:getFromDB\\(\\) expects int\\|string, int\\|string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -1789,18 +1957,36 @@ 'count' => 1, 'path' => __DIR__ . '/src/Certificate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Certificate.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Certificate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Certificate_Item.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$itemtype of method Certificate_Item\\:\\:getFromDBbyCertificatesAndItem\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Certificate_Item.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Change\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Change.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Change\\:\\:displayTabContentForItem\\(\\) should return bool but returns false\\|null\\.$#', 'identifier' => 'return.type', @@ -1819,6 +2005,24 @@ 'count' => 4, 'path' => __DIR__ . '/src/Change.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Change.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method ChangeCost\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ChangeCost.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method ChangeCost\\:\\:canUpdate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ChangeCost.php', +]; $ignoreErrors[] = [ 'message' => '#^Method ChangeTask\\:\\:displayPlanningItem\\(\\) should return string but returns string\\|false\\.$#', 'identifier' => 'return.type', @@ -1837,6 +2041,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ChangeTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ChangeTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$max of static method CleanSoftwareCron\\:\\:deleteItems\\(\\) expects int, int\\|null given\\.$#', 'identifier' => 'argument.type', @@ -1849,6 +2059,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Cluster.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Cluster.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -1861,6 +2077,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonDBChild.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 4, + 'path' => __DIR__ . '/src/CommonDBChild.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CommonDBConnexity\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', @@ -1874,10 +2096,10 @@ 'path' => __DIR__ . '/src/CommonDBConnexity.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 5, - 'path' => __DIR__ . '/src/CommonDBRelation.php', + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDBConnexity.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getID\\(\\) on CommonDBTM\\|false\\.$#', @@ -1897,6 +2119,12 @@ 'count' => 6, 'path' => __DIR__ . '/src/CommonDBRelation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 9, + 'path' => __DIR__ . '/src/CommonDBRelation.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method isEntityAssign\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -2014,7 +2242,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$item2 of method CommonDBRelation\\:\\:getFromDBForItems\\(\\) expects CommonDBTM, CommonDBTM\\|false given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/CommonDBRelation.php', ]; $ignoreErrors[] = [ @@ -2035,6 +2263,12 @@ 'count' => 3, 'path' => __DIR__ . '/src/CommonDBRelation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 8, + 'path' => __DIR__ . '/src/CommonDBRelation.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$subject of function Safe\\\\preg_match expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -2116,7 +2350,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method getField\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/CommonDBTM.php', ]; $ignoreErrors[] = [ @@ -2132,15 +2366,21 @@ 'path' => __DIR__ . '/src/CommonDBTM.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method isDeleted\\(\\) on CommonDBTM\\|false\\.$#', + 'message' => '#^Cannot call method getTable\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/CommonDBTM.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method isField\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 3, + 'path' => __DIR__ . '/src/CommonDBTM.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method update\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/CommonDBTM.php', ]; $ignoreErrors[] = [ @@ -2174,9 +2414,15 @@ 'path' => __DIR__ . '/src/CommonDBTM.php', ]; $ignoreErrors[] = [ - 'message' => '#^PHPDoc tag @var with type class\\-string\\ is not subtype of native type class\\-string\\\\.$#', - 'identifier' => 'varTag.nativeType', - 'count' => 2, + 'message' => '#^Parameter \\#1 \\$ID of function getUserName expects int, float\\|int\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDBTM.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$ID of function getUserName expects int, int\\|string\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, 'path' => __DIR__ . '/src/CommonDBTM.php', ]; $ignoreErrors[] = [ @@ -2197,6 +2443,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonDBTM.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of method Lockedfield\\:\\:getLockedValues\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDBTM.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of method Lockedfield\\:\\:setLastValue\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDBTM.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Glpi\\\\Search\\\\SearchOption\\:\\:getOptionsForItemtype\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDBTM.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -2239,6 +2503,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonDCModelDropdown.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDCModelDropdown.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$path of static method Html\\:\\:image\\(\\) expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -2312,9 +2582,15 @@ 'path' => __DIR__ . '/src/CommonDeviceModel.php', ]; $ignoreErrors[] = [ - 'message' => '#^Argument of an invalid type list\\\\|string\\>\\|string supplied for foreach, only iterables are supported\\.$#', - 'identifier' => 'foreach.nonIterable', - 'count' => 1, + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonDeviceModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Argument of an invalid type list\\\\|string\\>\\|string supplied for foreach, only iterables are supported\\.$#', + 'identifier' => 'foreach.nonIterable', + 'count' => 1, 'path' => __DIR__ . '/src/CommonDropdown.php', ]; $ignoreErrors[] = [ @@ -2348,14 +2624,44 @@ 'path' => __DIR__ . '/src/CommonGLPI.php', ]; $ignoreErrors[] = [ - 'message' => '#^Call to function method_exists\\(\\) with class\\-string\\ and \'canView\' will always evaluate to true\\.$#', - 'identifier' => 'function.alreadyNarrowedType', + 'message' => '#^Cannot access offset \'path\' on array\\|int\\|string\\|null\\.$#', + 'identifier' => 'offsetAccess.nonOffsetAccessible', 'count' => 1, 'path' => __DIR__ . '/src/CommonGLPI.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access offset \'path\' on array\\|int\\|string\\|null\\.$#', - 'identifier' => 'offsetAccess.nonOffsetAccessible', + 'message' => '#^Method CommonGLPI\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonGLPI.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonGLPI\\:\\:canDelete\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonGLPI.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonGLPI\\:\\:canPurge\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonGLPI.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonGLPI\\:\\:canUpdate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonGLPI.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonGLPI\\:\\:canView\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonGLPI.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$typeform of static method CommonGLPI\\:\\:getOtherTabs\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/CommonGLPI.php', ]; @@ -2462,10 +2768,10 @@ 'path' => __DIR__ . '/src/CommonITILCost.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonITILTask\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/CommonITILObject.php', + 'path' => __DIR__ . '/src/CommonITILCost.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot access constant class on CommonITILValidation\\|null\\.$#', @@ -2557,6 +2863,30 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILObject.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call static method getType\\(\\) on CommonITILTask\\|false\\.$#', + 'identifier' => 'staticMethod.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonITILObject\\:\\:canAdminActors\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonITILObject\\:\\:canAssign\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonITILObject\\:\\:canAssignToMe\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CommonITILObject\\:\\:computeCloseDelayStat\\(\\) should return int but returns float\\|int\\<0, max\\>\\.$#', 'identifier' => 'return.type', @@ -2641,12 +2971,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILObject.php', ]; -$ignoreErrors[] = [ - 'message' => '#^PHPDoc tag @var with type CommonITILObject is not subtype of native type class\\-string\\\\.$#', - 'identifier' => 'varTag.nativeType', - 'count' => 1, - 'path' => __DIR__ . '/src/CommonITILObject.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$haystack of function str_contains expects string, bool\\|string given\\.$#', 'identifier' => 'argument.type', @@ -2698,7 +3022,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/CommonITILObject.php', ]; $ignoreErrors[] = [ @@ -2719,6 +3043,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILObject.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method CommonITILObject\\:\\:getKanbanPluginFilters\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method CommonITILObject_CommonITILObject\\:\\:countLinksByStatus\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -2773,6 +3109,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILObject.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$with_private of method CommonITILObject\\:\\:numberOfFollowups\\(\\) expects bool, bool\\|int given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$with_private of method CommonITILObject\\:\\:numberOfTasks\\(\\) expects bool, bool\\|int given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -2803,6 +3151,12 @@ 'count' => 11, 'path' => __DIR__ . '/src/CommonITILObject.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILObject.php', +]; $ignoreErrors[] = [ 'message' => '#^Property CommonDBTM\\:\\:\\$updates \\(list\\\\) does not accept array\\, string\\>\\.$#', 'identifier' => 'assign.propertyType', @@ -2881,6 +3235,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILRecurrent.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILRecurrent.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$delay of method Calendar\\:\\:computeEndDate\\(\\) expects int, int\\\\|string given\\.$#', 'identifier' => 'argument.type', @@ -2905,6 +3265,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/CommonITILRecurrent.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonITILSatisfaction\\:\\:canUpdate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILSatisfaction.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CommonITILSatisfaction\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -3001,6 +3367,12 @@ 'count' => 3, 'path' => __DIR__ . '/src/CommonITILTask.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method CommonITILTask\\:\\:canPurgeItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILTask.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CommonITILTask\\:\\:genericPopulateNotPlanned\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', @@ -3073,6 +3445,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILTask.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILTask.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$users_id of method PlanningRecall\\:\\:getFromDBForItemAndUser\\(\\) expects int, int\\|string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -3193,6 +3571,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonITILValidation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonITILValidation.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$users_id of static method CommonITILObject\\:\\:getTimelinePosition\\(\\) expects int, int\\|string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -3355,18 +3739,42 @@ 'count' => 1, 'path' => __DIR__ . '/src/CommonItilObject_Item.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/CommonTreeDropdown.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Dropdown\\:\\:show\\(\\) expects string, bool\\|string given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/CommonTreeDropdown.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 5, + 'path' => __DIR__ . '/src/CommonTreeDropdown.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonTreeDropdown.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CommonType\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/CommonType.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/CommonType.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Agent\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -3374,25 +3782,25 @@ 'path' => __DIR__ . '/src/Computer.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', + 'message' => '#^Cannot call method getField\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Computer.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getID\\(\\) on CommonDBTM\\|false\\.$#', + 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Computer.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getLink\\(\\) on Agent\\|null\\.$#', + 'message' => '#^Cannot call method getID\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Computer.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method isGlobal\\(\\) on CommonDBTM\\|false\\.$#', + 'message' => '#^Cannot call method getLink\\(\\) on Agent\\|null\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Computer.php', @@ -3409,6 +3817,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Computer.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Computer.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -3469,6 +3883,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Config.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 10, + 'path' => __DIR__ . '/src/Config.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -3517,12 +3937,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Consumable.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Consumable.php', +]; $ignoreErrors[] = [ 'message' => '#^Method ConsumableItem\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/ConsumableItem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ConsumableItem.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -3541,6 +3973,18 @@ 'count' => 2, 'path' => __DIR__ . '/src/Contact.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Contact.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Contact_Supplier.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Contract\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -3559,6 +4003,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Contract.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Contract.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'contracts_id\' on array\\\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -3625,6 +4075,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Contract_User.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Binary operation "\\+" between int and 604800\\|string results in an error\\.$#', + 'identifier' => 'binaryOp.invalid', + 'count' => 1, + 'path' => __DIR__ . '/src/CronTask.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'plugin\' on non\\-empty\\-array\\|true\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -3634,7 +4090,7 @@ $ignoreErrors[] = [ 'message' => '#^Method CronTask\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/CronTask.php', ]; $ignoreErrors[] = [ @@ -3655,6 +4111,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/CronTask.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/CronTaskLog.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$dbhost on DBmysql\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -3793,6 +4255,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/DCRoom.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/DCRoom.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Agent\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -3811,12 +4279,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/DatabaseInstance.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/DatabaseInstance.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/DatabaseInstance.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/DatabaseInstance.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$input of method Datacenter\\:\\:managePictures\\(\\) expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -3841,6 +4321,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/DbUtils.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot assign offset \'COUNT\' to array\\\\|string\\.$#', + 'identifier' => 'offsetAssign.dimType', + 'count' => 2, + 'path' => __DIR__ . '/src/DbUtils.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot assign offset \'ORDER\' to array\\\\|string\\.$#', + 'identifier' => 'offsetAssign.dimType', + 'count' => 1, + 'path' => __DIR__ . '/src/DbUtils.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method maybeRecursive\\(\\) on CommonDBTM\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -3871,6 +4363,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/DbUtils.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Session\\:\\:haveTranslations\\(\\) expects string, class\\-string\\\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/DbUtils.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -3892,7 +4390,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$string of function strlen expects string, int\\|string given\\.$#', 'identifier' => 'argument.type', - 'count' => 3, + 'count' => 4, 'path' => __DIR__ . '/src/DbUtils.php', ]; $ignoreErrors[] = [ @@ -3910,7 +4408,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\.\\.\\.\\$arrays of function array_merge expects array, array\\\\|string given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/DbUtils.php', ]; $ignoreErrors[] = [ @@ -3919,12 +4417,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/DbUtils.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, array\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/DbUtils.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$offset of function substr_replace expects array\\|int, int\\|false given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/DbUtils.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Glpi\\\\Search\\\\SearchOption\\:\\:getOptionsForItemtype\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/DefaultFilter.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -3950,8 +4460,8 @@ 'path' => __DIR__ . '/src/DeviceCamera.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', - 'identifier' => 'classConstant.nonObject', + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|null\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/DeviceCase.php', ]; @@ -4033,12 +4543,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/DeviceGraphicCard.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/DeviceHardDrive.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -4052,10 +4556,10 @@ 'path' => __DIR__ . '/src/DeviceHardDrive.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', - 'identifier' => 'classConstant.nonObject', + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|null\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, - 'path' => __DIR__ . '/src/DeviceMemory.php', + 'path' => __DIR__ . '/src/DeviceHardDrive.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', @@ -4069,6 +4573,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/DeviceMemory.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|null\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/DeviceMemory.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', 'identifier' => 'classConstant.nonObject', @@ -4094,19 +4604,19 @@ 'path' => __DIR__ . '/src/DeviceNetworkCard.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', - 'identifier' => 'classConstant.nonObject', + 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/DevicePowerSupply.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', + 'message' => '#^Cannot call method getHeaderByName\\(\\) on HTMLTableRow\\|null\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/DevicePowerSupply.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getHeaderByName\\(\\) on HTMLTableRow\\|null\\.$#', + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|null\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/DevicePowerSupply.php', @@ -4315,6 +4825,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Domain.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Domain.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -4333,6 +4849,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/DomainRecordType.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Domain_Item.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'count\' on array\\|string\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -4393,6 +4915,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Dropdown.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Session\\:\\:haveTranslations\\(\\) expects string, class\\-string\\\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Dropdown.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -4447,6 +4975,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Enclosure.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Enclosure.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -4525,6 +5059,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Entity.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Entity.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$replace of function str_replace expects array\\\\|string, int\\|string given\\.$#', 'identifier' => 'argument.type', @@ -4753,12 +5293,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Api/API.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 4, - 'path' => __DIR__ . '/src/Glpi/Api/API.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'endpoint\' on array\\{api_version\\: string, version\\: string, description\\?\\: string, endpoint\\: string\\}\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -4813,6 +5347,12 @@ 'count' => 3, 'path' => __DIR__ . '/src/Glpi/Api/API.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 5, + 'path' => __DIR__ . '/src/Glpi/Api/API.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method isDynamic\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -5011,16 +5551,10 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Api/APIRest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Api\\\\APIRest\\:\\:getItemtype\\(\\) should return class\\-string\\\\|false but returns \'AllAssets\'\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/APIRest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Glpi\\\\Api\\\\APIRest\\:\\:getItemtype\\(\\) should return class\\-string\\\\|false but returns string\\.$#', 'identifier' => 'return.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Api/APIRest.php', ]; $ignoreErrors[] = [ @@ -5203,24 +5737,6 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Api/Deprecated/Computer_SoftwareVersion.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Api\\\\Deprecated\\\\Item_Plug\\:\\:mapCurrentToDeprecatedFields\\(\\) should return array but returns array\\|object\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/Deprecated/Item_Plug.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Api\\\\Deprecated\\\\Item_Plug\\:\\:mapDeprecatedToCurrentFields\\(\\) should return object but returns array\\|object\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/Deprecated/Item_Plug.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', - 'identifier' => 'argument.type', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Api/Deprecated/Item_Plug.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', @@ -5287,18 +5803,6 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/AdministrationController.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#2 \\$headers of class Glpi\\\\Http\\\\Response constructor expects array\\\\|string\\>, array\\\\> given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/AdministrationController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#3 \\$body of class Glpi\\\\Http\\\\Response constructor expects Psr\\\\Http\\\\Message\\\\StreamInterface\\|resource\\|string\\|null, string\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/AdministrationController.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\$itemtypes of static method Glpi\\\\Api\\\\HL\\\\Doc\\\\Schema\\:\\:getUnionSchemaForItemtypes\\(\\) expects non\\-empty\\-array\\\\>, array given\\.$#', 'identifier' => 'argument.type', @@ -5323,18 +5827,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/ITILController.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#2 \\$headers of class Glpi\\\\Http\\\\Response constructor expects array\\\\|string\\>, array\\\\> given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/ManagementController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#3 \\$body of class Glpi\\\\Http\\\\Response constructor expects Psr\\\\Http\\\\Message\\\\StreamInterface\\|resource\\|string\\|null, string\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/ManagementController.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset mixed on array\\|void\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -5767,6 +6259,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Application/ImportMapGenerator.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Glpi\\\\Application\\\\ImportMapGenerator\\:\\:generateVersionParam\\(\\) should return string but returns string\\|false\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Application/ImportMapGenerator.php', +]; $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type array\\|false supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', @@ -5923,6 +6421,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Application/View/Extension/SearchExtension.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$id of static method CommonDBTM\\:\\:getById\\(\\) expects int\\|null, int\\|string\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Application/View/Extension/SessionExtension.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$path of method Twig\\\\Loader\\\\FilesystemLoader\\:\\:addPath\\(\\) expects string, string\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Application/View/TemplateRenderer.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset non\\-falsy\\-string on array\\\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -5965,6 +6475,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Asset/Asset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Asset/Asset.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -6445,6 +6961,42 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Console/Database/UpdateCommand.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|false\\.$#', + 'identifier' => 'property.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Diagnostic/CheckHtmlEncodingCommand.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 2, + 'path' => __DIR__ . '/src/Glpi/Console/Diagnostic/CheckHtmlEncodingCommand.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call static method getTable\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'staticMethod.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Diagnostic/CheckHtmlEncodingCommand.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call static method getTypeName\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'staticMethod.nonObject', + 'count' => 3, + 'path' => __DIR__ . '/src/Glpi/Console/Diagnostic/CheckHtmlEncodingCommand.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$item of method Glpi\\\\Console\\\\Diagnostic\\\\CheckHtmlEncodingCommand\\:\\:fixOneItem\\(\\) expects CommonDBTM, CommonDBTM\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Diagnostic/CheckHtmlEncodingCommand.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Diagnostic/CheckHtmlEncodingCommand.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access an offset on float\\|int\\|list\\\\|string\\|false\\|null\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -6691,12 +7243,30 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Console/Migration/RacksPluginToCoreCommand.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Glpi\\\\Console\\\\Migration\\\\RacksPluginToCoreCommand\\:\\:getFallbackRoomId\\(\\) should return int but returns int\\|true\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Migration/RacksPluginToCoreCommand.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#4 \\$new_id of method Glpi\\\\Console\\\\Migration\\\\RacksPluginToCoreCommand\\:\\:addElementToMapping\\(\\) expects int, int\\\\|int\\<1, max\\>\\|true given\\.$#', 'identifier' => 'argument.type', 'count' => 6, 'path' => __DIR__ . '/src/Glpi/Console/Migration/RacksPluginToCoreCommand.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Property Glpi\\\\Console\\\\Migration\\\\RacksPluginToCoreCommand\\:\\:\\$datacenter_id \\(int\\) does not accept int\\|null\\.$#', + 'identifier' => 'assign.propertyType', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Migration/RacksPluginToCoreCommand.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Property Glpi\\\\Console\\\\Migration\\\\RacksPluginToCoreCommand\\:\\:\\$fallback_room_id \\(int\\) does not accept int\\|true\\.$#', + 'identifier' => 'assign.propertyType', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Console/Migration/RacksPluginToCoreCommand.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$dbdefault on DBmysql\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -6938,31 +7508,13 @@ 'path' => __DIR__ . '/src/Glpi/ContentTemplates/Parameters/UserParameters.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/AbstractDocumentUploadController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/AbstractDocumentUploadController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/AbstractDocumentUploadController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$dashboard_key of class Glpi\\\\Dashboard\\\\Grid constructor expects string, string\\|null given\\.$#', + 'message' => '#^Parameter \\#1 \\$dashboard_key of class Glpi\\\\Dashboard\\\\Grid constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Controller/CentralController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$where of static method Toolbox\\:\\:computeRedirect\\(\\) expects string, string\\|null given\\.$#', + 'message' => '#^Parameter \\#1 \\$where of static method Toolbox\\:\\:computeRedirect\\(\\) expects string, bool\\|float\\|int\\|string\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Controller/CentralController.php', @@ -7009,6 +7561,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Controller/Form/Condition/EditorController.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$ID of method CommonDBTM\\:\\:getFromDB\\(\\) expects int\\|string, float\\|int\\\\|int\\<1, max\\>\\|string\\|true given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Controller/Form/DelegationController.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Glpi\\\\Form\\\\Destination\\\\FormDestination\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -7202,460 +7760,148 @@ 'path' => __DIR__ . '/src/Glpi/Controller/ItemType/Form/SavedSearchFormController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/AddCommentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/AddCommentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\$status of class Symfony\\\\Component\\\\HttpFoundation\\\\Response constructor expects int, bool\\|int given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/AddCommentController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/LegacyFileLoadController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$code of method Glpi\\\\Security\\\\TOTPManager\\:\\:verifyBackupCodeForUser\\(\\) expects string, bool\\|float\\|int\\|string given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/DeleteArticleController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$url of class Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse constructor expects string, string\\|false given\\.$#', + 'identifier' => 'argument.type', 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/DeleteArticleController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#2 \\$secret of method Glpi\\\\Security\\\\TOTPManager\\:\\:verifyCodeForSecret\\(\\) expects string, bool\\|float\\|int\\|string given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/DeleteArticleController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#3 \\$algorithm of method Glpi\\\\Security\\\\TOTPManager\\:\\:setSecretForUser\\(\\) expects string\\|null, string\\|false\\|null given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/DeleteTranslationController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/DeleteTranslationController.php', + 'message' => '#^Property Glpi\\\\Controller\\\\ServiceCatalog\\\\IndexController\\:\\:\\$interface \\(string\\) does not accept string\\|false\\.$#', + 'identifier' => 'assign.propertyType', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Controller/ServiceCatalog/IndexController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Cannot call method getCurrentEntityId\\(\\) on Glpi\\\\Session\\\\SessionInfo\\|null\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/DeleteTranslationController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/ServiceCatalog/ItemsController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$id of static method CommonDBTM\\:\\:getById\\(\\) expects int\\|null, float\\|int\\<1, max\\>\\|string given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/GetTranslationContentController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/ServiceCatalog/ItemsController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/GetTranslationContentController.php', + 'message' => '#^Parameter \\$category_id of class Glpi\\\\Form\\\\ServiceCatalog\\\\ItemRequest constructor expects int\\|null, float\\|int\\|string\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Controller/ServiceCatalog/ItemsController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$url of class Glpi\\\\Http\\\\RedirectResponse constructor expects string, string\\|false given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/GetTranslationContentController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/Session/ChangeEntityController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$haystack of function str_contains expects string, string\\|false given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/KnowbaseFormController.php', + 'path' => __DIR__ . '/src/Glpi/Controller/Session/ChangeProfileController.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/KnowbaseFormController.php', + 'message' => '#^Parameter \\#1 \\$item of static method ImpactContext\\:\\:findForImpactItem\\(\\) expects ImpactItem, bool\\|ImpactItem given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Csv/ImpactCsvExport.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Cannot call method getForeignKeyField\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/KnowbaseFormController.php', + 'path' => __DIR__ . '/src/Glpi/Csv/PlanningCsv.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Cannot call method getTypeName\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/KnowbaseItemController.php', + 'path' => __DIR__ . '/src/Glpi/Csv/PlanningCsv.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#5 \\$end_date of static method PrinterLog\\:\\:getMetrics\\(\\) expects Safe\\\\DateTime, Safe\\\\DateTime\\|null given\\.$#', + 'identifier' => 'argument.type', 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/KnowbaseItemController.php', + 'path' => __DIR__ . '/src/Glpi/Csv/PrinterLogCsvExport.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#5 \\$end_date of static method PrinterLog\\:\\:getMetrics\\(\\) expects Safe\\\\DateTime, Safe\\\\DateTime\\|null given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/KnowbaseItemController.php', + 'path' => __DIR__ . '/src/Glpi/Csv/PrinterLogCsvExportComparison.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Cannot access property \\$categories on Gettext\\\\Languages\\\\Language\\|null\\.$#', + 'identifier' => 'property.nonObject', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/LinkDocumentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/LinkDocumentController.php', + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Cannot access property \\$formula on Gettext\\\\Languages\\\\Language\\|null\\.$#', + 'identifier' => 'property.nonObject', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/LinkDocumentController.php', + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Method Glpi\\\\CustomObject\\\\AbstractDefinition\\:\\:getCustomObjectClassName\\(\\) should return class\\-string\\ but returns string\\.$#', + 'identifier' => 'return.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/LinkItemController.php', + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/LinkItemController.php', + 'message' => '#^Method Glpi\\\\CustomObject\\\\AbstractDefinition\\:\\:prepareInputForAdd\\(\\) should return array\\\\|false but returns array\\|bool\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Method Glpi\\\\CustomObject\\\\AbstractDefinition\\:\\:prepareInputForUpdate\\(\\) should return array\\\\|false but returns array\\|bool\\.$#', + 'identifier' => 'return.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/LinkItemController.php', + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$id of static method Gettext\\\\Languages\\\\Language\\:\\:getById\\(\\) expects string, string\\|null given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/PurgeCommentController.php', + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/PurgeCommentController.php', + 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', ]; $ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', + 'message' => '#^Parameter \\#1 \\$json of function Safe\\\\json_decode expects string, string\\|null given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/PurgeCommentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/SaveTranslationController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/SaveTranslationController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/SaveTranslationController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/ToggleFavoriteController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/ToggleFavoriteController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/ToggleFavoriteController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/ToggleFieldController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/ToggleFieldController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/ToggleFieldController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UnlinkDocumentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UnlinkDocumentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UnlinkDocumentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UnlinkItemController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UnlinkItemController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UnlinkItemController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateCommentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateCommentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateCommentController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateServiceCatalogController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateServiceCatalogController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateServiceCatalogController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateVisibilityDatesController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateVisibilityDatesController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UpdateVisibilityDatesController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UploadInlineImageController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between array\\{id\\: int\\} and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UploadInlineImageController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Strict comparison using \\=\\=\\= between non\\-empty\\-array\\ and null will always evaluate to false\\.$#', - 'identifier' => 'identical.alwaysFalse', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Knowbase/UploadInlineImageController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\$status of class Symfony\\\\Component\\\\HttpFoundation\\\\Response constructor expects int, bool\\|int given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/LegacyFileLoadController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$code of method Glpi\\\\Security\\\\TOTPManager\\:\\:verifyBackupCodeForUser\\(\\) expects string, bool\\|float\\|int\\|string given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$url of class Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse constructor expects string, string\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#2 \\$secret of method Glpi\\\\Security\\\\TOTPManager\\:\\:verifyCodeForSecret\\(\\) expects string, bool\\|float\\|int\\|string given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#3 \\$algorithm of method Glpi\\\\Security\\\\TOTPManager\\:\\:setSecretForUser\\(\\) expects string\\|null, string\\|false\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Security/MFAController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Property Glpi\\\\Controller\\\\ServiceCatalog\\\\IndexController\\:\\:\\$interface \\(string\\) does not accept string\\|false\\.$#', - 'identifier' => 'assign.propertyType', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/ServiceCatalog/IndexController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getCurrentEntityId\\(\\) on Glpi\\\\Session\\\\SessionInfo\\|null\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/ServiceCatalog/ItemsController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$url of class Glpi\\\\Http\\\\RedirectResponse constructor expects string, string\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Session/ChangeEntityController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$haystack of function str_contains expects string, string\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Controller/Session/ChangeProfileController.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$item of static method ImpactContext\\:\\:findForImpactItem\\(\\) expects ImpactItem, bool\\|ImpactItem given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Csv/ImpactCsvExport.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getForeignKeyField\\(\\) on CommonDBTM\\|false\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Csv/PlanningCsv.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getTypeName\\(\\) on CommonDBTM\\|false\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Csv/PlanningCsv.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#5 \\$end_date of static method PrinterLog\\:\\:getMetrics\\(\\) expects Safe\\\\DateTime, Safe\\\\DateTime\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 2, - 'path' => __DIR__ . '/src/Glpi/Csv/PrinterLogCsvExport.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#5 \\$end_date of static method PrinterLog\\:\\:getMetrics\\(\\) expects Safe\\\\DateTime, Safe\\\\DateTime\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Csv/PrinterLogCsvExportComparison.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$categories on Gettext\\\\Languages\\\\Language\\|null\\.$#', - 'identifier' => 'property.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$formula on Gettext\\\\Languages\\\\Language\\|null\\.$#', - 'identifier' => 'property.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\CustomObject\\\\AbstractDefinition\\:\\:getCustomObjectClassName\\(\\) should return class\\-string\\ but returns string\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\CustomObject\\\\AbstractDefinition\\:\\:prepareInputForAdd\\(\\) should return array\\\\|false but returns array\\|bool\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\CustomObject\\\\AbstractDefinition\\:\\:prepareInputForUpdate\\(\\) should return array\\\\|false but returns array\\|bool\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$id of static method Gettext\\\\Languages\\\\Language\\:\\:getById\\(\\) expects string, string\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/CustomObject/AbstractDefinition.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$json of function Safe\\\\json_decode expects string, string\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Dashboard/Dashboard.php', + 'path' => __DIR__ . '/src/Glpi/Dashboard/Dashboard.php', ]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$raw_rights of static method Glpi\\\\Dashboard\\\\Dashboard\\:\\:convertRights\\(\\) expects array, array\\|null given\\.$#', @@ -7765,12 +8011,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Dashboard/Grid.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Dashboard/Provider.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getSystemSQLCriteria\\(\\) on CommonDBTM\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -7849,6 +8089,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Dashboard/Provider.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call static method getType\\(\\) on CommonDBTM\\|null\\.$#', + 'identifier' => 'staticMethod.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Dashboard/Provider.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call static method getTypeName\\(\\) on CommonDBTM\\|null\\.$#', 'identifier' => 'staticMethod.nonObject', @@ -7874,7 +8120,7 @@ 'path' => __DIR__ . '/src/Glpi/Dashboard/Provider.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, class\\-string\\\\|null given\\.$#', + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Dashboard/Provider.php', @@ -8143,6 +8389,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Form/Comment.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Glpi/Form/Comment.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getExtraDataConfig\\(\\) on Glpi\\\\Form\\\\QuestionType\\\\QuestionTypeInterface\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -8599,6 +8851,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Form/Form.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Form/Form.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Glpi\\\\ItemTranslation\\\\ItemTranslation\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -8725,6 +8983,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Form/Question.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Glpi/Form/Question.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call static method getById\\(\\) on class\\-string\\\\|null\\.$#', 'identifier' => 'staticMethod.nonObject', @@ -9484,7 +9748,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of method Lockedfield\\:\\:getLockedNames\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Inventory/Asset/InventoryAsset.php', ]; $ignoreErrors[] = [ @@ -10513,6 +10777,12 @@ 'count' => 4, 'path' => __DIR__ . '/src/Glpi/Inventory/Asset/Software.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Glpi/Inventory/Asset/Software.php', +]; $ignoreErrors[] = [ 'message' => '#^Property Glpi\\\\Inventory\\\\Asset\\\\InventoryAsset\\:\\:\\$known_links \\(array\\\\) does not accept array\\\\.$#', 'identifier' => 'assign.propertyType', @@ -10879,6 +11149,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/Conf.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 38, + 'path' => __DIR__ . '/src/Glpi/Inventory/Conf.php', +]; $ignoreErrors[] = [ 'message' => '#^Access to an undefined property object\\:\\:\\$properties\\.$#', 'identifier' => 'property.notFound', @@ -10952,13 +11228,25 @@ 'path' => __DIR__ . '/src/Glpi/Inventory/Inventory.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getItem\\(\\) on Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\|null\\.$#', + 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/Inventory.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method handle\\(\\) on Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\|null\\.$#', + 'message' => '#^Cannot call method getItem\\(\\) on Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\|null\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Inventory/Inventory.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Inventory/Inventory.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method handle\\(\\) on Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\|null\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/Inventory.php', @@ -11167,6 +11455,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/MainAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method isPartial\\(\\) on class\\-string\\|object\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/MainAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\:\\:addNetworkName\\(\\) should return int but returns int\\|false\\.$#', 'identifier' => 'return.type', @@ -11191,6 +11485,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/MainAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$items of method Transfer\\:\\:moveItems\\(\\) expects array\\, array\\\\>, array\\\\> given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/MainAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Dropdown\\:\\:importExternal\\(\\) expects string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -11227,6 +11527,12 @@ 'count' => 3, 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/MainAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of method Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\:\\:rulepassed\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/MainAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$val of static method Glpi\\\\Inventory\\\\MainAsset\\\\NetworkEquipment\\:\\:needToBeUpdatedFromDiscovery\\(\\) expects stdClass, object given\\.$#', 'identifier' => 'argument.type', @@ -11569,6 +11875,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/Unmanaged.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$items of method Transfer\\:\\:moveItems\\(\\) expects array\\, array\\\\>, array\\ given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/Unmanaged.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object of method Glpi\\\\Inventory\\\\MainAsset\\\\MainAsset\\:\\:isAccessPoint\\(\\) expects stdClass, object given\\.$#', 'identifier' => 'argument.type', @@ -11611,6 +11923,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/ItemTranslation/ItemTranslation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method send\\(\\) on Symfony\\\\Component\\\\HttpFoundation\\\\Response\\|null\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Kernel/Kernel.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$strategy of method Glpi\\\\Http\\\\Firewall\\:\\:applyStrategy\\(\\) expects \'admin_access\'\\|\'authenticated\'\\|\'central_access\'\\|\'faq_access\'\\|\'helpdesk_access\'\\|\'no_check\', string given\\.$#', 'identifier' => 'argument.type', @@ -11773,12 +12091,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Kernel/Listener/RequestListener/PluginsRouterListener.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Offset 1 might not exist on list\\{0\\?\\: string, 1\\?\\: numeric\\-string\\}\\.$#', - 'identifier' => 'offsetAccess.notFound', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Knowbase/History/LogEvent.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$level of method Monolog\\\\Handler\\\\StreamHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|\'ALERT\'\\|\'alert\'\\|\'CRITICAL\'\\|\'critical\'\\|\'DEBUG\'\\|\'debug\'\\|\'EMERGENCY\'\\|\'emergency\'\\|\'ERROR\'\\|\'error\'\\|\'INFO\'\\|\'info\'\\|\'NOTICE\'\\|\'notice\'\\|\'WARNING\'\\|\'warning\'\\|Monolog\\\\Level, string given\\.$#', 'identifier' => 'argument.type', @@ -11912,7 +12224,7 @@ 'path' => __DIR__ . '/src/Glpi/Migration/AbstractPluginMigration.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Glpi\\\\Migration\\\\GenericobjectPluginMigration\\:\\:getCustomFieldSpecs\\(\\) should return array\\{system_name\\: string, label\\: string, type\\: class\\-string\\, itemtype\\?\\: class\\-string\\, options\\?\\: array\\{min\\?\\: int, max\\?\\: int, step\\?\\: int\\}\\} but returns array\\{system_name\\: string, label\\: string, type\\: \'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\BooleanType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DateTimeType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DateType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\NumberType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\StringType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\TextType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\URLType\', options\\?\\: array\\{step\\: \'any\'\\}, translations\\?\\: array\\{\\}\\}\\|array\\{system_name\\: string\\|null, label\\: string, type\\: \'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\BooleanType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DateTimeType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DateType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DropdownType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\NumberType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\StringType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\TextType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\URLType\', itemtype\\?\\: class\\-string\\, options\\?\\: array\\{\\}\\|array\\{min\\?\\: int, max\\?\\: int, step\\?\\: int\\}, translations\\?\\: array\\{\\}\\}\\.$#', + 'message' => '#^Method Glpi\\\\Migration\\\\GenericobjectPluginMigration\\:\\:getCustomFieldSpecs\\(\\) should return array\\{system_name\\: string, label\\: string, type\\: class\\-string\\, itemtype\\?\\: class\\-string\\, options\\?\\: array\\{min\\?\\: int, max\\?\\: int, step\\?\\: int\\}\\} but returns array\\{system_name\\: string\\|null, label\\: string, type\\: \'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\BooleanType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DateTimeType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DateType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\DropdownType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\NumberType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\StringType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\TextType\'\\|\'Glpi\\\\\\\\Asset\\\\\\\\CustomFieldType\\\\\\\\URLType\', itemtype\\?\\: class\\-string\\, options\\?\\: array\\{\\}\\|array\\{min\\?\\: int, max\\?\\: int, step\\?\\: int\\}\\|array\\{step\\: \'any\'\\}, translations\\?\\: array\\{\\}\\}\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Migration/GenericobjectPluginMigration.php', @@ -12061,6 +12373,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Search/CriteriaFilter.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Search/CriteriaFilter.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'defaultfilter\' on array\\|true\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -12247,6 +12565,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$expression of class Glpi\\\\DBAL\\\\QueryExpression constructor expects string, array\\\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$haystack of function str_contains expects string, int\\|string given\\.$#', 'identifier' => 'argument.type', @@ -12278,13 +12602,13 @@ 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$num of function abs expects float\\|int, int\\\\|string given\\.$#', + 'message' => '#^Parameter \\#1 \\$itemtype of static method Session\\:\\:haveTranslations\\(\\) expects string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 4, 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', + 'message' => '#^Parameter \\#1 \\$num of function abs expects float\\|int, int\\\\|string given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', @@ -12319,6 +12643,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$val of static method Glpi\\\\Search\\\\Provider\\\\SQLProvider\\:\\:makeTextSearchValue\\(\\) expects string, int\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$val of static method Html\\:\\:computeGenericDateTimeSearch\\(\\) expects string, int\\|string given\\.$#', 'identifier' => 'argument.type', @@ -12355,6 +12685,18 @@ 'count' => 4, 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$val of static method Glpi\\\\Search\\\\Provider\\\\SQLProvider\\:\\:makeTextCriteria\\(\\) expects string, int\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$itemtype of static method Glpi\\\\Search\\\\Provider\\\\SQLProvider\\:\\:getDropdownTranslationJoinCriteria\\(\\) expects class\\-string\\, class\\-string\\\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Glpi/Search/Provider/SQLProvider.php', +]; $ignoreErrors[] = [ 'message' => '#^Part \\$token \\(array\\\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#', 'identifier' => 'encapsedStringPart.nonString', @@ -12475,6 +12817,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Socket.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Glpi\\\\Socket\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Socket.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Glpi\\\\Socket\\:\\:canPurgeItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Socket.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Glpi\\\\Socket\\:\\:dropdownWiringSide\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -12487,6 +12841,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Socket.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Socket.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Glpi/Socket.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$array of function array_keys expects array, array\\|null given\\.$#', 'identifier' => 'argument.type', @@ -12745,6 +13111,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Toolbox/Filesystem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Offset 0\\|1\\|2\\|3\\|4\\|5\\|6 might not exist on array\\{\\}\\|array\\{"\\\\032"\\}\\|array\\{0\\: \'"\', 1\\?\\: "\\\\032"\\}\\|list\\{0\\: "\\\\000", 1\\?\\: "\\\\n"\\|"\\\\r"\\|"\\\\032"\\|\'"\'\\|\'\\\\\'\'\\|\'\\\\\\\\\', 2\\?\\: "\\\\r"\\|"\\\\032"\\|\'"\'\\|\'\\\\\'\'\\|\'\\\\\\\\\', 3\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\'\\|\'\\\\\\\\\', 4\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\', 5\\?\\: "\\\\032"\\|\'"\', 6\\?\\: "\\\\032"\\}\\|list\\{0\\: "\\\\n", 1\\?\\: "\\\\r"\\|"\\\\032"\\|\'"\'\\|\'\\\\\'\'\\|\'\\\\\\\\\', 2\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\'\\|\'\\\\\\\\\', 3\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\', 4\\?\\: "\\\\032"\\|\'"\', 5\\?\\: "\\\\032"\\}\\|list\\{0\\: "\\\\r", 1\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\'\\|\'\\\\\\\\\', 2\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\', 3\\?\\: "\\\\032"\\|\'"\', 4\\?\\: "\\\\032"\\}\\|list\\{0\\: \'\\\\\'\', 1\\?\\: "\\\\032"\\|\'"\', 2\\?\\: "\\\\032"\\}\\|list\\{0\\: \'\\\\\\\\\', 1\\?\\: "\\\\032"\\|\'"\'\\|\'\\\\\'\', 2\\?\\: "\\\\032"\\|\'"\', 3\\?\\: "\\\\032"\\}\\.$#', + 'identifier' => 'offsetAccess.notFound', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Toolbox/Sanitizer.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\|int\\|string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -12775,6 +13147,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Group.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Group.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Group\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -12829,6 +13207,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/HTMLTableGroup.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of method HTMLTableMain\\:\\:addItemType\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/HTMLTableHeader.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$content of class HTMLTableCell constructor expects string, array\\|string given\\.$#', 'identifier' => 'argument.type', @@ -12991,12 +13375,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Html.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/IPAddress.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset 1\\|2\\|3 on array\\\\|string\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -13027,6 +13405,12 @@ 'count' => 3, 'path' => __DIR__ . '/src/IPAddress.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/IPAddress.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$address of method IPAddress\\:\\:setAddressFromBinary\\(\\) expects array\\, array\\ given\\.$#', 'identifier' => 'argument.type', @@ -13075,6 +13459,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/IPAddress.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/IPAddress.php', +]; $ignoreErrors[] = [ 'message' => '#^Property IPAddress\\:\\:\\$binary \\(array\\\\|string\\) does not accept array\\\\.$#', 'identifier' => 'assign.propertyType', @@ -13213,12 +13603,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/IPNetwork_Vlan.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/IPNetwork_Vlan.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'code\' on array\\\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', 'count' => 1, 'path' => __DIR__ . '/src/ITILCategory.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILCategory.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'_job\' on array\\\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -13274,8 +13676,14 @@ 'path' => __DIR__ . '/src/ITILFollowup.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call static method getForeignKeyField\\(\\) on CommonDBTM\\|false\\|null\\.$#', - 'identifier' => 'staticMethod.nonObject', + 'message' => '#^Cannot call method getForeignKeyField\\(\\) on CommonDBTM\\|false\\|null\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILFollowup.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method ITILFollowup\\:\\:canViewItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/ITILFollowup.php', ]; @@ -13315,6 +13723,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ITILFollowupTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILFollowupTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'_job\' on array\\\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -13339,6 +13753,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ITILSolution.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILSolution.php', +]; $ignoreErrors[] = [ 'message' => '#^Method ITILTemplate\\:\\:getITILObjectClass\\(\\) should return class\\-string\\ but returns string\\.$#', 'identifier' => 'return.type', @@ -13357,6 +13777,30 @@ 'count' => 1, 'path' => __DIR__ . '/src/ITILTemplateField.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILTemplateHiddenField.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILTemplateMandatoryField.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILTemplatePredefinedField.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILTemplateReadonlyField.php', +]; $ignoreErrors[] = [ 'message' => '#^Method ITILValidationTemplate\\:\\:displayValidatorField\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -13369,6 +13813,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ITILValidationTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ITILValidationTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method find\\(\\) on ITIL_ValidationStep\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -13465,6 +13915,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Impact.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Impact.php', +]; $ignoreErrors[] = [ 'message' => '#^Binary operation "\\+" between float\\|int\\|string and float\\|int results in an error\\.$#', 'identifier' => 'binaryOp.invalid', @@ -13495,6 +13951,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Infocom.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Infocom\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Infocom.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Infocom\\:\\:canPurgeItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Infocom.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Infocom\\:\\:canUpdateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Infocom.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Infocom\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -13531,6 +14005,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Infocom.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Infocom.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#4 \\$buydate of static method Infocom\\:\\:linearAmortise\\(\\) expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -13615,6 +14095,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ItemAntivirus.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ItemAntivirus.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method can\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -13639,6 +14125,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ItemVirtualMachine.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ItemVirtualMachine.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -13651,6 +14143,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Cluster.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_Cluster.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -13747,6 +14245,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Devices.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Item_Devices.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#4 \\$item of method HTMLTableRow\\:\\:addCell\\(\\) expects CommonDBTM\\|null, CommonDBTM\\|false\\|null given\\.$#', 'identifier' => 'argument.type', @@ -13765,6 +14269,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Disk.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_Disk.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -13783,6 +14293,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Enclosure.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_Enclosure.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_Environment.php', +]; $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type array\\|null supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', @@ -13861,6 +14383,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Line.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Item_Line.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -13891,6 +14419,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_OperatingSystem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_OperatingSystem.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Item_Process.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -13903,6 +14443,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_Project.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Item_Project.php', +]; $ignoreErrors[] = [ 'message' => '#^Access to an undefined property object\\:\\:\\$fields\\.$#', 'identifier' => 'property.notFound', @@ -13999,6 +14545,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Item_SoftwareVersion.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Itil_Project.php', +]; $ignoreErrors[] = [ 'message' => '#^Binary operation "\\." between non\\-falsy\\-string and array\\\\|string results in an error\\.$#', 'identifier' => 'binaryOp.invalid', @@ -14053,6 +14605,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/KnowbaseItem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method KnowbaseItem\\:\\:getCategoryItem\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', @@ -14077,6 +14635,48 @@ 'count' => 1, 'path' => __DIR__ . '/src/KnowbaseItem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$item of static method KnowbaseItemTranslation\\:\\:getAlreadyTranslatedForItem\\(\\) expects KnowbaseItem, CommonDBTM given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItemTranslation.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method KnowbaseItem_Comment\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_Comment.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method KnowbaseItem_Comment\\:\\:canDelete\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_Comment.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method KnowbaseItem_Comment\\:\\:canUpdate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_Comment.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method KnowbaseItem_Comment\\:\\:canView\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_Comment.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_Comment.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -14086,7 +14686,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/KnowbaseItem_Item.php', ]; $ignoreErrors[] = [ @@ -14113,6 +14713,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/KnowbaseItem_Item.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method KnowbaseItem_KnowbaseItemCategory\\:\\:canPurgeItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_KnowbaseItemCategory.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method KnowbaseItem_Revision\\:\\:createNew\\(\\) should return int\\|false but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/KnowbaseItem_Revision.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on LevelAgreementLevel\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -14209,6 +14821,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/LevelAgreement.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/LevelAgreement.php', +]; $ignoreErrors[] = [ 'message' => '#^Static property LevelAgreement\\:\\:\\$itemtype \\(string\\) does not accept class\\-string\\|false\\.$#', 'identifier' => 'assign.propertyType', @@ -14239,6 +14857,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/LevelAgreementLevel.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/LevelAgreementLevel.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -14257,6 +14881,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Link.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Link.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$get_mac of static method Link\\:\\:getIPAndMACForItem\\(\\) expects bool, string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -14329,6 +14959,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Location.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Location.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -14336,11 +14972,17 @@ 'path' => __DIR__ . '/src/Lock.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', + 'message' => '#^Cannot call method getErrorMessage\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Lock.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 2, + 'path' => __DIR__ . '/src/Lock.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getLink\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -14389,12 +15031,36 @@ 'count' => 1, 'path' => __DIR__ . '/src/Lock.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$baseitemtype of static method Lock\\:\\:getLocksQueryInfosByItemType\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Lock.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Lock.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call static method getTypeName\\(\\) on class\\-string\\\\|null\\.$#', 'identifier' => 'staticMethod.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Lockedfield.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Lockedfield\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Lockedfield.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Lockedfield\\:\\:canPurge\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Lockedfield.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Log\\:\\:constructHistory\\(\\) should return bool but returns bool\\|int\\.$#', 'identifier' => 'return.type', @@ -14437,12 +15103,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Log.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Glpi\\\\Search\\\\SearchOption\\:\\:getOptionsForItemtype\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Log.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$params of method DBmysql\\:\\:buildInsert\\(\\) expects array\\\\|Glpi\\\\DBAL\\\\QuerySubQuery, array\\ given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Log.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Log.php', +]; $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type Laminas\\\\Mail\\\\Storage\\\\AbstractStorage\\|null supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', @@ -14593,6 +15271,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/MailCollector.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ManualLink.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -14611,6 +15295,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Manufacturer.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Manufacturer.php', +]; $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type array\\|false supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', @@ -14671,6 +15361,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/MassiveAction.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getTableForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/MassiveAction.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Glpi\\\\Search\\\\SearchOption\\:\\:getOptionsForItemtype\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', @@ -14761,6 +15457,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Monitor.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Monitor.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -14773,6 +15475,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/NetworkAlias.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getID\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/NetworkAlias.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/NetworkAlias.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Dropdown\\:\\:show\\(\\) expects string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -14797,6 +15511,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/NetworkEquipment.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/NetworkEquipment.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -14804,13 +15524,13 @@ 'path' => __DIR__ . '/src/NetworkEquipment.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', - 'identifier' => 'classConstant.nonObject', + 'message' => '#^Cannot call method getID\\(\\) on CommonDBTM\\|null\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/NetworkEquipmentModelStencil.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getID\\(\\) on CommonDBTM\\|null\\.$#', + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|null\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/NetworkEquipmentModelStencil.php', @@ -14893,6 +15613,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/NetworkPort.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of method NetworkPort\\:\\:getIpsForPort\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/NetworkPort.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$user_id of static method DisplayPreference\\:\\:getForTypeUser\\(\\) expects int, int\\|string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -15109,6 +15835,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotImportedEmail.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Notepad\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Notepad.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Notepad\\:\\:canUpdateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Notepad.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$input of method CommonDBTM\\:\\:addFiles\\(\\) expects array\\, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -15121,12 +15859,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Notepad.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Notepad.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Notification\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/Notification.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Glpi\\\\Search\\\\SearchOption\\:\\:getOptionsForItemtype\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Notification.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$ong of method CommonGLPI\\:\\:addStandardTab\\(\\) expects array\\, array\\ given\\.$#', 'identifier' => 'argument.type', @@ -15259,6 +16009,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTarget.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/NotificationTarget.php', +]; $ignoreErrors[] = [ 'message' => '#^Property NotificationTarget\\\\:\\:\\$mode \\(\'ajax\'\\|\'irc\'\\|\'mailing\'\\|\'sms\'\\|\'websocket\'\\|\'xmpp\'\\|null\\) does not accept string\\.$#', 'identifier' => 'assign.propertyType', @@ -15352,7 +16108,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot access constant class on \\(T of CommonITILObject\\)\\|null\\.$#', 'identifier' => 'classConstant.nonObject', - 'count' => 7, + 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetCommonITILObject.php', ]; $ignoreErrors[] = [ @@ -15403,6 +16159,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetCommonITILObject.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getField\\(\\) on \\(T of CommonITILObject\\)\\|null\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/NotificationTargetCommonITILObject.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getField\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -15430,7 +16192,13 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method getID\\(\\) on \\(T of CommonITILObject\\)\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 2, + 'count' => 1, + 'path' => __DIR__ . '/src/NotificationTargetCommonITILObject.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on \\(T of CommonITILObject\\)\\|null\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 6, 'path' => __DIR__ . '/src/NotificationTargetCommonITILObject.php', ]; $ignoreErrors[] = [ @@ -15496,7 +16264,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$string of function strtolower expects string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', - 'count' => 2, + 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetCommonITILObject.php', ]; $ignoreErrors[] = [ @@ -15637,12 +16405,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetInfocom.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on Entity\\|Group\\|Profile\\|User\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/NotificationTargetKnowbaseItem.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Entity\\|Group\\|Profile\\|User\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -15679,6 +16441,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetKnowbaseItem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on Entity\\|Group\\|Profile\\|User\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/NotificationTargetKnowbaseItem.php', +]; $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type array\\\\|string supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', @@ -15745,12 +16513,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetPlanningRecall.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on CommonDBTM\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/NotificationTargetPlanningRecall.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CommonITILTask\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -15763,6 +16525,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetPlanningRecall.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/NotificationTargetPlanningRecall.php', +]; $ignoreErrors[] = [ 'message' => '#^Offset \'label\' might not exist on array\\|string\\.$#', 'identifier' => 'offsetAccess.notFound', @@ -15784,19 +16552,19 @@ $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Project\\|null\\.$#', 'identifier' => 'property.nonObject', - 'count' => 23, + 'count' => 5, 'path' => __DIR__ . '/src/NotificationTargetProject.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getField\\(\\) on Project\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 4, + 'count' => 26, 'path' => __DIR__ . '/src/NotificationTargetProject.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getID\\(\\) on Project\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 6, + 'count' => 2, 'path' => __DIR__ . '/src/NotificationTargetProject.php', ]; $ignoreErrors[] = [ @@ -15838,19 +16606,19 @@ $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on ProjectTask\\|null\\.$#', 'identifier' => 'property.nonObject', - 'count' => 26, + 'count' => 6, 'path' => __DIR__ . '/src/NotificationTargetProjectTask.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getField\\(\\) on ProjectTask\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 3, + 'count' => 26, 'path' => __DIR__ . '/src/NotificationTargetProjectTask.php', ]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getID\\(\\) on ProjectTask\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 4, + 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetProjectTask.php', ]; $ignoreErrors[] = [ @@ -15883,12 +16651,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetReservation.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$fields on Reservation\\|null\\.$#', - 'identifier' => 'property.nonObject', - 'count' => 4, - 'path' => __DIR__ . '/src/NotificationTargetReservation.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot assign new offset to array\\\\|string\\>\\|string\\.$#', 'identifier' => 'offsetAssign.dimType', @@ -15898,13 +16660,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method getField\\(\\) on Reservation\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/NotificationTargetReservation.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getID\\(\\) on Reservation\\|null\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, + 'count' => 6, 'path' => __DIR__ . '/src/NotificationTargetReservation.php', ]; $ignoreErrors[] = [ @@ -15926,8 +16682,8 @@ 'path' => __DIR__ . '/src/NotificationTargetSavedSearch_Alert.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$fields on SavedSearch_Alert\\|null\\.$#', - 'identifier' => 'property.nonObject', + 'message' => '#^Cannot call method getField\\(\\) on SavedSearch_Alert\\|null\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetSavedSearch_Alert.php', ]; @@ -15956,7 +16712,7 @@ 'path' => __DIR__ . '/src/NotificationTargetSoftwareLicense.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot call method getID\\(\\) on Ticket\\|null\\.$#', + 'message' => '#^Cannot call method getField\\(\\) on Ticket\\|null\\.$#', 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/NotificationTargetTicket.php', @@ -15970,7 +16726,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on User\\|null\\.$#', 'identifier' => 'property.nonObject', - 'count' => 3, + 'count' => 2, 'path' => __DIR__ . '/src/NotificationTargetUser.php', ]; $ignoreErrors[] = [ @@ -15982,7 +16738,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method getField\\(\\) on User\\|null\\.$#', 'identifier' => 'method.nonObject', - 'count' => 3, + 'count' => 4, 'path' => __DIR__ . '/src/NotificationTargetUser.php', ]; $ignoreErrors[] = [ @@ -16057,12 +16813,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/NotificationTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/NotificationTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object of function get_class expects object, CommonGLPI\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/NotificationTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$object_or_class of function method_exists expects object\\|string, CommonGLPI\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/NotificationTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object_or_class of function property_exists expects object\\|string, CommonGLPI\\|null given\\.$#', 'identifier' => 'argument.type', @@ -16129,12 +16897,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Notification_NotificationTemplate.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method OAuthClient\\:\\:defineTabs\\(\\) should return array\\ but returns array\\\\.$#', - 'identifier' => 'return.type', - 'count' => 1, - 'path' => __DIR__ . '/src/OAuthClient.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -16159,6 +16921,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/PDU.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/PDU.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -16177,6 +16945,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/PassiveDCEquipment.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/PassiveDCEquipment.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -16303,6 +17077,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Peripheral.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Peripheral.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -16327,6 +17107,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Phone.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Phone.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -16669,6 +17455,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Printer.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Printer.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -16699,6 +17491,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/PrinterLog.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/PrinterLog.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Problem\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Problem.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Problem\\:\\:displayTabContentForItem\\(\\) should return bool but returns false\\|null\\.$#', 'identifier' => 'return.type', @@ -16711,6 +17515,24 @@ 'count' => 4, 'path' => __DIR__ . '/src/Problem.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Problem.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method ProblemCost\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ProblemCost.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method ProblemCost\\:\\:canUpdate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ProblemCost.php', +]; $ignoreErrors[] = [ 'message' => '#^Method ProblemTask\\:\\:displayPlanningItem\\(\\) should return string but returns string\\|false\\.$#', 'identifier' => 'return.type', @@ -16729,6 +17551,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ProblemTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ProblemTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'id\' on array\\\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -16777,6 +17605,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Profile.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Profile.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Profile\\:\\:getRightsFor\\(\\) expects class\\-string\\, string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -16801,24 +17635,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/ProfileRight.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on Entity\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/src/Profile_User.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on Profile\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 2, - 'path' => __DIR__ . '/src/Profile_User.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access constant class on User\\|false\\.$#', - 'identifier' => 'classConstant.nonObject', - 'count' => 3, - 'path' => __DIR__ . '/src/Profile_User.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$dohistory on Entity\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -16873,6 +17689,36 @@ 'count' => 1, 'path' => __DIR__ . '/src/Profile_User.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on Entity\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Profile_User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on Profile\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 2, + 'path' => __DIR__ . '/src/Profile_User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getType\\(\\) on User\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 3, + 'path' => __DIR__ . '/src/Profile_User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$itemtype of static method Log\\:\\:history\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Profile_User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/Profile_User.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on static\\(Project\\)\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -16885,6 +17731,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Project.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Project\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Project.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Project\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', @@ -16903,6 +17755,18 @@ 'count' => 2, 'path' => __DIR__ . '/src/Project.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Project.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Project\\:\\:getKanbanPluginFilters\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Project.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$object_or_class of function is_a expects object\\|string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -16987,6 +17851,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/ProjectTask.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method ProjectTask\\:\\:canCreate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ProjectTask.php', +]; $ignoreErrors[] = [ 'message' => '#^Offset \'display_name\' might not exist on array\\{id\\: int, projecttasks_id\\: int, itemtype\\: class\\-string\\, items_id\\: int, display_name\\?\\: string\\}\\.$#', 'identifier' => 'offsetAccess.notFound', @@ -17023,12 +17893,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/ProjectTask.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ProjectTaskTeam.php', +]; $ignoreErrors[] = [ 'message' => '#^Part \\$sort \\(\'_effect_duration\'\\|\'fname\'\\|\'name\'\\|\'percent_done\'\\|\'plan_end_date\'\\|\'plan_start_date\'\\|\'planned_duration\'\\|\'projectname\'\\|\'sname\'\\|\'tname\'\\|array\\{\'plan_start_date ASC\'\\|\'plan_start_date DESC\', \'name\'\\}\\) of encapsed string cannot be cast to string\\.$#', 'identifier' => 'encapsedStringPart.nonString', 'count' => 1, 'path' => __DIR__ . '/src/ProjectTask_Ticket.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ProjectTeam.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CronTask\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -17071,6 +17953,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/QueuedWebhook.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method QueuedWebhook\\:\\:canDelete\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/QueuedWebhook.php', +]; $ignoreErrors[] = [ 'message' => '#^Method RSSFeed\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -17083,6 +17971,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/RSSFeed.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/RSSFeed.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|false\\.$#', 'identifier' => 'property.nonObject', @@ -17113,6 +18007,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Rack.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Rack.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$fields on Agent\\|null\\.$#', 'identifier' => 'property.nonObject', @@ -17206,7 +18106,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/Reminder.php', ]; $ignoreErrors[] = [ @@ -17221,12 +18121,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Reminder.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Reminder.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$users_id of method PlanningRecall\\:\\:getFromDBForItemAndUser\\(\\) expects int, int\\|string\\|false given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Reminder.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ReminderTranslation.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -17326,7 +18238,7 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', - 'count' => 2, + 'count' => 3, 'path' => __DIR__ . '/src/Reservation.php', ]; $ignoreErrors[] = [ @@ -17335,6 +18247,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Reservation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot call method getLink\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', + 'count' => 1, + 'path' => __DIR__ . '/src/Reservation.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getName\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -17353,12 +18271,30 @@ 'count' => 1, 'path' => __DIR__ . '/src/Reservation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Reservation\\:\\:canDelete\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Reservation.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$datetime of function Safe\\\\strtotime expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/Reservation.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of method ReservationItem\\:\\:getFromDBbyItem\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 5, + 'path' => __DIR__ . '/src/Reservation.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Reservation.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$id of static method CommonDBTM\\:\\:getById\\(\\) expects int\\|null, int\\|string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -17473,6 +18409,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Rule.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$ID of function getUserName expects int, int\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Rule.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$ID of function getUserName expects int, int\\|string\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Rule.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$ID of method CommonDBTM\\:\\:getFromDB\\(\\) expects int\\|string, int\\|string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -17491,6 +18439,12 @@ 'count' => 4, 'path' => __DIR__ . '/src/Rule.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Rule.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of function getItemForItemtype expects string, class\\-string\\\\|null given\\.$#', 'identifier' => 'argument.type', @@ -17605,6 +18559,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Rule.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Rule.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$pattern of method Rule\\:\\:getAdditionalCriteriaDisplayPattern\\(\\) expects string, int\\|string\\|null given\\.$#', 'identifier' => 'argument.type', @@ -17677,12 +18637,30 @@ 'count' => 2, 'path' => __DIR__ . '/src/RuleAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot assign offset array\\\\|string to array\\\\|string\\.$#', + 'identifier' => 'offsetAssign.dimType', + 'count' => 1, + 'path' => __DIR__ . '/src/RuleAsset.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Offset \'appendto\' might not exist on array\\\\|string\\>\\|string\\.$#', + 'identifier' => 'offsetAccess.notFound', + 'count' => 1, + 'path' => __DIR__ . '/src/RuleAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$value of function count expects array\\|Countable, array\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/RuleAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Possibly invalid array key type array\\\\|string\\.$#', + 'identifier' => 'offsetAccess.invalidOffset', + 'count' => 2, + 'path' => __DIR__ . '/src/RuleAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access constant class on CommonDBTM\\|null\\.$#', 'identifier' => 'classConstant.nonObject', @@ -18043,6 +19021,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/RuleImportAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Argument of an invalid type array\\|null supplied for foreach, only iterables are supported\\.$#', + 'identifier' => 'foreach.nonIterable', + 'count' => 2, + 'path' => __DIR__ . '/src/RuleImportAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getAgent\\(\\) on class\\-string\\|object\\.$#', 'identifier' => 'method.nonObject', @@ -18061,6 +19045,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/RuleImportAsset.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$value of function count expects array\\|Countable, array\\|null given\\.$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/RuleImportAsset.php', +]; $ignoreErrors[] = [ 'message' => '#^Possibly invalid array key type class\\-string\\\\|CommonDBTM\\.$#', 'identifier' => 'offsetAccess.invalidOffset', @@ -18241,12 +19231,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/SavedSearch.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/SavedSearch.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of method Search\\:\\:prepareDatasForSearch\\(\\) expects class\\-string\\, class\\-string given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/src/SavedSearch.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/SavedSearch_Alert.php', +]; $ignoreErrors[] = [ 'message' => '#^Method SavedSearch_User\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -18373,12 +19375,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Session.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$id of static method CommonDBTM\\:\\:getById\\(\\) expects int\\|null, int\\|string\\|false given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/src/Session.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$interface of static method Profile\\:\\:getRightsForForm\\(\\) expects \'all\'\\|\'central\'\\|\'helpdesk\', string\\|false given\\.$#', 'identifier' => 'argument.type', @@ -18469,6 +19465,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Software.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Software.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Search\\:\\:getDatas\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', @@ -18511,6 +19513,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/SoftwareLicense.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/SoftwareLicense.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\\\|false given\\.$#', 'identifier' => 'argument.type', @@ -18529,6 +19537,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/SolutionTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/SolutionTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access an offset on array\\\\|int\\|string\\>\\>\\|int\\|string\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -18613,6 +19627,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/State.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/State.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#', 'identifier' => 'method.nonObject', @@ -18625,6 +19645,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Stencil.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Stencil.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'linktype\' on array\\{linktype\\: class\\-string\\, entities_id\\: int, name\\: string, id\\: int, serial\\: string\\|null, otherserial\\: string\\|null, is_deleted\\: 0\\|1\\}\\|false\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -18643,12 +19669,24 @@ 'count' => 2, 'path' => __DIR__ . '/src/Supplier.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Supplier.php', +]; $ignoreErrors[] = [ 'message' => '#^Method TaskCategory\\:\\:prepareInputForClone\\(\\) should return array but returns array\\|false\\.$#', 'identifier' => 'return.type', 'count' => 1, 'path' => __DIR__ . '/src/TaskCategory.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/TaskCategory.php', +]; $ignoreErrors[] = [ 'message' => '#^Method TaskTemplate\\:\\:getSpecificValueToSelect\\(\\) should return string but returns int\\|string\\.$#', 'identifier' => 'return.type', @@ -18661,6 +19699,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/TaskTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/TaskTemplate.php', +]; $ignoreErrors[] = [ 'message' => '#^Offset \'engine\' might not exist on array\\{0\\?\\: string, engine\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, 2\\?\\: string, version\\?\\: string, 3\\?\\: string, 4\\?\\: string, comment\\?\\: non\\-falsy\\-string, \\.\\.\\.\\}\\.$#', 'identifier' => 'offsetAccess.notFound', @@ -18686,8 +19730,8 @@ 'path' => __DIR__ . '/src/Telemetry.php', ]; $ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$fields on CommonDBTM\\|false\\.$#', - 'identifier' => 'property.nonObject', + 'message' => '#^Cannot call method getField\\(\\) on CommonDBTM\\|false\\.$#', + 'identifier' => 'method.nonObject', 'count' => 1, 'path' => __DIR__ . '/src/Ticket.php', ]; @@ -18697,6 +19741,24 @@ 'count' => 1, 'path' => __DIR__ . '/src/Ticket.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Ticket\\:\\:canAssign\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Ticket.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Ticket\\:\\:canDelete\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Ticket.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Ticket\\:\\:canUpdate\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Ticket.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Ticket\\:\\:displayTabContentForItem\\(\\) should return bool but returns false\\|null\\.$#', 'identifier' => 'return.type', @@ -18757,6 +19819,12 @@ 'count' => 9, 'path' => __DIR__ . '/src/Ticket.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Ticket.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$specifictime of static method Html\\:\\:computeGenericDateTimeSearch\\(\\) expects int\\|string, int\\|false given\\.$#', 'identifier' => 'argument.type', @@ -18781,6 +19849,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/TicketTemplate.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/TicketTemplate.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method TicketValidation\\:\\:canCreateItem\\(\\) should return bool but returns bool\\|int\\.$#', + 'identifier' => 'return.type', + 'count' => 2, + 'path' => __DIR__ . '/src/TicketValidation.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'class\' on non\\-empty\\-array\\|true\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -18793,6 +19873,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/Toolbox.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Toolbox\\:\\:addslashes_deep\\(\\) should return array\\\\|string but returns array\\\\|string\\>\\|string\\.$#', + 'identifier' => 'return.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Toolbox.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Toolbox\\:\\:checkNewVersionAvailable\\(\\) should return string but returns string\\|null\\.$#', 'identifier' => 'return.type', @@ -18901,12 +19987,24 @@ 'count' => 2, 'path' => __DIR__ . '/src/Toolbox.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$string of function stripslashes expects string, array\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Toolbox.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$string of function strtolower expects string, string\\|false\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 3, 'path' => __DIR__ . '/src/Toolbox.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$string of method DBmysql\\:\\:escape\\(\\) expects string, array\\\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Toolbox.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$width of function Safe\\\\imagecreatetruecolor expects int, float\\|int given\\.$#', 'identifier' => 'argument.type', @@ -19261,6 +20359,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/User.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/User.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Search\\:\\:getDatas\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', @@ -19303,6 +20407,12 @@ 'count' => 3, 'path' => __DIR__ . '/src/User.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 3, + 'path' => __DIR__ . '/src/User.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#3 \\$value of function getEntitiesRestrictCriteria expects \'\'\\|array\\\\|int, int\\|string given\\.$#', 'identifier' => 'argument.type', @@ -19327,6 +20437,12 @@ 'count' => 2, 'path' => __DIR__ . '/src/ValidatorSubstitute.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/ValidatorSubstitute.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot call method addCell\\(\\) on HTMLTableRow\\|null\\.$#', 'identifier' => 'method.nonObject', @@ -19423,6 +20539,18 @@ 'count' => 1, 'path' => __DIR__ . '/src/Webhook.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of function getForeignKeyFieldForItemType expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Webhook.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$itemtype of static method Glpi\\\\Search\\\\SearchOption\\:\\:getOptionsForItemtype\\(\\) expects class\\-string\\, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Webhook.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$itemtype of static method Webhook\\:\\:getAPISchemaBySupportedItemtype\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', @@ -19438,7 +20566,7 @@ $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$itemtype of method Webhook\\:\\:addParentItemData\\(\\) expects class\\-string\\, string given\\.$#', 'identifier' => 'argument.type', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/src/Webhook.php', ]; $ignoreErrors[] = [ @@ -19447,6 +20575,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Webhook.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\$form_itemtype of static method CommonGLPI\\:\\:createTabEntry\\(\\) expects class\\-string\\\\|null, string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 5, + 'path' => __DIR__ . '/src/Webhook.php', +]; $ignoreErrors[] = [ 'message' => '#^Possibly invalid array key type array\\\\|string\\.$#', 'identifier' => 'offsetAccess.invalidOffset', @@ -19454,11 +20588,17 @@ 'path' => __DIR__ . '/src/Webhook.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$string of function strtolower expects string, array\\\\|string, list\\\\|string\\>\\|int\\|string\\>\\|class\\-string\\|string given\\.$#', + 'message' => '#^Parameter \\#1 \\$string of function strtolower expects string, array\\\\|string\\>\\|int\\|string\\>\\|class\\-string\\|string given\\.$#', 'identifier' => 'argument.type', 'count' => 2, 'path' => __DIR__ . '/src/autoload/CFG_GLPI.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\.\\.\\.\\$arrays of function array_merge expects array, array\\\\|string, list\\\\|string\\>\\|int\\|string given\\.$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/autoload/CFG_GLPI.php', +]; $ignoreErrors[] = [ 'message' => '#^Function getItemForItemtype\\(\\) should return \\(T of CommonDBTM\\)\\|false but returns CommonDBTM\\|false\\.$#', 'identifier' => 'return.type', @@ -19477,5 +20617,17 @@ 'count' => 1, 'path' => __DIR__ . '/src/autoload/legacy-autoloader.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 $headers of class Glpi\\Http\\Response constructor expects array|string>, array> given.#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => __DIR__ . '/src/Glpi/Api/HL/Controller/AbstractController.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 $headers of class Glpi\\Http\\Response constructor expects array|string>, array> given.#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => __DIR__ . '/src/Glpi/Api/HL/StreamedResponseWrapper.php', +]; return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; diff --git a/.phpstan-baseline.specific.php b/.phpstan-baseline.specific.php index cd073f3cacd..a5ee0ef2464 100644 --- a/.phpstan-baseline.specific.php +++ b/.phpstan-baseline.specific.php @@ -164,6 +164,12 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Inventory/MainAsset/Unmanaged.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Possibly invalid array key type string\\|null\\.$#', + 'identifier' => 'offsetAccess.invalidOffset', + 'count' => 2, + 'path' => __DIR__ . '/src/Item_Ola.php', +]; $ignoreErrors[] = [ 'message' => '#^Possibly invalid array key type string\\|null\\.$#', 'identifier' => 'offsetAccess.invalidOffset', diff --git a/Makefile b/Makefile index e755956f971..e780ab2e335 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,9 @@ include tests/e2e/.env -include tests/e2e/.env.local # See: https://playwright.dev/docs/docker +DOCKER_TTY_FLAGS = $(shell [ -t 0 ] && echo "-it") PLAYWRIGHT = docker run \ - -it \ + $(DOCKER_TTY_FLAGS) \ --rm \ --ipc=host \ --user=$(shell id -u):$(shell id -g) \ diff --git a/css/includes/components/itilobject/_footer.scss b/css/includes/components/itilobject/_footer.scss index 875666e7fa0..5c529e85837 100644 --- a/css/includes/components/itilobject/_footer.scss +++ b/css/includes/components/itilobject/_footer.scss @@ -43,6 +43,14 @@ z-index: var(--glpi-zindex-fixed); } + .main-actions { + &.btn-splitted { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + } + } + .answer-action { &:hover { font-weight: bold; diff --git a/eslint.config.mjs b/eslint.config.mjs index e639c728f26..7012cb08738 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -191,7 +191,7 @@ export default [ "playwright/no-force-option": "error", "playwright/no-raw-locators": "error", "playwright/expect-expect": [ - "warn", + "error", { "assertFunctionPatterns": ["^expect.*", "^assert.*"] } diff --git a/front/auth.others.php b/front/auth.others.php index bbb76f470e8..ab3d046b3f3 100644 --- a/front/auth.others.php +++ b/front/auth.others.php @@ -43,7 +43,7 @@ //Update CAS configuration if (isset($_POST["update"])) { - $_POST['id'] = 1; + $_POST['id'] = Config::getConfigIDForContext('core'); $config->update($_POST); Html::redirect($CFG_GLPI["root_doc"] . "/front/auth.others.php"); } diff --git a/front/ticket.form.php b/front/ticket.form.php index a56c3e5d185..c672aa1f401 100644 --- a/front/ticket.form.php +++ b/front/ticket.form.php @@ -66,6 +66,7 @@ } } +// form submitted (add & update) // as _actors virtual field stores json, bypass automatic escaping if (isset($_POST['_actors'])) { $_POST['_actors'] = json_decode($_POST['_actors'], true); @@ -73,6 +74,7 @@ } if (isset($_POST["add"])) { + // form submitted (add) $track->check(-1, CREATE, $_POST); $_POST = $track->enforceReadonlyFields($_POST, true); @@ -83,6 +85,7 @@ } Html::back(); } elseif (isset($_POST['update'])) { + // form submitted (update) if (!$track::canUpdate()) { throw new AccessDeniedHttpException(); } @@ -123,6 +126,8 @@ } Html::redirect(Ticket::getFormURLWithID($_POST["id"]) . $toadd); } + + // in case user can update the ticket but not read it, redirect to the ticket list Session::addMessageAfterRedirect( __s('You have been redirected because you no longer have access to this ticket'), true, @@ -181,20 +186,6 @@ sprintf(__('%s updates an item'), $_SESSION["glpiname"]) ); - Html::redirect(Ticket::getFormURLWithID($_POST["id"])); -} elseif (isset($_POST['ola_delete'])) { - $track->check($_POST["id"], UPDATE); - - $track->deleteLevelAgreement("OLA", $_POST["id"], $_POST['type'], $_POST['delete_date']); - Event::log( - $_POST["id"], - "ticket", - 4, - "tracking", - //TRANS: %s is the user login - sprintf(__('%s updates an item'), $_SESSION["glpiname"]) - ); - Html::redirect(Ticket::getFormURLWithID($_POST["id"])); } elseif (isset($_POST['addme_as_actor'])) { $id = (int) $_POST['id']; @@ -234,6 +225,7 @@ Html::back(); } +// show form when editing a ticket $id = (int) $_GET['id']; if ($id > 0) { $available_options = ['_openfollowup']; diff --git a/inc/relation.constant.php b/inc/relation.constant.php index 5a1d97e7129..eb7bace7989 100644 --- a/inc/relation.constant.php +++ b/inc/relation.constant.php @@ -752,6 +752,7 @@ 'glpi_tickettasks' => 'groups_id_tech', 'glpi_users' => 'groups_id', 'glpi_itilvalidationtemplates_targets' => 'groups_id', + 'glpi_olas' => 'groups_id', ], 'glpi_holidays' => [ @@ -1036,15 +1037,11 @@ '_glpi_olalevelactions' => 'olalevels_id', '_glpi_olalevelcriterias' => 'olalevels_id', '_glpi_olalevels_tickets' => 'olalevels_id', - 'glpi_tickets' => 'olalevels_id_ttr', ], 'glpi_olas' => [ 'glpi_olalevels' => 'olas_id', - 'glpi_tickets' => [ - 'olas_id_ttr', - 'olas_id_tto', - ], + '_glpi_items_olas' => 'olas_id', ], 'glpi_operatingsystemarchitectures' => [ diff --git a/install/empty_data.php b/install/empty_data.php index 7ff6936718f..03d309d304c 100644 --- a/install/empty_data.php +++ b/install/empty_data.php @@ -786,7 +786,7 @@ public function getEmptyData(): array 'hourmax' => 24, ], [ 'id' => 32, - 'itemtype' => 'OlaLevel_Ticket', + 'itemtype' => 'Item_Ola', 'name' => 'olaticket', 'frequency' => 5 * MINUTE_TIMESTAMP, 'param' => null, diff --git a/install/migrations/update_11.0.x_to_12.0.0/ola.php b/install/migrations/update_11.0.x_to_12.0.0/ola.php new file mode 100644 index 00000000000..71d38a4b7b9 --- /dev/null +++ b/install/migrations/update_11.0.x_to_12.0.0/ola.php @@ -0,0 +1,205 @@ +. + * + * --------------------------------------------------------------------- + */ + +/** + * @var DBmysql $DB + * @var Migration $migration + */ + +use function Safe\json_encode; + +$migration->log('Group managed olas', false); + +add_groups_id_field_in_olas($migration); +create_items_olas_table($migration); +migrate_items_olas_data($migration); +remove_olas_fields_in_tickets($migration); +update_crontask($migration, $DB); + +$migration->executeMigration(); +return; + +// --- functions + +function add_groups_id_field_in_olas(Migration $migration): void +{ + $migration->addField( + OLA::getTable(), + Group::getForeignKeyField(), + 'fkey', + [ + 'value' => '0', + 'null' => false, + 'after' => 'slms_id', + ] + ); + + // addKey requires the table to exist -> execute migration before + $migration->executeMigration(); + $migration->addKey(OLA::getTable(), Group::getForeignKeyField()); +} + +function remove_olas_fields_in_tickets(Migration $migration): void +{ + $fields_to_remove = [ + 'ola_waiting_duration', + 'olas_id_tto', + 'olas_id_ttr', + 'olalevels_id_ttr', + 'ola_tto_begin_date', + 'ola_ttr_begin_date', + 'internal_time_to_resolve', + 'internal_time_to_own', + ]; + + foreach ($fields_to_remove as $field) { + $migration->dropField(Ticket::getTable(), $field); + } +} +function create_items_olas_table(Migration $migration): void +{ + $charset = DBConnection::getDefaultCharset(); + $collation = DBConnection::getDefaultCollation(); + $pk_sign = DBConnection::getDefaultPrimaryKeySignOption(); + + $query = "CREATE TABLE IF NOT EXISTS `glpi_items_olas` ( + `id` int {$pk_sign} NOT NULL AUTO_INCREMENT, + `itemtype` varchar(255) NOT NULL, + `items_id` int unsigned NOT NULL, + `olas_id` int unsigned NOT NULL, + `ola_type` tinyint NOT NULL, -- 1: TTO, 2: TTR + `start_time` timestamp NULL DEFAULT NULL, + `due_time` timestamp NULL DEFAULT NULL, + `end_time` timestamp NULL DEFAULT NULL, + `waiting_time` int NOT NULL DEFAULT 0, + `waiting_start` timestamp, + `is_late` tinyint NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=$charset COLLATE=$collation ROW_FORMAT=DYNAMIC;"; + $migration->addPreQuery($query); + $migration->executeMigration(); + + $migration->addKey('glpi_items_olas', 'olas_id'); + $migration->addKey('glpi_items_olas', ['itemtype', 'items_id'], 'item'); +} + +function migrate_items_olas_data(Migration $migration): void +{ + $_ticket = new Ticket(); + if (!$_ticket->isField('olas_id_tto')) { + // olas_id_tto field is removed : considere migration as done + return; + } + $tickets_with_ola = $_ticket->find(['OR' + => [ + ['NOT' => ['olas_id_tto' => null]], + ['NOT' => ['olas_id_ttr' => null]], + ]]); + + foreach ($tickets_with_ola as $ticket) { + if ($ticket['olas_id_tto'] !== 0) { + $io = new Item_Ola(); + + $_data = [ + 'itemtype' => Ticket::class, + 'items_id' => $ticket['id'], + 'olas_id' => $ticket['olas_id_tto'], + 'start_time' => $ticket['ola_tto_begin_date'], + 'due_time' => $ticket['internal_time_to_own'], + 'waiting_time' => $ticket['ola_waiting_duration'], + 'waiting_start' => null, + ]; + + if (!$io->add($_data)) { + throw new Exception('Failed to migrate OLA TTO data: ' . json_encode($_data)); + } + } + + if ($ticket['olas_id_ttr'] !== 0) { + $io = new Item_Ola(); + + $_data = [ + 'itemtype' => Ticket::class, + 'items_id' => $ticket['id'], + 'olas_id' => $ticket['olas_id_ttr'], + 'start_time' => $ticket['ola_ttr_begin_date'], + 'due_time' => $ticket['internal_time_to_resolve'], + 'waiting_time' => $ticket['ola_waiting_duration'], + ]; + + if (!$io->add($_data)) { + throw new Exception('Failed to migrato OLA TTO data: ' . json_encode($_data)); + } + } + } +} + +function update_crontask(Migration $migration, DBmysql $DB): void +{ + // find if cron task already exists to choose against adding it or updating it + $crontask = $DB->request([ + 'SELECT' => ['id'], + 'FROM' => CronTask::getTable(), + 'WHERE' => [ + 'name' => 'olaticket', + ], + ]); + $id = $crontask->current() ? $crontask->current()['id'] : null; + + if (is_null($id)) { + // add new crontask + $migration->insertInTable( + 'glpi_crontasks', + [ + 'itemtype' => 'Item_Ola', + 'name' => 'olaticket', + 'frequency' => 5 * MINUTE_TIMESTAMP, + 'param' => null, + 'state' => CronTask::STATE_WAITING, + 'mode' => CronTask::MODE_INTERNAL, + 'lastrun' => null, + 'logs_lifetime' => 30, + 'hourmin' => 0, + 'hourmax' => 24, + ] + ); + } else { + // update existing crontask + if (false === $DB->doQuery($DB->buildUpdate('glpi_crontasks', ['itemtype' => 'Item_Ola'], ['id' => $id]))) { + throw new Exception('Failed to update crontask itemtype'); + } + } +} diff --git a/install/migrations/update_11.0.x_to_12.0.0/sharetokens.php b/install/migrations/update_11.0.x_to_12.0.0/sharetokens.php index f3a04c97edb..ee362510aab 100644 --- a/install/migrations/update_11.0.x_to_12.0.0/sharetokens.php +++ b/install/migrations/update_11.0.x_to_12.0.0/sharetokens.php @@ -46,7 +46,8 @@ `itemtype` varchar(255) NOT NULL, `items_id` int {$default_key_sign} NOT NULL DEFAULT '0', `name` varchar(255) DEFAULT NULL, - `token` varchar(64) NOT NULL, + `token` varchar(255) NOT NULL, + `token_hint` varchar(16) NOT NULL DEFAULT '', `is_active` tinyint NOT NULL DEFAULT '1', `users_id` int {$default_key_sign} NOT NULL DEFAULT '0', `date_creation` timestamp NULL DEFAULT NULL, @@ -54,6 +55,7 @@ `date_expiration` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `token` (`token`), + KEY `token_hint` (`token_hint`), KEY `item` (`itemtype`, `items_id`), KEY `name` (`name`), KEY `is_active` (`is_active`), diff --git a/install/mysql/glpi-empty.sql b/install/mysql/glpi-empty.sql index 32b4ed1f48a..8593a467dd4 100644 --- a/install/mysql/glpi-empty.sql +++ b/install/mysql/glpi-empty.sql @@ -6932,6 +6932,7 @@ CREATE TABLE `glpi_olas` ( `end_of_working_day` tinyint NOT NULL DEFAULT '0', `date_creation` timestamp NULL DEFAULT NULL, `slms_id` int unsigned NOT NULL DEFAULT '0', + `groups_id` int unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `name` (`name`), KEY `entities_id` (`entities_id`), @@ -6939,7 +6940,28 @@ CREATE TABLE `glpi_olas` ( KEY `date_mod` (`date_mod`), KEY `date_creation` (`date_creation`), KEY `calendars_id` (`calendars_id`), - KEY `slms_id` (`slms_id`) + KEY `slms_id` (`slms_id`), + KEY `groups_id` (`groups_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + +### Dump table glpi_items_olas + +DROP TABLE IF EXISTS `glpi_items_olas`; +CREATE TABLE `glpi_items_olas` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `due_time` timestamp, + `end_time` timestamp, + `items_id` int unsigned NOT NULL, + `itemtype` varchar(255) NOT NULL, + `olas_id` int unsigned NOT NULL, + `ola_type` tinyint NOT NULL, + `start_time` timestamp, + `waiting_time` int NOT NULL DEFAULT 0, + `waiting_start` timestamp, + `is_late` tinyint NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `item` (`itemtype`,`items_id`), + KEY `olas_id` (`olas_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; ### Dump table glpi_softwarecategories @@ -7531,14 +7553,6 @@ CREATE TABLE `glpi_tickets` ( `time_to_own` timestamp NULL DEFAULT NULL, `begin_waiting_date` timestamp NULL DEFAULT NULL, `sla_waiting_duration` int NOT NULL DEFAULT '0', - `ola_waiting_duration` int NOT NULL DEFAULT '0', - `olas_id_tto` int unsigned NOT NULL DEFAULT '0', - `olas_id_ttr` int unsigned NOT NULL DEFAULT '0', - `olalevels_id_ttr` int unsigned NOT NULL DEFAULT '0', - `ola_tto_begin_date` timestamp NULL DEFAULT NULL, - `ola_ttr_begin_date` timestamp NULL DEFAULT NULL, - `internal_time_to_resolve` timestamp NULL DEFAULT NULL, - `internal_time_to_own` timestamp NULL DEFAULT NULL, `waiting_duration` int NOT NULL DEFAULT '0', `close_delay_stat` int NOT NULL DEFAULT '0', `solve_delay_stat` int NOT NULL DEFAULT '0', @@ -7567,11 +7581,7 @@ CREATE TABLE `glpi_tickets` ( KEY `slas_id_ttr` (`slas_id_ttr`), KEY `time_to_resolve` (`time_to_resolve`), KEY `time_to_own` (`time_to_own`), - KEY `olas_id_tto` (`olas_id_tto`), - KEY `olas_id_ttr` (`olas_id_ttr`), KEY `slalevels_id_ttr` (`slalevels_id_ttr`), - KEY `internal_time_to_resolve` (`internal_time_to_resolve`), - KEY `internal_time_to_own` (`internal_time_to_own`), KEY `users_id_lastupdater` (`users_id_lastupdater`), KEY `type` (`type`), KEY `itilcategories_id` (`itilcategories_id`), @@ -7579,8 +7589,6 @@ CREATE TABLE `glpi_tickets` ( KEY `name` (`name`), KEY `locations_id` (`locations_id`), KEY `date_creation` (`date_creation`), - KEY `ola_waiting_duration` (`ola_waiting_duration`), - KEY `olalevels_id_ttr` (`olalevels_id_ttr`), KEY `tickettemplates_id` (`tickettemplates_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; @@ -10510,7 +10518,8 @@ CREATE TABLE `glpi_sharetokens` ( `itemtype` varchar(255) NOT NULL, `items_id` int unsigned NOT NULL DEFAULT '0', `name` varchar(255) DEFAULT NULL, - `token` varchar(64) NOT NULL, + `token` varchar(255) NOT NULL, + `token_hint` varchar(16) NOT NULL DEFAULT '', `is_active` tinyint NOT NULL DEFAULT '1', `users_id` int unsigned NOT NULL DEFAULT '0', `date_creation` timestamp NULL DEFAULT NULL, @@ -10518,6 +10527,7 @@ CREATE TABLE `glpi_sharetokens` ( `date_expiration` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `token` (`token`), + KEY `token_hint` (`token_hint`), KEY `item` (`itemtype`, `items_id`), KEY `name` (`name`), KEY `is_active` (`is_active`), diff --git a/package-lock.json b/package-lock.json index c2a675111d8..838154caca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10351,9 +10351,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.4.tgz", - "integrity": "sha512-G3iTQw1DizJQ5eEqj1CbFCWhq+pzum7qepkxU7rS1FGZDqjYKcrguo9XDRbV7EgPnn8CgaPigTq+NEjyioeYZQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -10364,7 +10364,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", diff --git a/rector.php b/rector.php index 9d71976fb8e..aac4a2097e4 100644 --- a/rector.php +++ b/rector.php @@ -41,6 +41,7 @@ use RectorGlpi\Set\GlpiSetList; return RectorConfig::configure() + ->withImportNames(removeUnusedImports: true) ->withSets([ GlpiSetList::GLPI_DEFAULT_SET, ]) diff --git a/src/Auth.php b/src/Auth.php index ab82c188129..1e03abe6147 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -1111,6 +1111,12 @@ public function login($login_name, $login_password, $noauto = false, $remember_m // GET THE IP OF THE CLIENT $ip = IPUtilities::getClientIP(); + // For external auth (e.g. OAuth SSO), $login_name is empty because + // it is resolved inside validateLogin(). Use the resolved user name. + if (empty($login_name) && !empty($this->user->fields['name'])) { + $login_name = $this->user->fields['name']; + } + if ($this->auth_succeded) { //TRANS: %1$s is the login of the user and %2$s its IP address Event::log(0, "system", 3, "login", sprintf( diff --git a/src/Change.php b/src/Change.php index 87640e5f8c1..08cfbb02c0d 100644 --- a/src/Change.php +++ b/src/Change.php @@ -283,9 +283,6 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ case 1: $item->showStats(); break; - case 3: - self::showSatisfactionTabContent($item); - break; } break; diff --git a/src/CommonITILObject.php b/src/CommonITILObject.php index dc8751536df..62f057bc991 100644 --- a/src/CommonITILObject.php +++ b/src/CommonITILObject.php @@ -689,6 +689,7 @@ public function showForm($ID, array $options = []) 'canassign' => $canupdate, 'can_requester' => $this->canRequesterUpdateItem(), 'has_pending_reason' => PendingReason_Item::getForItem($this) !== false, + 'survey' => $this->getSatisfactionSurvey(), ]); return true; @@ -735,6 +736,7 @@ protected function setPredefinedFields(ITILTemplate $tt, array &$options, array 'priority', 'time_to_resolve', 'entities_id', + '_olas_id', ]; foreach ($fields as $field) { if (!isset($options['_saved'][$field])) { @@ -784,6 +786,7 @@ protected function setPredefinedFields(ITILTemplate $tt, array &$options, array if ($this->isNewItem()) { if (count($tt->predefined)) { + // apply predefined fields foreach ($tt->predefined as $predeffield => $predefvalue) { if (isset($options[$predeffield]) && isset($default_values[$predeffield])) { // Is always default value : not set @@ -808,6 +811,16 @@ protected function setPredefinedFields(ITILTemplate $tt, array &$options, array || ($problems_id != null && $options[$predeffield] == $problem->fields[$predeffield]) ) { + // predefined fields _olas_id_tto, _olas_id_ttr should be merged in _olas_id + if (in_array($predeffield, ['_olas_id_tto', '_olas_id_ttr'])) { + $to_merge = is_array($predefvalue) ? $predefvalue : [$predefvalue]; + $options['_olas_id'] = array_merge($options['_olas_id'] ?? [], $to_merge); + $predefined_fields['_olas_id'] = array_merge($predefined_fields['_olas_id'] ?? [], $to_merge); + $this->fields['_olas_id'] = array_merge($this->fields['_olas_id'] ?? [], $to_merge); + + continue; + } + $options[$predeffield] = $predefvalue; $predefined_fields[$predeffield] = $predefvalue; $this->fields[$predeffield] = $predefvalue; @@ -1321,9 +1334,9 @@ public function getUsers($type) /** - * get groups linked to a object + * Get linked groups * - * @param int $type type to search (see constants) + * @param int $type e.g.CommonITILActor::REQUESTER, etc * * @return array **/ @@ -1811,7 +1824,7 @@ protected function handleTemplateFields(array $input, bool $show_error_message = // Closed tickets || in_array($this->fields['status'], static::getClosedStatusArray())) ) { - $allowed_fields = ['id']; + $allowed_fields = ['id', '_olas_id_ttr', '_olas_id_tto']; $check_allowed_fields_for_template = true; if (in_array($this->fields['status'], static::getClosedStatusArray())) { @@ -1892,10 +1905,25 @@ class_exists($validation_class) } } + // If category, entity, or type fields are not updated, the template is not changed. + if ( + (empty($input['itilcategories_id']) || $this->fields['itilcategories_id'] == $input['itilcategories_id']) + && (empty($input['entities_id']) || $this->fields['entities_id'] == $input['entities_id']) + && (empty($input['type']) || $this->fields['type'] == $input['type']) + ) { + return $input; + } + + [SLM::TTO => $input['_olas_id_tto'], SLM::TTR => $input['_olas_id_ttr']] = OLA::splitIdsByType($input['_olas_id'] ?? []); + + + [SLM::TTO => $input['_olas_id_tto'], SLM::TTR => $input['_olas_id_ttr']] = OLA::splitIdsByType($input['_olas_id'] ?? []); + // First get ticket template associated: entity and type/category $tt = $this->getITILTemplateFromInput($input); - if (!$tt) { + // If no template or template not found, return input without template fields + if (!$tt || $tt->getID() <= 0) { return $input; } @@ -2011,6 +2039,8 @@ protected function manageITILObjectLinkInput($input) public function prepareInputForUpdate($input) { +// $input = $this->handleInputDeprecations($input); @todo a remettre ? - a sauter lors du merge + if (!$this->checkFieldsConsistency($input)) { return false; } @@ -2468,6 +2498,7 @@ public function pre_updateInDB() unset($this->oldvalues['date']); } + // do not update closedate fields if not changed if ( (($key = array_search('closedate', $this->updates)) !== false) && isset($this->oldvalues['closedate']) @@ -2477,6 +2508,7 @@ public function pre_updateInDB() unset($this->oldvalues['closedate']); } + // do not update time_to_resolve if date is not changed if ( (($key = array_search('time_to_resolve', $this->updates)) !== false) && isset($this->oldvalues['time_to_resolve']) @@ -2486,6 +2518,7 @@ public function pre_updateInDB() unset($this->oldvalues['time_to_resolve']); } + // do not update solvedate if date is not changed if ( (($key = array_search('solvedate', $this->updates)) !== false) && isset($this->oldvalues['solvedate']) @@ -2495,6 +2528,7 @@ public function pre_updateInDB() unset($this->oldvalues['solvedate']); } + // status update if (isset($this->input["status"])) { // status changed to solved if ( @@ -2533,13 +2567,13 @@ public function pre_updateInDB() // check dates - // check time_to_resolve (SLA) + // unset date and time_to_resolve time_to_resolve is before 'date' time. if ( (in_array("date", $this->updates) || in_array("time_to_resolve", $this->updates)) && !is_null($this->fields["time_to_resolve"]) ) { // Date set if ($this->fields["time_to_resolve"] < $this->fields["date"]) { - Session::addMessageAfterRedirect(__s('Invalid dates. Update cancelled.'), false, ERROR); + Session::addMessageAfterRedirect(__s("Invalid dates, computed ttr is before 'date'. Update cancelled."), false, ERROR); if (($key = array_search('date', $this->updates)) !== false) { unset($this->updates[$key]); @@ -2552,32 +2586,13 @@ public function pre_updateInDB() } } - // check internal_time_to_resolve (OLA) - if ( - (in_array("date", $this->updates) || in_array("internal_time_to_resolve", $this->updates)) - && !is_null($this->fields["internal_time_to_resolve"]) - ) { // Date set - if ($this->fields["internal_time_to_resolve"] < $this->fields["date"]) { - Session::addMessageAfterRedirect(__s('Invalid dates. Update cancelled.'), false, ERROR); - - if (($key = array_search('date', $this->updates)) !== false) { - unset($this->updates[$key]); - unset($this->oldvalues['date']); - } - if (($key = array_search('internal_time_to_resolve', $this->updates)) !== false) { - unset($this->updates[$key]); - unset($this->oldvalues['internal_time_to_resolve']); - } - } - } - - // Status close: check dates + // Unset closedate if before solvedate + // unset 'date' and unset 'closedate' if closedate is before 'date' if ( in_array($this->fields["status"], static::getClosedStatusArray()) && (in_array("date", $this->updates) || in_array("closedate", $this->updates)) ) { - // Invalid dates : no change - // closedate must be > solvedate + // Unset closedate if before solvedate if ($this->fields["closedate"] < $this->fields["solvedate"]) { Session::addMessageAfterRedirect(__s('Invalid dates. Update cancelled.'), false, ERROR); @@ -2587,7 +2602,7 @@ public function pre_updateInDB() } } - // closedate must be > create date + // unset 'date' and unset 'closedate' if closedate is before 'date' if ($this->fields["closedate"] < $this->fields["date"]) { Session::addMessageAfterRedirect(__s('Invalid dates. Update cancelled.'), false, ERROR); if (($key = array_search('date', $this->updates)) !== false) { @@ -2601,6 +2616,7 @@ public function pre_updateInDB() } } + // unset 'status' if unchanged if ( (($key = array_search('status', $this->updates)) !== false) && $this->oldvalues['status'] == $this->fields['status'] @@ -2609,7 +2625,7 @@ public function pre_updateInDB() unset($this->oldvalues['status']); } - // Status solved: check dates + // unset solvedate and date if solvedate is before 'date' time. if ( in_array($this->fields["status"], static::getSolvedStatusArray()) && (in_array("date", $this->updates) || in_array("solvedate", $this->updates)) @@ -2630,7 +2646,14 @@ public function pre_updateInDB() } } - // Manage come back to waiting state + // OLA / SLA tto/ttr computations + // if + // - begin_waiting_date is set + // - and status is updated + // - and previous status was 'waiting' + // - and + // - status is switching from any 'solved' status to any 'closed' status + // - or status is switching from any 'closed' status to any 'not solved' status if ( !is_null($this->fields['begin_waiting_date']) && ($key = array_search('status', $this->updates)) !== false @@ -2651,7 +2674,6 @@ public function pre_updateInDB() // Compute ticket waiting time use calendar if exists $calendar = new Calendar(); $calendars_id = $this->getCalendar(); - $delay_time = 0; // Compute ticket waiting time use calendar if exists // Using calendar @@ -2669,8 +2691,10 @@ public function pre_updateInDB() } // SLA case: compute sla_ttr duration + // associated sla if (isset($this->fields['slas_id_ttr']) && ($this->fields['slas_id_ttr'] > 0)) { $sla = new SLA(); + // Compute begin_waiting_date if ($sla->getFromDB($this->fields['slas_id_ttr'])) { $sla->setTicketCalendar($calendars_id); $delay_time_sla = $sla->getActiveTimeBetween( @@ -2691,7 +2715,7 @@ public function pre_updateInDB() if ($this instanceof Ticket) { // TODO: rewrite with polymorphism... $sla->addLevelToDo($this); } - } else { + } else { // sla set by date // Using calendar if ( ($calendars_id > 0) @@ -2718,58 +2742,7 @@ public function pre_updateInDB() } } - // OLA case: compute ola_ttr duration - if (isset($this->fields['olas_id_ttr']) && ($this->fields['olas_id_ttr'] > 0)) { - $ola = new OLA(); - if ($ola->getFromDB($this->fields['olas_id_ttr'])) { - $ola->setTicketCalendar($calendars_id); - $delay_time_ola = $ola->getActiveTimeBetween( - $this->fields['begin_waiting_date'], - $_SESSION["glpi_currenttime"] - ); - $this->updates[] = "ola_waiting_duration"; - $this->fields["ola_waiting_duration"] += $delay_time_ola; - } - - // Compute new internal_time_to_resolve - $this->updates[] = "internal_time_to_resolve"; - $this->fields['internal_time_to_resolve'] = $ola->computeDate( - $this->fields['ola_ttr_begin_date'], - $this->fields["ola_waiting_duration"] - ); - // Add current level to do - if ($this instanceof Ticket) { // TODO: rewrite with polymorphism... - $ola->addLevelToDo($this, $this->fields["olalevels_id_ttr"]); - } - } elseif (array_key_exists("internal_time_to_resolve", $this->fields)) { - // Change doesn't have internal_time_to_resolve - // Using calendar - if ( - ($calendars_id > 0) - && $calendar->getFromDB($calendars_id) - && $calendar->hasAWorkingDay() - ) { - if ((int) $this->fields['internal_time_to_resolve'] > 0) { - // compute new internal_time_to_resolve using calendar - $this->updates[] = "internal_time_to_resolve"; - $this->fields['internal_time_to_resolve'] = $calendar->computeEndDate( - $this->fields['internal_time_to_resolve'], - $delay_time - ); - } - } else { // Not calendar defined - if ((int) $this->fields['internal_time_to_resolve'] > 0) { - // compute new internal_time_to_resolve: no calendar so add computed delay_time - $this->updates[] = "internal_time_to_resolve"; - $this->fields['internal_time_to_resolve'] = date( - 'Y-m-d H:i:s', - $delay_time - + strtotime($this->fields['internal_time_to_resolve']) - ); - } - } - } - + // --- OLA case is handled in Ticket::recomputeOlas $this->updates[] = "waiting_duration"; $this->fields["waiting_duration"] += $delay_time; @@ -2779,6 +2752,7 @@ public function pre_updateInDB() } // Set begin waiting date if needed + // handle levels if ( (($key = array_search('status', $this->updates)) !== false) && (($this->fields['status'] == self::WAITING) @@ -2793,9 +2767,7 @@ public function pre_updateInDB() SLA::deleteLevelsToDo($this); } - if (isset($this->fields['olas_id_ttr']) && ($this->fields['olas_id_ttr'] > 0)) { - OLA::deleteLevelsToDo($this); - } + OLA::deleteLevelsToDo($this); } } @@ -2989,6 +2961,9 @@ public function prepareInputForAdd($input) $input = $this->computeDefaultValuesForAdd($input); + // split OLA ids by type to allow mandatory check + [SLM::TTO => $input['_olas_id_tto'], SLM::TTR => $input['_olas_id_ttr']] = OLA::splitIdsByType($input['_olas_id'] ?? []); + // Do not check mandatory on auto import (mailgates) $key = static::getTemplateFormFieldName(); if (!isset($input['_auto_import'])) { @@ -3060,7 +3035,6 @@ public function prepareInputForAdd($input) if (static::class === Ticket::class) { // For time_to_resolve and time_to_own : check also slas - // For internal_time_to_resolve and internal_time_to_own : check also olas foreach ([SLM::TTR, SLM::TTO] as $slmType) { [$dateField, $slaField] = SLA::getFieldNames($slmType); if ( @@ -3070,14 +3044,6 @@ public function prepareInputForAdd($input) ) { unset($mandatory_missing[$dateField]); } - [$dateField, $olaField] = OLA::getFieldNames($slmType); - if ( - ($key == $dateField) - && isset($input[$olaField]) && ($input[$olaField] > 0) - && isset($mandatory_missing[$dateField]) - ) { - unset($mandatory_missing[$dateField]); - } } } @@ -5118,7 +5084,11 @@ public function getSearchOptionsActors() } /** - * @param string $type + * Generate an 'if' QueryExpression condition to match ticket with exceeded OLA/SLA TTO/TTR + * + * internal_time_to_own & internal_time_to_resolve fields are removed from ticket but can still be used here. + * + * @param string $type ticket field to match ('time_to_own', 'internal_time_to_own', 'time_to_resolve', 'internal_time_to_resolve') * @param string $table * @return QueryExpression|void */ @@ -5127,7 +5097,24 @@ public static function generateSLAOLAComputation($type, $table = "TABLE") global $DB; switch ($type) { + // OLA TTO/TTR uses the same logic, we rely on item_ola datas case 'internal_time_to_own': + $ola_type_to_filter = SLM::TTO; + // no break + case 'internal_time_to_resolve': + $ola_type_to_filter ??= SLM::TTR; + + // QueryFunction::max is used to match a late ola amongst associated OLAs + return QueryFunction::max( + QueryFunction::if( + condition: [ + "{$table}.ola_type" => $ola_type_to_filter, + "$table.is_late" => 1, + ], + true_expression: new QueryExpression('1'), + false_expression: new QueryExpression('0') + ) + ); case 'time_to_own': return QueryFunction::if( condition: [ @@ -5164,7 +5151,6 @@ public static function generateSLAOLAComputation($type, $table = "TABLE") false_expression: new QueryExpression('0') ); - case 'internal_time_to_resolve': case 'time_to_resolve': return QueryFunction::if( condition: [ @@ -11021,6 +11007,24 @@ public static function cronCreateInquest($task) return ($tot > 0 ? 1 : 0); } + /** + * Returns the satisfaction survey instance for the current item if it exists, null otherwise. + * + * @return CommonITILSatisfaction|null + */ + protected function getSatisfactionSurvey(): ?CommonITILSatisfaction + { + $satisfaction = static::getSatisfactionClassInstance(); + if ($satisfaction === null) { + return null; + } + $survey_exist = $satisfaction->getFromDBByCrit([ + static::getForeignKeyField() => $this->getID(), + ]); + + return $survey_exist ? $satisfaction : null; + } + /** * Returns the {@link CommonITILSatisfaction} class instance for the current itemtype * @return CommonITILSatisfaction|null @@ -11040,6 +11044,7 @@ public static function getSatisfactionClassInstance(): ?CommonITILSatisfaction * @param CommonITILObject $item The ITIL Object * @return void * @since 11.0.0 + * @TODO Remove this unused method in GLPI 12.0. */ final protected static function showSatisfactionTabContent(CommonITILObject $item): void { @@ -11406,6 +11411,9 @@ protected function processRules(int $condition, array &$input, int $entid = -1): if ($condition === RuleCommonITILObject::ONUPDATE) { $rules_params['entities_id'] = $entid; $changes = []; + if (isset($input['entities_id'])) { + $changes[] = 'entities_id'; + } foreach ($rule->getCriterias() as $key => $val) { if (array_key_exists($key, $input)) { if ( diff --git a/src/Document.php b/src/Document.php index 17fedebf043..09db840ffec 100644 --- a/src/Document.php +++ b/src/Document.php @@ -1686,6 +1686,10 @@ public static function isImage($file): bool */ public static function getResizedImagePath(string $path, int $width, int $height): string { + if ($width <= 0 || $height <= 0) { + return $path; + } + // let's see if original image needs resize $img_infos = getimagesize($path); if ($img_infos[0] <= $width && $img_infos[1] <= $height) { diff --git a/src/Entity.php b/src/Entity.php index 0607ea5fd8f..8de8d7c6b33 100644 --- a/src/Entity.php +++ b/src/Entity.php @@ -2977,6 +2977,8 @@ public static function inheritedValue($value = "", bool $inline = false, bool $d * @param string $field The field name * @param string|null $strategy_field The field name of the strategy * @param mixed $default_value + * @param mixed $inherit_parent_value The sentinel value stored in the field when it inherits from its parent. + * Use CONFIG_PARENT (-2) for numeric fields, or null for text/email/URL fields. * @return string|null The badge HTML or null if the field is not inherited */ public function getInheritedValueBadge(string $field, ?string $strategy_field = null, mixed $default_value = self::CONFIG_PARENT, mixed $inherit_parent_value = self::CONFIG_PARENT): ?string @@ -2989,7 +2991,9 @@ public function getInheritedValueBadge(string $field, ?string $strategy_field = if ($strategy_field === null) { $strategy_field = $field; } - $inherited_strategy = self::getUsedConfig($strategy_field, $this->fields['entities_id']); + // pass a non-numeric default ('') For text/null fields to recognize inherited values in ancestor entities. + $get_used_config_default = is_numeric($inherit_parent_value) ? $default_value : ''; + $inherited_strategy = self::getUsedConfig($strategy_field, $this->fields['entities_id'], '', $get_used_config_default); $inherited_value = $inherited_strategy === 0 ? self::getUsedConfig($strategy_field, $this->fields['entities_id'], $field, $default_value) : $inherited_strategy; diff --git a/src/GLPIKey.php b/src/GLPIKey.php index 772fbc065f8..6df42e0827e 100644 --- a/src/GLPIKey.php +++ b/src/GLPIKey.php @@ -71,6 +71,7 @@ class GLPIKey 'glpi_authldaps.rootdn_passwd', 'glpi_mailcollectors.passwd', 'glpi_oauthclients.secret', + 'glpi_sharetokens.token', 'glpi_snmpcredentials.auth_passphrase', 'glpi_snmpcredentials.priv_passphrase', 'glpi_users.api_token', diff --git a/src/Glpi/Api/HL/Controller/AbstractController.php b/src/Glpi/Api/HL/Controller/AbstractController.php index 3fb8af86371..f00372a7923 100644 --- a/src/Glpi/Api/HL/Controller/AbstractController.php +++ b/src/Glpi/Api/HL/Controller/AbstractController.php @@ -36,10 +36,12 @@ namespace Glpi\Api\HL\Controller; use CommonDBTM; +use Document; use Entity; use Glpi\Api\HL\Doc as Doc; use Glpi\Api\HL\RoutePath; use Glpi\Api\HL\Router; +use Glpi\Api\HL\StreamedResponseWrapper; use Glpi\Http\JSONResponse; use Glpi\Http\Request; use Glpi\Http\Response; @@ -47,6 +49,8 @@ use Plugin; use RuntimeException; use Session; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Toolbox; /** * @phpstan-type AdditionalErrorMessage array{priority: string, message: string} @@ -351,4 +355,37 @@ public static function getAPIPathForRouteFunction(string $controller, string $fu } return '/'; } + + /** + * Download a file using streaming. + * + * This method does not check permissions or validate the document in any way. + * + * If this method actually sends the file content (not a 304 FOUND or similar), the underlying {@link Toolbox::getFileAsResponse()} response would be a Symfony StreamedResponse, which is not PSR-7 compatible and cannot be processed by the HLAPI. + * To ensure it can be processed and the headers do not get sent too early, the StreamedResponse is wrapped with {@link StreamedResponseWrapper} which allows it to be treated as a normal PSR-7 Response until the HLAPI is ready to send the response. + * As the HLAPI ultimately converts PSR-7 responses to Symfony responses before sending, the underlying Symfony response will be sent directly instead of creating in a new Symfony Response. + * + * @param Request $request The request object + * @param string $filepath The file path + * @param string $filename The name of the file to be downloaded (used in the Content-Disposition header) + * @param string|null $mime The mime type of the file if known. If null, the mime type will be determined based on the file content. + * @return Response + * @internal This method is not intended to be overriden and should not be relied on too much. It should be replaced in GLPI 12 as part of a switch to Symfony requests/responses (if possible). + */ + protected function downloadFile(Request $request, string $filepath, string $filename, ?string $mime = null): Response + { + $is_head_request = $request->getMethod() === 'HEAD'; + $symfony_response = Toolbox::getFileAsResponse($filepath, $filename, $mime); + + if ($symfony_response instanceof StreamedResponse) { + if ($is_head_request) { + // we can return a normal PSR-7 response with the headers and status code, but without content + return new Response($symfony_response->getStatusCode(), $symfony_response->headers->all()); + } + return new StreamedResponseWrapper($symfony_response); + } else { + $content = $is_head_request ? null : (string) $symfony_response->getContent(); + return new Response($symfony_response->getStatusCode(), $symfony_response->headers->all(), $content); + } + } } diff --git a/src/Glpi/Api/HL/Controller/AdministrationController.php b/src/Glpi/Api/HL/Controller/AdministrationController.php index 3e9a026e2e3..f622dc9ddda 100644 --- a/src/Glpi/Api/HL/Controller/AdministrationController.php +++ b/src/Glpi/Api/HL/Controller/AdministrationController.php @@ -1349,16 +1349,14 @@ public function getMyDefaultEmail(Request $request): Response * @param string|null $picture_path The path to the picture from the user's "picture" field. * @return Response A response with the picture as binary content (or the placeholder user picture if the user has no picture). */ - private function getUserPictureResponse(string $username, ?string $picture_path): Response + private function getUserPictureResponse(Request $request, string $username, ?string $picture_path): Response { if ($picture_path !== null) { $picture_path = GLPI_PICTURE_DIR . '/' . $picture_path; } else { - $picture_path = 'public/pics/picture.png'; + $picture_path = GLPI_ROOT . '/public/pics/picture.png'; } - $symfony_response = Toolbox::getFileAsResponse($picture_path, $username); - - return new Response($symfony_response->getStatusCode(), $symfony_response->headers->all(), $symfony_response->getContent()); + return $this->downloadFile($request, $picture_path, $username); } #[Route(path: '/User/Me/Picture', methods: ['GET'], scopes: ['user'])] @@ -1377,7 +1375,7 @@ public function getMyPicture(Request $request): Response ], ]); $data = $it->current(); - return $this->getUserPictureResponse($data['name'], $data['picture']); + return $this->getUserPictureResponse($request, $data['name'], $data['picture']); } #[Route(path: '/User', methods: ['POST'])] @@ -1420,7 +1418,7 @@ public function getUserPictureByID(Request $request): Response ], ]); $data = $it->current(); - return $this->getUserPictureResponse($data['name'], $data['picture']); + return $this->getUserPictureResponse($request, $data['name'], $data['picture']); } #[Route(path: '/User/username/{username}/Picture', methods: ['GET'], requirements: ['username' => '[a-zA-Z0-9_]+'])] @@ -1439,7 +1437,7 @@ public function getUserPictureByUsername(Request $request): Response ], ]); $data = $it->current(); - return $this->getUserPictureResponse($data['name'], $data['picture']); + return $this->getUserPictureResponse($request, $data['name'], $data['picture']); } #[Route(path: '/User/{id}', methods: ['PATCH'], requirements: ['id' => '\d+'])] diff --git a/src/Glpi/Api/HL/Controller/ITILController.php b/src/Glpi/Api/HL/Controller/ITILController.php index ca524ed4352..c03c6006cd9 100644 --- a/src/Glpi/Api/HL/Controller/ITILController.php +++ b/src/Glpi/Api/HL/Controller/ITILController.php @@ -73,7 +73,6 @@ use ITILReminder; use ITILSolution; use Location; -use OLA; use OlaLevel; use PendingReason; use PendingReason_Item; @@ -447,46 +446,147 @@ public static function getRawKnownSchemas(): array ]; $schemas[$itil_type]['properties']['sla_ttr'] = self::getDropdownTypeSchema(class: SLA::class, field: 'slas_id_ttr', full_schema: 'SLA') + ['x-version-introduced' => '2.1.0']; $schemas[$itil_type]['properties']['sla_tto'] = self::getDropdownTypeSchema(class: SLA::class, field: 'slas_id_tto', full_schema: 'SLA') + ['x-version-introduced' => '2.1.0']; - $schemas[$itil_type]['properties']['ola_ttr'] = self::getDropdownTypeSchema(class: OLA::class, field: 'olas_id_ttr', full_schema: 'OLA') + ['x-version-introduced' => '2.1.0']; - $schemas[$itil_type]['properties']['ola_tto'] = self::getDropdownTypeSchema(class: OLA::class, field: 'olas_id_tto', full_schema: 'OLA') + ['x-version-introduced' => '2.1.0']; $schemas[$itil_type]['properties']['sla_level_ttr'] = self::getDropdownTypeSchema(class: SlaLevel::class, field: 'slalevels_id_ttr', full_schema: 'SLALevel') + ['x-version-introduced' => '2.1.0']; - $schemas[$itil_type]['properties']['ola_level_ttr'] = self::getDropdownTypeSchema(class: OlaLevel::class, field: 'olalevels_id_ttr', full_schema: 'OLALevel') + ['x-version-introduced' => '2.1.0']; $schemas[$itil_type]['properties']['sla_waiting_duration'] = [ 'x-version-introduced' => '2.1.0', 'type' => Doc\Schema::TYPE_INTEGER, 'readOnly' => true, 'description' => 'Total SLA waiting duration in seconds', ]; + + // OLA fields: since OLAs are now stored in the items_ola junction table + // (no more direct olas_id_ttr / olas_id_tto FK on the ticket), we use + // x-mapper to read the first TTR / first TTO OLA for backward compatibility. + // + // A per-request static cache avoids repeated getById + getOlas*Data() calls + // when multiple OLA fields are resolved for the same ticket during a list. + $ola_data_cache = []; + $get_ola_data = static function (int $ticket_id, int $ola_type) use (&$ola_data_cache): ?array { + $cache_key = $ticket_id . '_' . $ola_type; + if (!array_key_exists($cache_key, $ola_data_cache)) { + $ticket = Ticket::getById($ticket_id); + if (!$ticket instanceof Ticket) { + $ola_data_cache[$cache_key] = null; + } else { + $olas = $ola_type === \SLM::TTR + ? $ticket->getOlasTTRData() + : $ticket->getOlasTTOData(); + $ola_data_cache[$cache_key] = $olas !== [] ? $olas[0] : null; + } + } + return $ola_data_cache[$cache_key]; + }; + + $ola_dropdown_properties = [ + 'type' => Doc\Schema::TYPE_OBJECT, + 'nullable' => true, + 'x-full-schema' => 'OLA', + 'x-mapped-from' => 'id', + 'properties' => [ + 'id' => ['type' => Doc\Schema::TYPE_INTEGER, 'format' => Doc\Schema::FORMAT_INTEGER_INT64], + 'name' => ['type' => Doc\Schema::TYPE_STRING, 'readOnly' => true], + ], + ]; + $schemas[$itil_type]['properties']['ola_ttr'] = $ola_dropdown_properties + [ + 'x-version-introduced' => '2.1.0', + 'readOnly' => true, + 'x-mapper' => static function ($v) use ($get_ola_data): array { + $ola = $get_ola_data((int) $v, \SLM::TTR); + return $ola !== null ? ['id' => $ola['olas_id'], 'name' => $ola['name']] : []; + }, + ]; + $schemas[$itil_type]['properties']['ola_tto'] = $ola_dropdown_properties + [ + 'x-version-introduced' => '2.1.0', + 'readOnly' => true, + 'x-mapper' => static function ($v) use ($get_ola_data): array { + $ola = $get_ola_data((int) $v, \SLM::TTO); + return $ola !== null ? ['id' => $ola['olas_id'], 'name' => $ola['name']] : []; + }, + ]; + + // OLA level for TTR (equivalent of sla_level_ttr) + $schemas[$itil_type]['properties']['ola_level_ttr'] = [ + 'x-version-introduced' => '2.1.0', + 'type' => Doc\Schema::TYPE_OBJECT, + 'nullable' => true, + 'x-full-schema' => 'OLALevel', + 'x-mapped-from' => 'id', + 'properties' => [ + 'id' => ['type' => Doc\Schema::TYPE_INTEGER, 'format' => Doc\Schema::FORMAT_INTEGER_INT64], + 'name' => ['type' => Doc\Schema::TYPE_STRING, 'readOnly' => true], + ], + 'readOnly' => true, + 'x-mapper' => static function ($v) use ($get_ola_data): ?array { + $ola = $get_ola_data((int) $v, \SLM::TTR); + if ($ola === null || empty($ola['level']) || !($ola['level'] instanceof OlaLevel)) { + return null; + } + return ['id' => $ola['level']->getID(), 'name' => $ola['level']->getName()]; + }, + ]; + + // OLA waiting duration (sum of TTR + TTO waiting times; replaces old single ola_waiting_duration column) $schemas[$itil_type]['properties']['ola_waiting_duration'] = [ 'x-version-introduced' => '2.1.0', 'type' => Doc\Schema::TYPE_INTEGER, 'readOnly' => true, 'description' => 'Total OLA waiting duration in seconds', + 'x-mapped-from' => 'id', + 'x-mapper' => static function ($v) use ($get_ola_data): int { + $ttr = $get_ola_data((int) $v, \SLM::TTR); + $tto = $get_ola_data((int) $v, \SLM::TTO); + return (int) ($ttr['waiting_time'] ?? 0) + (int) ($tto['waiting_time'] ?? 0); + }, ]; + + // OLA begin dates (replacing ola_ttr_begin_date / ola_tto_begin_date columns) $schemas[$itil_type]['properties']['ola_ttr_begin_date'] = [ 'x-version-introduced' => '2.1.0', 'type' => Doc\Schema::TYPE_STRING, - 'readOnly' => true, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, + 'readOnly' => true, + 'x-mapped-from' => 'id', + 'x-mapper' => static function ($v) use ($get_ola_data): ?string { + $ola = $get_ola_data((int) $v, \SLM::TTR); + return ($ola !== null && !empty($ola['start_time'])) ? $ola['start_time'] : null; + }, ]; $schemas[$itil_type]['properties']['ola_tto_begin_date'] = [ 'x-version-introduced' => '2.1.0', 'type' => Doc\Schema::TYPE_STRING, - 'readOnly' => true, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, + 'readOnly' => true, + 'x-mapped-from' => 'id', + 'x-mapper' => static function ($v) use ($get_ola_data): ?string { + $ola = $get_ola_data((int) $v, \SLM::TTO); + return ($ola !== null && !empty($ola['start_time'])) ? $ola['start_time'] : null; + }, ]; + + // OLA due dates (replacing internal_time_to_resolve / internal_time_to_own columns) $schemas[$itil_type]['properties']['internal_resolution_date'] = [ 'x-version-introduced' => '2.1.0', 'type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, - 'x-field' => 'internal_time_to_resolve', + 'readOnly' => true, + 'x-mapped-from' => 'id', + 'x-mapper' => static function ($v) use ($get_ola_data): ?string { + $ola = $get_ola_data((int) $v, \SLM::TTR); + return ($ola !== null && !empty($ola['due_time'])) ? $ola['due_time'] : null; + }, ]; $schemas[$itil_type]['properties']['internal_take_into_account_date'] = [ 'x-version-introduced' => '2.1.0', 'type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, - 'x-field' => 'internal_time_to_own', + 'readOnly' => true, + 'x-mapped-from' => 'id', + 'x-mapper' => static function ($v) use ($get_ola_data): ?string { + $ola = $get_ola_data((int) $v, \SLM::TTO); + return ($ola !== null && !empty($ola['due_time'])) ? $ola['due_time'] : null; + }, ]; + } if ($itil_type === Ticket::class || $itil_type === Change::class) { $schemas[$itil_type]['properties']['global_validation'] = [ diff --git a/src/Glpi/Api/HL/Controller/ManagementController.php b/src/Glpi/Api/HL/Controller/ManagementController.php index d4f211fa755..ef6ee3d89c6 100644 --- a/src/Glpi/Api/HL/Controller/ManagementController.php +++ b/src/Glpi/Api/HL/Controller/ManagementController.php @@ -1206,8 +1206,7 @@ public function downloadDocument(Request $request): Response $document = new Document(); if ($document->getFromDB($request->getAttribute('id'))) { if ($document->canViewFile()) { - $symfony_response = $document->getAsResponse(); - return new Response($symfony_response->getStatusCode(), $symfony_response->headers->all(), $symfony_response->getContent()); + return $this->downloadFile($request, GLPI_DOC_DIR . "/" . $document->fields['filepath'], $document->fields['filename'], $document->fields['mime']); } return self::getAccessDeniedErrorResponse(); } diff --git a/src/Glpi/Api/HL/Controller/SetupController.php b/src/Glpi/Api/HL/Controller/SetupController.php index b3d6eeb1a64..ce6117ddd76 100644 --- a/src/Glpi/Api/HL/Controller/SetupController.php +++ b/src/Glpi/Api/HL/Controller/SetupController.php @@ -265,7 +265,7 @@ public static function getRawKnownSchemas(): array 'x-version-introduced' => '2.1.0', 'x-itemtype' => OLA::class, 'type' => Doc\Schema::TYPE_OBJECT, - 'properties' => $base_la_properties, + 'properties' => ['group' => ['x-version-introduced' => '2.4.0'] + self::getDropdownTypeSchema(class: \Group::class, full_schema: 'Group')] + $base_la_properties, ], 'SLALevel' => [ 'x-version-introduced' => '2.1.0', diff --git a/src/Glpi/Api/HL/Router.php b/src/Glpi/Api/HL/Router.php index 428270b6b4b..aac627cfede 100644 --- a/src/Glpi/Api/HL/Router.php +++ b/src/Glpi/Api/HL/Router.php @@ -95,7 +95,7 @@ class Router { /** @var string */ - public const API_VERSION = '2.3.0'; + public const API_VERSION = '2.4.0'; /** * @var AbstractController[] diff --git a/src/Glpi/Api/HL/Search.php b/src/Glpi/Api/HL/Search.php index a646314f3c4..8c3ad879e6c 100644 --- a/src/Glpi/Api/HL/Search.php +++ b/src/Glpi/Api/HL/Search.php @@ -890,6 +890,10 @@ public static function getSearchResultsBySchema(array $schema, array $request_pa } } foreach ($mapped_objs as $path => $data) { + if ($data === null) { + ArrayPathAccessor::setElementByArrayPath($result, $path, null); + continue; + } $existing_data = ArrayPathAccessor::getElementByArrayPath($result, $path) ?? []; /** @noinspection SlowArrayOperationsInLoopInspection */ $data = array_merge($existing_data, $data); diff --git a/src/Glpi/Api/HL/StreamedResponseWrapper.php b/src/Glpi/Api/HL/StreamedResponseWrapper.php new file mode 100644 index 00000000000..1ff9753114b --- /dev/null +++ b/src/Glpi/Api/HL/StreamedResponseWrapper.php @@ -0,0 +1,71 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Api\HL; + +use Glpi\Http\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * This class wraps a Symfony {@link StreamedResponse} to allow it to be used in the API, which expects a PSR-7 response. + * + * @internal Intended to be removed in the future, when the API requests/responses use Symfony's classes which aren't PSR-7 compatible. + */ +final class StreamedResponseWrapper extends Response +{ + public function __construct( + private StreamedResponse $symfony_response + ) { + parent::__construct( + $symfony_response->getStatusCode(), + $symfony_response->headers->all(), + '', // The content is always returned as 'false' in a StreamedResponse, so we set it to an empty string here. + ); + } + + /** + * Get the underlying Symfony StreamedResponse with some of its data synced with the PSR-7 response. + * @return StreamedResponse + */ + public function getSymfonyResponse(): StreamedResponse + { + $this->symfony_response->setStatusCode($this->getStatusCode()); + foreach ($this->getHeaders() as $header => $values) { + foreach ($values as $value) { + $this->symfony_response->headers->set($header, $value); + } + } + return $this->symfony_response; + } +} diff --git a/src/Glpi/Asset/Capacity/IsInventoriableCapacity.php b/src/Glpi/Asset/Capacity/IsInventoriableCapacity.php index c994e5a0684..9b35d487601 100644 --- a/src/Glpi/Asset/Capacity/IsInventoriableCapacity.php +++ b/src/Glpi/Asset/Capacity/IsInventoriableCapacity.php @@ -49,6 +49,7 @@ use Override; use Printer; use RuleImportAsset; +use RuleMatchedLog; use Session; class IsInventoriableCapacity extends AbstractCapacity @@ -143,6 +144,7 @@ public function onClassBootstrap(string $classname, CapacityConfig $config): voi CommonGLPI::registerStandardTab($classname, Item_Environment::class, 85); CommonGLPI::registerStandardTab($classname, Item_Process::class, 85); + CommonGLPI::registerStandardTab($classname, RuleMatchedLog::class, 90); } public function onCapacityEnabled(string $classname, CapacityConfig $config): void diff --git a/src/Glpi/ContentTemplates/Parameters/OLAParameters.php b/src/Glpi/ContentTemplates/Parameters/OLAParameters.php index 3c0b982bcaa..2c5c4e8aa79 100644 --- a/src/Glpi/ContentTemplates/Parameters/OLAParameters.php +++ b/src/Glpi/ContentTemplates/Parameters/OLAParameters.php @@ -35,6 +35,7 @@ namespace Glpi\ContentTemplates\Parameters; +use Glpi\ContentTemplates\Parameters\ParametersTypes\ObjectParameter; use OLA; /** @@ -44,6 +45,11 @@ */ class OLAParameters extends LevelAgreementParameters { + public function getAvailableParameters(): array + { + return parent::getAvailableParameters() + [new ObjectParameter(new GroupParameters()),]; + } + public static function getDefaultNodeName(): string { return 'ola'; diff --git a/src/Glpi/ContentTemplates/Parameters/TicketParameters.php b/src/Glpi/ContentTemplates/Parameters/TicketParameters.php index d3848073c0b..c67f47dff7f 100644 --- a/src/Glpi/ContentTemplates/Parameters/TicketParameters.php +++ b/src/Glpi/ContentTemplates/Parameters/TicketParameters.php @@ -39,14 +39,17 @@ use Glpi\ContentTemplates\Parameters\ParametersTypes\ArrayParameter; use Glpi\ContentTemplates\Parameters\ParametersTypes\AttributeParameter; use Glpi\ContentTemplates\Parameters\ParametersTypes\ObjectParameter; +use Item_Ola; use Item_Ticket; use KnowbaseItem; use KnowbaseItem_Item; use Location; +use LogicException; use OLA; use RequestType; use Session; use SLA; +use SLM; use Ticket; use TicketValidation; @@ -81,8 +84,8 @@ public function getAvailableParameters(): array new AttributeParameter("ttr", __('Time to resolve'), 'date("d/m/y H:i")'), new ObjectParameter(new SLAParameters(), 'sla_tto'), new ObjectParameter(new SLAParameters(), 'sla_ttr'), - new ObjectParameter(new OLAParameters(), 'ola_tto'), - new ObjectParameter(new OLAParameters(), 'ola_ttr'), + new ArrayParameter('olas_tto', new OLAParameters(), __('OLA TTO')), + new ArrayParameter('olas_ttr', new OLAParameters(), __('OLA TTR')), new ObjectParameter(new RequestTypeParameters()), new ObjectParameter(new LocationParameters()), new ArrayParameter("knowbaseitems", new KnowbaseItemParameters(), KnowbaseItem_Item::getTypeName(Session::getPluralNumber())), @@ -95,7 +98,6 @@ protected function defineValues(CommonDBTM $ticket): array global $CFG_GLPI; $fields = $ticket->fields; - $values = parent::defineValues($ticket); /** @var Ticket $ticket */ @@ -104,7 +106,7 @@ protected function defineValues(CommonDBTM $ticket): array $values['tto'] = $fields['time_to_own']; $values['ttr'] = $fields['time_to_resolve']; - // Add ticket's SLA / OLA + // Add ticket's SLA $sla_parameters = new SLAParameters(); if ($sla = SLA::getById($fields['slas_id_tto'])) { $values['sla_tto'] = $sla_parameters->getValues($sla); @@ -112,12 +114,24 @@ protected function defineValues(CommonDBTM $ticket): array if ($sla = SLA::getById($fields['slas_id_ttr'])) { $values['sla_ttr'] = $sla_parameters->getValues($sla); } + + // Add ticket's OLA $ola_parameters = new OLAParameters(); - if ($ola = OLA::getById($fields['olas_id_tto'])) { - $values['ola_tto'] = $ola_parameters->getValues($ola); - } - if ($ola = OLA::getById($fields['olas_id_ttr'])) { - $values['ola_ttr'] = $ola_parameters->getValues($ola); + + $values['olas_tto'] = []; + $values['olas_ttr'] = []; + + $ticket_olas = (new Item_Ola())->find(['itemtype' => Ticket::class, 'items_id' => $ticket->getID()]); + foreach ($ticket_olas as $ticket_ola_data) { + $key = match ($ticket_ola_data['ola_type']) { + SLM::TTO => 'olas_tto', + SLM::TTR => 'olas_ttr', + default => throw new LogicException(), + }; + + if ($ola = OLA::getById($ticket_ola_data['olas_id'])) { + $values[$key][] = $ola_parameters->getValues($ola); + } } // Add ticket's request type diff --git a/src/Glpi/Controller/ApiController.php b/src/Glpi/Controller/ApiController.php index d420b9d9a38..d851b477639 100644 --- a/src/Glpi/Controller/ApiController.php +++ b/src/Glpi/Controller/ApiController.php @@ -37,6 +37,7 @@ use Glpi\Api\APIRest; use Glpi\Api\HL\Controller\AbstractController as ApiAbstractController; use Glpi\Api\HL\Router; +use Glpi\Api\HL\StreamedResponseWrapper; use Glpi\Error\ErrorHandler; use Glpi\Http\HeaderlessStreamedResponse; use Glpi\Http\JSONResponse; @@ -109,6 +110,10 @@ public function __invoke(SymfonyRequest $request): SymfonyResponse $response = new JSONResponse(null, 500); } + if ($response instanceof StreamedResponseWrapper) { + return $response->getSymfonyResponse(); + } + return new SymfonyResponse( (string) $response->getBody(), $response->getStatusCode(), diff --git a/src/Glpi/Controller/ShareTokenController.php b/src/Glpi/Controller/ShareTokenController.php index 37152430460..2c02322f2ec 100644 --- a/src/Glpi/Controller/ShareTokenController.php +++ b/src/Glpi/Controller/ShareTokenController.php @@ -37,6 +37,7 @@ use Glpi\Exception\Http\AccessDeniedHttpException; use Glpi\Exception\Http\BadRequestHttpException; use Glpi\Exception\Http\NotFoundHttpException; +use Glpi\Security\ShareTokenManager; use Glpi\ShareableInterface; use Glpi\ShareToken; use Symfony\Component\HttpFoundation\JsonResponse; @@ -56,7 +57,14 @@ public function list(string $itemtype, int $items_id): JsonResponse { $this->checkRightToShareItem($itemtype, $items_id); /** @var class-string<\CommonDBTM> $itemtype */ - $tokens = ShareToken::getTokensForItem($itemtype, $items_id); + $manager = new ShareTokenManager(); + $tokens = \array_map( + function (array $row) use ($manager): array { + $row['token'] = $manager->decryptToken((string) $row['token']); + return $row; + }, + $manager->getTokensForItem($itemtype, $items_id), + ); return new JsonResponse(['tokens' => $tokens]); } @@ -71,6 +79,7 @@ public function create(Request $request, string $itemtype, int $items_id): JsonR { $this->checkRightToShareItem($itemtype, $items_id); /** @var class-string<\CommonDBTM> $itemtype */ + $manager = new ShareTokenManager(); $name = $request->getPayload()->getString('name') ?: null; $input = [ @@ -89,9 +98,12 @@ public function create(Request $request, string $itemtype, int $items_id): JsonR ); } + $fields = $token->fields; + $fields['token'] = $manager->decryptToken((string) $fields['token']); + return new JsonResponse([ 'success' => true, - 'token' => $token->fields, + 'token' => $fields, ]); } diff --git a/src/Glpi/Controller/UI/DropdownController.php b/src/Glpi/Controller/UI/DropdownController.php new file mode 100644 index 00000000000..1032d920d43 --- /dev/null +++ b/src/Glpi/Controller/UI/DropdownController.php @@ -0,0 +1,65 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Controller\UI; + +use Glpi\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +class DropdownController extends AbstractController +{ + #[Route( + path: "/dropdown", + name: "glpi_ui_dropdown", + )] + public function __invoke(Request $request): Response + { + $itemtype = $request->query->getString('itemtype'); + $fieldName = $request->query->getString('fieldname'); + $selectedValue = $request->query->getInt('value', 0); + + return $this->render('components/dropdown/dropdown.html.twig', [ + 'itemtype' => $itemtype, + 'fieldname' => $fieldName, + 'selected_value' => $selectedValue, + 'options' => [ + 'full_width' => true, + 'no_label' => true, + 'include_field' => false, + ], + ]); + } +} diff --git a/src/Glpi/Dashboard/Dashboard.php b/src/Glpi/Dashboard/Dashboard.php index e01062e9c13..f4c9a4e491b 100644 --- a/src/Glpi/Dashboard/Dashboard.php +++ b/src/Glpi/Dashboard/Dashboard.php @@ -195,7 +195,10 @@ public function getTitle(): string public function canViewCurrent(): bool { Profiler::getInstance()->start(__METHOD__); - $this->load(); + + if (!$this->load()) { + return false; + } if ($this->fields['users_id'] === Session::getLoginUserID()) { // User is always allowed to view its own dashboards. diff --git a/src/Glpi/Dashboard/Grid.php b/src/Glpi/Dashboard/Grid.php index 63b20e27390..3ea339fa804 100644 --- a/src/Glpi/Dashboard/Grid.php +++ b/src/Glpi/Dashboard/Grid.php @@ -1619,10 +1619,11 @@ public static function getDefaultDashboardForMenu(string $menu = "", bool $stric // if default not found, return first dashboard if (!$strict) { self::loadAllDashboards(); - $first_dashboard = array_shift(self::$all_dashboards); - if (isset($first_dashboard['key'])) { - return $first_dashboard['key']; - } + $dashboards = $menu === 'mini_ticket' + ? self::$all_dashboards + : array_filter(self::$all_dashboards, static fn($d) => $d['key'] !== 'mini_tickets'); + + return array_shift($dashboards)['key'] ?? ""; } return ""; diff --git a/src/Glpi/Dashboard/Provider.php b/src/Glpi/Dashboard/Provider.php index 8f9379d7586..8b77188cfcf 100644 --- a/src/Glpi/Dashboard/Provider.php +++ b/src/Glpi/Dashboard/Provider.php @@ -54,7 +54,6 @@ use Glpi\DBAL\QuerySubQuery; use Glpi\Debug\Profiler; use Glpi\Search\Input\QueryBuilder; -use Glpi\Search\SearchOption; use Group; use Group_Ticket; use Profile_User; @@ -331,12 +330,47 @@ public static function nbTicketsGeneric( ], ]; $query_criteria['WHERE']["$table.status"] = Ticket::getNotSolvedStatusArray(); + + $ola_ttr_late_criteria = new QuerySubQuery( + [ + 'SELECT' => [Ticket::generateSLAOLAComputation('internal_time_to_resolve', 'glpi_items_olas')], + 'FROM' => new QueryExpression('glpi_tickets', 'glpi_tickets_subquery'), + 'LEFT JOIN' => [ + 'glpi_items_olas' => [ + 'ON' => [ + 'glpi_tickets_subquery' => 'id', + 'glpi_items_olas' => 'items_id', + ['AND' => [ 'glpi_items_olas.itemtype' => ['=', (new QueryExpression(Ticket::class))->getValue()] ] ], + ], + ], + ], + 'WHERE' => ['glpi_tickets_subquery.id' => new QueryExpression('glpi_tickets.id') ], + ] + ); + + $ola_tto_late_criteria = new QuerySubQuery( + [ + 'SELECT' => [Ticket::generateSLAOLAComputation('internal_time_to_own', 'glpi_items_olas')], + 'FROM' => new QueryExpression('glpi_tickets', 'glpi_tickets_subquery'), + 'LEFT JOIN' => [ + 'glpi_items_olas' => [ + 'ON' => [ + 'glpi_tickets' => 'id', + 'glpi_items_olas' => 'items_id', + ['AND' => [ 'glpi_items_olas.itemtype' => ['=', Ticket::class]]], + ], + ], + ], + 'WHERE' => ['glpi_tickets_subquery.id' => new QueryExpression('glpi_tickets.id')], + ] + ); + $query_criteria['WHERE'][] = [ 'OR' => [ CommonITILObject::generateSLAOLAComputation('time_to_resolve', 'glpi_tickets'), - CommonITILObject::generateSLAOLAComputation('internal_time_to_resolve', 'glpi_tickets'), + ['1' => $ola_ttr_late_criteria ], CommonITILObject::generateSLAOLAComputation('time_to_own', 'glpi_tickets'), - CommonITILObject::generateSLAOLAComputation('internal_time_to_own', 'glpi_tickets'), + ['1' => $ola_tto_late_criteria ], ], ]; break; @@ -491,6 +525,9 @@ public static function nbTicketsGeneric( } + /** + * Get the number of late tickets by SLA status and technician + */ public static function nbTicketsByAgreementStatusAndTechnician(array $params = []): array { global $DB; diff --git a/src/Glpi/Form/Condition/ConditionHandler/UrgencyConditionHandler.php b/src/Glpi/Form/Condition/ConditionHandler/UrgencyConditionHandler.php index 3f6b7b030fe..54a96467641 100644 --- a/src/Glpi/Form/Condition/ConditionHandler/UrgencyConditionHandler.php +++ b/src/Glpi/Form/Condition/ConditionHandler/UrgencyConditionHandler.php @@ -54,6 +54,6 @@ public function getTemplate(): string #[Override] public function getTemplateParameters(ConditionData $condition): array { - return ['values' => Urgency::getUrgencyValuesForDropdown()]; + return ['values' => Urgency::getEnabledUrgencyValuesForDropdown()]; } } diff --git a/src/Glpi/Form/Destination/AbstractCommonITILFormDestination.php b/src/Glpi/Form/Destination/AbstractCommonITILFormDestination.php index 94149a3a7ba..805aeae3328 100644 --- a/src/Glpi/Form/Destination/AbstractCommonITILFormDestination.php +++ b/src/Glpi/Form/Destination/AbstractCommonITILFormDestination.php @@ -453,6 +453,7 @@ private function applyPredefinedTemplateFields(array $input): array $fields = $predefined_fields->getPredefinedFields($template->fields['id']); foreach ($fields as $field => $value) { + $field = in_array($field, ['_olas_id_tto','_olas_id_ttr']) ? '_olas_id' : $field; $field_definition = $fields_definition[$field] ?? null; if ( $field_definition @@ -477,6 +478,11 @@ private function applyPredefinedTemplateFields(array $input): array } } + // If the template has a predefined OLA, we set the _la_update flag to update them + if (isset($input['_olas_id'])) { + $input['_la_update'] = true; + } + return $input; } diff --git a/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldStrategy.php b/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldStrategy.php index 26ad7da9f3c..b0994d96614 100644 --- a/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldStrategy.php +++ b/src/Glpi/Form/Destination/CommonITILField/ITILActorFieldStrategy.php @@ -130,6 +130,7 @@ private function getActorsFromCurrentUser(AnswersSet $answers_set): ?array if ( $delegation->users_id !== null + && $delegation->users_id !== (int) $user_id && Ticket::canDelegateeCreateTicket($delegation->users_id) ) { return [ diff --git a/src/Glpi/Form/Destination/CommonITILField/OLAField.php b/src/Glpi/Form/Destination/CommonITILField/OLAField.php new file mode 100644 index 00000000000..de4fa974350 --- /dev/null +++ b/src/Glpi/Form/Destination/CommonITILField/OLAField.php @@ -0,0 +1,74 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Form\Destination\CommonITILField; + +use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\AnswersSet; +use InvalidArgumentException; +use Override; + +abstract class OLAField extends SLMField +{ + #[Override] + public function applyConfiguratedValueToInputUsingAnswers( + JsonFieldInterface $config, + array $input, + AnswersSet $answers_set + ): array { + if (!$config instanceof SLMFieldConfig) { + throw new InvalidArgumentException("Unexpected config class"); + } + + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + if ($strategy === false) { + return $input; + } + + // Compute value according to strategy + $slm_id = $strategy->getSLMID($config); + + // Do not edit input if invalid value was found + $slm = $this->getSLM(); + if (!$slm::getById($slm_id)) { + return $input; + } + + $input['_olas_id'] = [$slm_id]; + $input['_la_update'] = true; + + return $input; + } +} diff --git a/src/Glpi/Form/Destination/CommonITILField/OLATTOField.php b/src/Glpi/Form/Destination/CommonITILField/OLATTOField.php index 5bae1e3287f..743121efc2a 100644 --- a/src/Glpi/Form/Destination/CommonITILField/OLATTOField.php +++ b/src/Glpi/Form/Destination/CommonITILField/OLATTOField.php @@ -40,7 +40,7 @@ use Override; use SLM; -final class OLATTOField extends SLMField +final class OLATTOField extends OLAField { #[Override] public function getLabel(): string diff --git a/src/Glpi/Form/Destination/CommonITILField/OLATTRField.php b/src/Glpi/Form/Destination/CommonITILField/OLATTRField.php index b54fca75d17..f7f80b44f09 100644 --- a/src/Glpi/Form/Destination/CommonITILField/OLATTRField.php +++ b/src/Glpi/Form/Destination/CommonITILField/OLATTRField.php @@ -40,7 +40,7 @@ use Override; use SLM; -final class OLATTRField extends SLMField +final class OLATTRField extends OLAField { #[Override] public function getLabel(): string diff --git a/src/Glpi/Form/Destination/CommonITILField/SLAField.php b/src/Glpi/Form/Destination/CommonITILField/SLAField.php new file mode 100644 index 00000000000..a64e0c728a9 --- /dev/null +++ b/src/Glpi/Form/Destination/CommonITILField/SLAField.php @@ -0,0 +1,70 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Form\Destination\CommonITILField; + +use Glpi\DBAL\JsonFieldInterface; +use Glpi\Form\AnswersSet; +use InvalidArgumentException; +use Override; + +abstract class SLAField extends SLMField +{ + #[Override] + public function applyConfiguratedValueToInputUsingAnswers( + JsonFieldInterface $config, + array $input, + AnswersSet $answers_set + ): array { + if (!$config instanceof SLMFieldConfig) { + throw new InvalidArgumentException("Unexpected config class"); + } + + // Only one strategy is allowed + $strategy = current($config->getStrategies()); + + // Compute value according to strategy + $slm_id = $strategy->getSLMID($config); + + // Do not edit input if invalid value was found + $slm = $this->getSLM(); + if (!$slm::getById($slm_id)) { + return $input; + } + + $input[$slm::getFieldNames($this->getType())[1]] = $slm_id; + + return $input; + } +} diff --git a/src/Glpi/Form/Destination/CommonITILField/SLATTOField.php b/src/Glpi/Form/Destination/CommonITILField/SLATTOField.php index 45309721c9f..cd9507ac8d3 100644 --- a/src/Glpi/Form/Destination/CommonITILField/SLATTOField.php +++ b/src/Glpi/Form/Destination/CommonITILField/SLATTOField.php @@ -40,7 +40,7 @@ use SLA; use SLM; -final class SLATTOField extends SLMField +final class SLATTOField extends SLAField { #[Override] public function getLabel(): string diff --git a/src/Glpi/Form/Destination/CommonITILField/SLATTRField.php b/src/Glpi/Form/Destination/CommonITILField/SLATTRField.php index 8df25106222..2c067d00f57 100644 --- a/src/Glpi/Form/Destination/CommonITILField/SLATTRField.php +++ b/src/Glpi/Form/Destination/CommonITILField/SLATTRField.php @@ -43,7 +43,7 @@ use SLA; use SLM; -final class SLATTRField extends SLMField +final class SLATTRField extends SLAField { #[Override] public function getLabel(): string diff --git a/src/Glpi/Form/Destination/CommonITILField/SLMField.php b/src/Glpi/Form/Destination/CommonITILField/SLMField.php index 80fd36b1f1e..6520f2eb61c 100644 --- a/src/Glpi/Form/Destination/CommonITILField/SLMField.php +++ b/src/Glpi/Form/Destination/CommonITILField/SLMField.php @@ -54,6 +54,12 @@ abstract class SLMField extends AbstractConfigField implements DestinationFieldConverterInterface { abstract public function getSLM(): LevelAgreement; + + /** + * Get the type of SLM field. + * + * @return \SLM::TTO|\SLM::TTR + */ abstract public function getType(): int; /** @return class-string */ abstract public function getConfigClass(): string; diff --git a/src/Glpi/Form/Destination/CommonITILField/TitleField.php b/src/Glpi/Form/Destination/CommonITILField/TitleField.php index 6124301acce..3b75afc18f5 100644 --- a/src/Glpi/Form/Destination/CommonITILField/TitleField.php +++ b/src/Glpi/Form/Destination/CommonITILField/TitleField.php @@ -134,11 +134,14 @@ public function applyConfiguratedValueToInputUsingAnswers( } $tag_manager = new FormTagsManager(); - $input['name'] = $tag_manager->insertTagsContent( + + $title = $tag_manager->insertTagsContent( $config->getValue(), $answers_set ); + $input['name'] = html_entity_decode(strip_tags($title)); + return $input; } diff --git a/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php b/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php index d1f13735a02..df9c731bc94 100644 --- a/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php +++ b/src/Glpi/Form/Destination/CommonITILField/UrgencyField.php @@ -34,7 +34,6 @@ namespace Glpi\Form\Destination\CommonITILField; -use CommonITILObject; use Glpi\Application\View\TemplateRenderer; use Glpi\DBAL\JsonFieldInterface; use Glpi\Form\AnswersSet; @@ -47,6 +46,7 @@ use Glpi\Form\Migration\FormMigration; use Glpi\Form\Question; use Glpi\Form\QuestionType\QuestionTypeUrgency; +use Glpi\Urgency; use InvalidArgumentException; use Override; @@ -96,7 +96,7 @@ public function renderConfigForm( 'empty_label' => __("Select an urgency level..."), 'value' => $config->getSpecificUrgency(), 'input_name' => $input_name . "[" . UrgencyFieldConfig::SPECIFIC_URGENCY_VALUE . "]", - 'possible_values' => $this->getUrgencyLevels(), + 'possible_values' => Urgency::getEnabledUrgencyValuesForDropdown(), ], // Specific additional config for SPECIFIC_ANSWER strategy @@ -126,7 +126,7 @@ public function applyConfiguratedValueToInputUsingAnswers( $urgency = $strategy->computeUrgency($config, $answers_set); // Do not edit input if invalid value was found - $valid_values = array_keys($this->getUrgencyLevels()); + $valid_values = array_keys(Urgency::getEnabledUrgencyValuesForDropdown()); if (!in_array($urgency, $valid_values)) { return $input; } @@ -174,31 +174,6 @@ public function convertFieldConfig(FormMigration $migration, Form $form, array $ return $this->getDefaultConfig($form); } - /** - * Retrieve available urgency levels - * - * @return array - */ - private function getUrgencyLevels(): array - { - global $CFG_GLPI; - - // Get the urgency levels - $urgency_levels = array_combine( - range(1, 5), - array_map(fn($urgency) => CommonITILObject::getUrgencyName($urgency), range(1, 5)) - ); - - // Filter out the urgency levels that are not enabled - $urgency_levels = array_filter( - $urgency_levels, - fn($key) => (($CFG_GLPI['urgency_mask'] & (1 << $key)) > 0), - ARRAY_FILTER_USE_KEY - ); - - return $urgency_levels; - } - public function getStrategiesForDropdown(): array { $values = []; diff --git a/src/Glpi/Form/QuestionType/QuestionTypeUrgency.php b/src/Glpi/Form/QuestionType/QuestionTypeUrgency.php index a6d36cb6f2c..d9636c6396b 100644 --- a/src/Glpi/Form/QuestionType/QuestionTypeUrgency.php +++ b/src/Glpi/Form/QuestionType/QuestionTypeUrgency.php @@ -43,6 +43,7 @@ use Glpi\Form\Condition\UsedAsCriteriaInterface; use Glpi\Form\Migration\FormQuestionDataConverterInterface; use Glpi\Form\Question; +use Glpi\Urgency; use Override; final class QuestionTypeUrgency extends AbstractQuestionType implements UsedAsCriteriaInterface, FormQuestionDataConverterInterface, ConditionValueTransformerInterface @@ -69,31 +70,6 @@ public function getDefaultValue(?Question $question): int return 0; } - /** - * Retrieve available urgency levels - * - * @return array - */ - private function getUrgencyLevels(): array - { - global $CFG_GLPI; - - // Get the urgency levels - $urgency_levels = array_combine( - range(1, 5), - array_map(fn($urgency) => CommonITILObject::getUrgencyName($urgency), range(1, 5)) - ); - - // Filter out the urgency levels that are not enabled - $urgency_levels = array_filter( - $urgency_levels, - fn($key) => (($CFG_GLPI['urgency_mask'] & (1 << $key)) > 0), - ARRAY_FILTER_USE_KEY - ); - - return $urgency_levels; - } - #[Override] public function renderAdministrationTemplate(?Question $question): string { @@ -118,7 +94,7 @@ public function renderAdministrationTemplate(?Question $question): string return $twig->renderFromStringTemplate($template, [ 'init' => $question != null, 'value' => $this->getDefaultValue($question), - 'urgency_levels' => $this->getUrgencyLevels(), + 'urgency_levels' => Urgency::getEnabledUrgencyValuesForDropdown(), ]); } @@ -146,7 +122,7 @@ public function renderEndUserTemplate(Question $question): string return $twig->renderFromStringTemplate($template, [ 'value' => $this->getDefaultValue($question), 'question' => $question, - 'urgency_levels' => $this->getUrgencyLevels(), + 'urgency_levels' => Urgency::getEnabledUrgencyValuesForDropdown(), 'label' => $question->fields['name'], ]); } diff --git a/src/Glpi/Inventory/MainAsset/MainAsset.php b/src/Glpi/Inventory/MainAsset/MainAsset.php index 7dbd859d7e0..f814468fd51 100644 --- a/src/Glpi/Inventory/MainAsset/MainAsset.php +++ b/src/Glpi/Inventory/MainAsset/MainAsset.php @@ -449,6 +449,9 @@ public function prepareAllRulesInput(stdClass $val): array case 'domains_id': $prop = 'domain'; break; + case 'manufacturers_id': + $prop = 'manufacturer'; + break; case 'ips': $prop = 'ip'; break; diff --git a/src/Glpi/Kernel/Listener/PostBootListener/BootPlugins.php b/src/Glpi/Kernel/Listener/PostBootListener/BootPlugins.php index 4dadd3f19a4..e995c64eaef 100644 --- a/src/Glpi/Kernel/Listener/PostBootListener/BootPlugins.php +++ b/src/Glpi/Kernel/Listener/PostBootListener/BootPlugins.php @@ -34,6 +34,7 @@ namespace Glpi\Kernel\Listener\PostBootListener; +use Glpi\Application\Environment; use Glpi\Debug\Profiler; use Glpi\Kernel\KernelListenerTrait; use Glpi\Kernel\ListenersPriority; @@ -65,6 +66,10 @@ public function onPostBoot(): void Profiler::getInstance()->start('BootPlugins::execute', Profiler::CATEGORY_BOOT); + if (Environment::get()->shouldSetupTesterPlugin()) { + $this->setupTesterPlugin(); + } + $plugin = new Plugin(); if (!$plugin->isPluginsExecutionSuspended()) { @@ -73,4 +78,15 @@ public function onPostBoot(): void Profiler::getInstance()->stop('BootPlugins::execute'); } + + private function setupTesterPlugin(): void + { + global $DB; + $DB->updateOrInsert(table: Plugin::getTable(), params: [ + 'directory' => 'tester', + 'name' => 'tester', + 'state' => 1, + 'version' => '1.0.0', + ], where: ['directory' => 'tester']); + } } diff --git a/src/Glpi/Kernel/Listener/PostBootListener/InitializePlugins.php b/src/Glpi/Kernel/Listener/PostBootListener/InitializePlugins.php index 5cca34d1e30..117828880a1 100644 --- a/src/Glpi/Kernel/Listener/PostBootListener/InitializePlugins.php +++ b/src/Glpi/Kernel/Listener/PostBootListener/InitializePlugins.php @@ -34,7 +34,6 @@ namespace Glpi\Kernel\Listener\PostBootListener; -use Glpi\Application\Environment; use Glpi\Debug\Profiler; use Glpi\DependencyInjection\PluginContainer; use Glpi\Kernel\KernelListenerTrait; @@ -68,10 +67,6 @@ public function onPostBoot(): void $plugin = new Plugin(); if (!$plugin->isPluginsExecutionSuspended()) { - if (Environment::get()->shouldSetupTesterPlugin()) { - $this->setupTesterPlugin(); - } - $plugin->init(); } @@ -79,15 +74,4 @@ public function onPostBoot(): void Profiler::getInstance()->stop('InitializePlugins::execute'); } - - private function setupTesterPlugin(): void - { - global $DB; - $DB->updateOrInsert(table: Plugin::getTable(), params: [ - 'directory' => 'tester', - 'name' => 'tester', - 'state' => 1, - 'version' => '1.0.0', - ], where: ['directory' => 'tester']); - } } diff --git a/src/Glpi/Knowbase/SidePanel/SharingRenderer.php b/src/Glpi/Knowbase/SidePanel/SharingRenderer.php index 013bdb59f05..7423fc7c070 100644 --- a/src/Glpi/Knowbase/SidePanel/SharingRenderer.php +++ b/src/Glpi/Knowbase/SidePanel/SharingRenderer.php @@ -34,7 +34,7 @@ namespace Glpi\Knowbase\SidePanel; -use Glpi\ShareToken; +use Glpi\Security\ShareTokenManager; use KnowbaseItem; use Override; @@ -56,7 +56,14 @@ public function getTemplate(): string public function getParams(KnowbaseItem $item): array { $id = $item->getID(); - $tokens = ShareToken::getTokensForItem(KnowbaseItem::class, $id); + $manager = new ShareTokenManager(); + $tokens = \array_map( + function (array $row) use ($manager): array { + $row['token'] = $manager->decryptToken((string) $row['token']); + return $row; + }, + $manager->getTokensForItem(KnowbaseItem::class, $id), + ); return [ 'id' => $id, diff --git a/src/Glpi/Marketplace/Controller.php b/src/Glpi/Marketplace/Controller.php index c0a9766d1ea..b7cdb62dd86 100644 --- a/src/Glpi/Marketplace/Controller.php +++ b/src/Glpi/Marketplace/Controller.php @@ -229,7 +229,29 @@ public function downloadPlugin($auto_install = true, ?string $version = null): b } // clean dir in case of update - Toolbox::deleteDir(GLPI_MARKETPLACE_DIR . "/{$this->plugin_key}"); + $plugin_dir = GLPI_MARKETPLACE_DIR . "/{$this->plugin_key}"; + $opcache_files = []; + + // If OPCache is enabled, we must invalidate the cache for the affected plugin files before deleting them. + // Otherwise, a removed file may remain cached between the deletion and extraction steps, + // causing the application to execute an outdated version of the code. + if (function_exists('opcache_invalidate') && is_dir($plugin_dir)) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($plugin_dir, \RecursiveDirectoryIterator::SKIP_DOTS) + ); + foreach ($iterator as $file) { + if ($file->isFile() && $file->getExtension() === 'php') { + $opcache_files[] = $file->getRealPath(); + } + } + } + + Toolbox::deleteDir($plugin_dir); + + // Invalidate the cache for the affected files. + foreach ($opcache_files as $cached_file) { + opcache_invalidate($cached_file, true); + } $archive = new $driver($dest, $format); try { diff --git a/src/Glpi/Migration/GenericobjectPluginMigration.php b/src/Glpi/Migration/GenericobjectPluginMigration.php index c36d65a7624..5b73c9efcf8 100644 --- a/src/Glpi/Migration/GenericobjectPluginMigration.php +++ b/src/Glpi/Migration/GenericobjectPluginMigration.php @@ -121,6 +121,12 @@ class GenericobjectPluginMigration extends AbstractPluginMigration */ private array $dropdown_definitions = []; + /** + * Plugin fields definitions fetched from plugin constant files. + * @var array> + */ + private array $generic_object_fields_definitions = []; + #[Override] protected function getHasBeenExecutedConfigurationKey(): string { @@ -1277,6 +1283,7 @@ private function getCustomFieldSpecs( break; } + $is_dropdown_type = false; if (\isForeignKeyField($field_name)) { // Foreign key field if ($this->isAGenericObjectFkeyField($field_name)) { @@ -1284,18 +1291,23 @@ private function getCustomFieldSpecs( $target_type = $this->getTargetItemtype($source_type); } else { $target_type = \getItemtypeForForeignKeyField($field_name); - if ($target_type === null) { - throw new MigrationException( - sprintf(__('Unable to import the "%s" field.'), $field_name), - sprintf('Unable to import the `%s` field.', $field_name) - ); - } } - $specs['system_name'] = $this->getTargetField($itemtype, $field_name, with_prefix: false); - $specs['label'] = $target_type::getTypeName(); - $specs['type'] = DropdownType::class; - $specs['itemtype'] = $target_type; - } else { + if ($target_type !== null) { + $is_dropdown_type = true; + + $specs['system_name'] = $this->getTargetField($itemtype, $field_name, with_prefix: false); + $specs['label'] = $target_type::getTypeName(); + $specs['type'] = DropdownType::class; + $specs['itemtype'] = $target_type; + } else { + $this->progress_indicator?->addMessage( + MessageType::Warning, + sprintf(__('Unable to find the target type for the "%s" field, it will not be imported as a dropdown field.'), $field_name) + ); + } + } + + if (!$is_dropdown_type) { // Keep only the main column type by removing anything that is preceded by a space (e.g. " unsigned") // or a parenthesis (e.g. "(255)"). $field_type = \strtolower(preg_replace('/^([a-z]+)([ (].+)*$/', '$1', $field_type)); @@ -1431,6 +1443,10 @@ private function getTargetField(string $itemtype, string $field, bool $with_pref */ private function getGenericObjectFieldsDefinition(string $itemtype): array { + if (\array_key_exists($itemtype, $this->generic_object_fields_definitions)) { + return $this->generic_object_fields_definitions[$itemtype]; + } + $system_name = preg_replace('/^PluginGenericObject/', '', $itemtype); $constant_files = [ @@ -1442,6 +1458,7 @@ private function getGenericObjectFieldsDefinition(string $itemtype): array sprintf('%s/genericobject/fields/%s.constant.php', GLPI_PLUGIN_DOC_DIR, \lcfirst($system_name)), ]; + /** @var array $GO_FIELDS */ $GO_FIELDS = []; // @phpstan-ignore closure.unusedUse (function () use (&$GO_FIELDS, $constant_files) { @@ -1466,16 +1483,18 @@ private function getGenericObjectFieldsDefinition(string $itemtype): array ]; // Clean definitions to keep only valid values. - $clean_definitions = []; - // @phpstan-ignore foreach.emptyArray + $this->generic_object_fields_definitions[$itemtype] = []; foreach ($GO_FIELDS as $key => $definition) { - // @phpstan-ignore nullCoalesce.offset + if (!is_string($key)) { + // Array key is supposed to be a field name, not an integer. + continue; + } + $name = $definition['name'] ?? null; if (!\is_string($name)) { $name = $key; } - // @phpstan-ignore nullCoalesce.offset $input_type = $definition['input_type'] ?? 'none'; if (!\in_array($input_type, $valid_input_types, true)) { throw new MigrationException( @@ -1488,22 +1507,24 @@ private function getGenericObjectFieldsDefinition(string $itemtype): array ); } - $clean_definitions[$key] = [ + $this->generic_object_fields_definitions[$itemtype][$key] = [ 'name' => $name, 'input_type' => $input_type, ]; - if ($definition['input_type'] === 'integer') { - foreach (['min', 'max', 'step'] as $opt) { - // @phpstan-ignore nullCoalesce.offset - $opt_value = $definition[$opt] ?? null; - if (\is_int($opt_value)) { - $clean_definitions[$key][$opt] = $opt_value; - } + if ($input_type === 'integer') { + if (\array_key_exists('min', $definition) && \is_int($definition['min'])) { + $this->generic_object_fields_definitions[$itemtype][$key]['min'] = $definition['min']; + } + if (\array_key_exists('max', $definition) && \is_int($definition['max'])) { + $this->generic_object_fields_definitions[$itemtype][$key]['max'] = $definition['max']; + } + if (\array_key_exists('step', $definition) && \is_int($definition['step'])) { + $this->generic_object_fields_definitions[$itemtype][$key]['step'] = $definition['step']; } } } - return $clean_definitions; + return $this->generic_object_fields_definitions[$itemtype]; } } diff --git a/src/Glpi/RichText/UserMention.php b/src/Glpi/RichText/UserMention.php index df423edf938..f8c6c3ef657 100644 --- a/src/Glpi/RichText/UserMention.php +++ b/src/Glpi/RichText/UserMention.php @@ -166,19 +166,19 @@ public static function handleUserMentions(CommonDBTM $item): void $current_actors_ids[] = $actor['users_id']; } } - + $default_use_notif = \Entity::getUsedConfig('is_notif_enable_default', $main_item->getEntityID(), '', 1); // Add newly mentioned actors as observers foreach ($mentionned_actors_ids as $user_id) { if (in_array($user_id, $current_actors_ids)) { continue; } - $input = [ 'type' => CommonITILActor::OBSERVER, 'users_id' => $user_id, $main_item->getForeignKeyField() => $main_item->fields['id'], '_do_not_compute_takeintoaccount' => true, '_from_object' => true, + 'use_notification' => $default_use_notif, ]; $userlink->add($input); } diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 0bc7067054e..685dc23c9ac 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -56,6 +56,7 @@ use Dropdown; use Entity; use Entity_KnowbaseItem; +use Exception; use Glpi\Application\View\TemplateRenderer; use Glpi\Asset\Asset_PeripheralAsset; use Glpi\DBAL\Operator; @@ -102,6 +103,7 @@ use Search; use Session; use SLA; +use SLM; use Software; use Ticket; use TicketSatisfaction; @@ -902,20 +904,7 @@ public static function getDefaultWhereCriteria(string $itemtype): array ] ) ) { - $criteria['OR'][] = [ - 'AND' => [ - "`glpi_ticketvalidations`.`itemtype_target`" => User::class, - "`glpi_ticketvalidations`.`items_id_target`" => Session::getLoginUserID(), - ], - ]; - if (count($_SESSION['glpigroups'])) { - $criteria['OR'][] = [ - 'AND' => [ - "`glpi_ticketvalidations`.`itemtype_target`" => Group::class, - "`glpi_ticketvalidations`.`items_id_target`" => $_SESSION['glpigroups'], - ], - ]; - } + $criteria['OR'][] = TicketValidation::getTargetCriteriaForUser((int) Session::getLoginUserID()); } } break; @@ -6179,10 +6168,8 @@ public static function giveItem( case "glpi_problems.time_to_resolve": case "glpi_changes.time_to_resolve": case "glpi_tickets.time_to_own": - case "glpi_tickets.internal_time_to_own": - case "glpi_tickets.internal_time_to_resolve": // Due date + progress - if (in_array($orig_id, [151, 158, 181, 186])) { + if (in_array($orig_id, [151, 158, 181])) { $out = htmlescape(Html::convDateTime($data[$ID][0]['name'])); $color = null; @@ -6190,10 +6177,7 @@ public static function giveItem( $data[$ID][0]['status'] == CommonITILObject::WAITING ) { // No due date in waiting status for TTRs - if ( - $table . '.' . $field == "glpi_tickets.time_to_resolve" - || $table . '.' . $field == "glpi_tickets.internal_time_to_resolve" - ) { + if ($table . '.' . $field == "glpi_tickets.time_to_resolve") { return ''; } else { $color = '#AAAAAA'; @@ -6227,20 +6211,11 @@ public static function giveItem( case "glpi_tickets.time_to_own": $slaField = 'slas_id_tto'; break; - case "glpi_tickets.internal_time_to_own": - $slaField = 'olas_id_tto'; - $sla_class = OLA::class; - break; - case "glpi_tickets.internal_time_to_resolve": - $slaField = 'olas_id_ttr'; - $sla_class = OLA::class; - break; } switch ($table . '.' . $field) { // If ticket has been taken into account: no progression display case "glpi_tickets.time_to_own": - case "glpi_tickets.internal_time_to_own": if (($item->fields['takeintoaccount_delay_stat'] > 0)) { return $out; } @@ -6335,9 +6310,9 @@ public static function giveItem( } $progressbar_data = [ - 'text' => Html::convDateTime($data[$ID][0]['name']), - 'percent' => $percentage, - 'percent_text' => $percentage_text, + 'text' => Html::convDateTime($data[$ID][0]['name']) ?? '', + 'percent' => (int) $percentage, + 'percent_text' => (string) $percentage_text, 'color' => $color, ]; } else { @@ -6348,7 +6323,6 @@ public static function giveItem( switch ($table . "." . $field) { case "glpi_tickets.time_to_resolve": - case "glpi_tickets.internal_time_to_resolve": case "glpi_problems.time_to_resolve": case "glpi_changes.time_to_resolve": $solve_date = $data[$ID][0]['solvedate']; @@ -6385,6 +6359,135 @@ public static function giveItem( } break; + // OLA TTO / OLA TTR - due time and due time + progress + case "glpi_items_olas.due_time": + $out = ''; + + $data_items = array_filter($data[$ID], fn($key) => is_numeric($key), ARRAY_FILTER_USE_KEY); + foreach ($data_items as $data_item) { + // data used : + // - name -> due_time (see below) + // - status (ticket status) + // - takeintoaccount_delay_stat + // - date (ticket creation date) + // - olas_id + // - waiting_time + // - end_time + $ticket_status = $data_item['status'] ?? null; + $due_time = $data_item['name'] ?? null; + $ticket_creation_date = $data_item['date'] ?? null; + $waiting_time = $data_item['waiting_time'] ?? null; + $ola_end_time = $data_item['end_time'] ?? null; + $olas_id = $data_item['olas_id'] ?? null; + + $ola = new OLA(); + // no item_olas associated + if (!$olas_id) { + continue; + } + if (!$ola->getFromDB($olas_id)) { + throw new Exception('Referenced OLA not found for item_olas ID: ' . $olas_id); + } + $ola_name = $ola->fields['name']; + $ola_type = $ola->fields['type']; // won't change between loops + + // no due time set, nothing to display + if (is_null($due_time)) { + continue; + } + + // For TTR : no display if ticket is in waiting status + if ( + $ticket_status == CommonITILObject::WAITING + && $ola_type == SLM::TTR) { + continue; + } + + // ticket is solved or closed, no progress to display, just display the date + if ( + ($ticket_status == Ticket::SOLVED) + || ($ticket_status == Ticket::CLOSED) + ) { + $out .= $ola_name . ' : ' . htmlescape(Html::convDateTime($due_time)) . '
'; + continue; + } + + // no progress bar if ola completed (end_time is set) + if ($ola_end_time) { + $out .= $ola_name . ' : ' . htmlescape(Html::convDateTime($due_time)) . '
'; + continue; + } + + // - progress bar construction + $color = null; + if ($ticket_status == CommonITILObject::WAITING) { + $color = '#AAAAAA'; + } + + // time calculations to build the progress bar + // progress bar is displayed only is ola is not completed + $delay_between_ticket_creation_and_now = $ola->getActiveTimeBetween($ticket_creation_date, date('Y-m-d H:i:s')); + $delay_between_ticket_creation_and_ola_due_time = $ola->getActiveTimeBetween($ticket_creation_date, $due_time); + + if (($delay_between_ticket_creation_and_ola_due_time - $waiting_time) != 0) { + $percentage_done = round((100 * ($delay_between_ticket_creation_and_now - $waiting_time)) / ($delay_between_ticket_creation_and_ola_due_time - $waiting_time)); + } else { + // Total time is null : no active time + $percentage_done = 100; + } + if ($percentage_done > 100) { + $percentage_done = 100; + } + switch ($_SESSION['glpiduedatewarning_unit']) { + case 'hour': + $less_warn_limit = $_SESSION['glpiduedatewarning_less'] * HOUR_TIMESTAMP; + $less_warn = ($delay_between_ticket_creation_and_ola_due_time - $delay_between_ticket_creation_and_now); + break; + case 'day': + $less_warn_limit = $_SESSION['glpiduedatewarning_less'] * DAY_TIMESTAMP; + $less_warn = ($delay_between_ticket_creation_and_ola_due_time - $delay_between_ticket_creation_and_now); + break; + case '%': + default: + $less_warn_limit = $_SESSION['glpiduedatewarning_less']; + $less_warn = (100 - $percentage_done); + break; + } + + $less_crit_limit = 0; + $less_crit = 0; + if ($_SESSION['glpiduedatecritical_unit'] == '%') { + $less_crit_limit = $_SESSION['glpiduedatecritical_less']; + $less_crit = (100 - $percentage_done); + } elseif ($_SESSION['glpiduedatecritical_unit'] == 'hour') { + $less_crit_limit = $_SESSION['glpiduedatecritical_less'] * HOUR_TIMESTAMP; + $less_crit = ($delay_between_ticket_creation_and_ola_due_time - $delay_between_ticket_creation_and_now); + } elseif ($_SESSION['glpiduedatecritical_unit'] == 'day') { + $less_crit_limit = $_SESSION['glpiduedatecritical_less'] * DAY_TIMESTAMP; + $less_crit = ($delay_between_ticket_creation_and_ola_due_time - $delay_between_ticket_creation_and_now); + } + + if ($color === null) { + $color = $_SESSION['glpiduedateok_color']; + if ($less_crit < $less_crit_limit) { + $color = $_SESSION['glpiduedatecritical_color']; + } elseif ($less_warn < $less_warn_limit) { + $color = $_SESSION['glpiduedatewarning_color']; + } + } + + $progressbar_data = [ + 'text' => $ola_name . ' : ' . Html::convDateTime($due_time), + 'percent' => (int) $percentage_done, + 'percent_text' => (string) $percentage_done, + 'color' => $color, + ]; + + $out .= self::getProgressBar([$progressbar_data]); + } + + return $out; + case "glpi_softwarelicenses.number": if ($data[$ID][0]['min'] == -1) { return __s('Unlimited'); @@ -7037,7 +7140,7 @@ public static function giveItem( case 'progressbar': if (!isset($progressbar_data)) { $progressbar_data = array_map(fn($entry) => [ - 'percent' => ltrim(($entry['name'] ?? ""), "0"), + 'percent' => (int) ltrim(($entry['name'] ?? ""), "0"), 'percent_text' => ltrim(($entry['name'] ?? ""), "0"), 'color' => 'green', 'text' => '', @@ -7047,19 +7150,7 @@ public static function giveItem( $progressbar_data = [$progressbar_data]; } - $out = ''; - foreach ($progressbar_data as $k => $v) { - $out .= '
' . \htmlescape($v['text']) . '' - . '
' - . '
' - . \htmlescape($v['percent_text']) . '%' - . '
' - . '
'; - } - - return $out; + return self::getProgressBar($progressbar_data); case 'color': $color = \htmlescape($data[$ID][0]['name']); return "
@@ -7218,4 +7309,22 @@ private static function getOptComputation(string|QueryExpression $opt_value, str ) ); } + + /** + * @param array $progressbar_data + */ + public static function getProgressBar(array $progressbar_data): string + { + $out = ''; + foreach ($progressbar_data as $pg_data) { + $out .= '
' . \htmlescape($pg_data['text']) . '' + . '
' + . '
' + . \htmlescape($pg_data['percent_text']) . '%' + . '
'; + } + return $out; + } } diff --git a/src/Glpi/Security/ShareTokenManager.php b/src/Glpi/Security/ShareTokenManager.php index bd8b72c1580..7eba71ff6df 100644 --- a/src/Glpi/Security/ShareTokenManager.php +++ b/src/Glpi/Security/ShareTokenManager.php @@ -38,6 +38,7 @@ use Glpi\DBAL\QueryFunction; use Glpi\ShareableInterface; use Glpi\ShareToken; +use GLPIKey; use function Safe\strtotime; @@ -59,48 +60,12 @@ final class ShareTokenManager */ public function grantSessionAccess(string $token): (CommonDBTM&ShareableInterface)|null { - global $DB; - - $result = $DB->request([ - 'FROM' => ShareToken::getTable(), - 'WHERE' => [ - 'token' => $token, - 'is_active' => 1, - 'OR' => [ - ['date_expiration' => null], - ['date_expiration' => ['>', QueryFunction::now()]], - ], - ], - 'LIMIT' => 1, - ]); - - $row = $result->current(); + $row = $this->findValidTokenRowByPlaintext($token); if ($row === null) { return null; } - $item = \getItemForItemtype($row['itemtype']); - - if ( - !($item instanceof ShareableInterface) - || !($item instanceof CommonDBTM) - || !$item->getFromDB((int) $row['items_id']) - || ( - $item->maybeDeleted() - && (!$item->useDeletedToLockIfDynamic() || !$item->isDynamic()) - && $item->isDeleted() - ) - ) { - return null; - } - - // allow access for the next 5 minutes - $_SESSION[self::SESSION_KEY][$item::class][$item->getID()] = [ - 'token' => $token, - 'expires_at' => \date('Y-m-d H:i:s', strtotime('+5 minutes', strtotime($_SESSION['glpi_currenttime']))), - ]; - - return $item; + return $this->openSessionAccessFromRow($row); } /** @@ -116,8 +81,8 @@ public function hasSessionAccess(string $itemtype, int $items_id): bool $session_data = $_SESSION[self::SESSION_KEY][$itemtype][$items_id] ?? null; if ( !\is_array($session_data) - || !\array_key_exists('token', $session_data) - || !\is_string($session_data['token']) + || !\array_key_exists('sharetoken_id', $session_data) + || !\is_int($session_data['sharetoken_id']) || !\array_key_exists('expires_at', $session_data) || !\is_string($session_data['expires_at']) ) { @@ -128,10 +93,9 @@ public function hasSessionAccess(string $itemtype, int $items_id): bool return true; } - $access_granted_again = $this->grantSessionAccess($session_data['token']) !== null; - - if (!$access_granted_again) { - // Revoke access + // TTL expired: revalidate by indexed id lookup. No decrypt needed. + $row = $this->findValidTokenRowById($session_data['sharetoken_id']); + if ($row === null || $this->openSessionAccessFromRow($row) === null) { unset($_SESSION[self::SESSION_KEY][$itemtype][$items_id]); return false; } @@ -151,9 +115,10 @@ public function getAccessibleItems(): array $items = $_SESSION[self::SESSION_KEY] ?? []; $validated = []; - foreach ($items as $itemtype => $ids) { - foreach ($ids as $items_id => $token) { - if ($this->hasSessionAccess($itemtype, (int) $items_id)) { + foreach ($items as $itemtype => $entries) { + foreach (\array_keys($entries) as $items_id) { + $items_id = (int) $items_id; + if ($this->hasSessionAccess($itemtype, $items_id)) { if (!\array_key_exists($itemtype, $validated)) { $validated[$itemtype] = []; } @@ -164,4 +129,158 @@ public function getAccessibleItems(): array return $validated; } + + /** + * Find an active, non-expired token row matching the given plaintext. + * + * Lookup is filtered by `token_hint` (deterministic SHA-256 truncation of + * the plaintext) so the DB returns at most a handful of candidates — usually + * one. Each candidate is decrypted and compared in constant time. + * + * @return array|null + */ + private function findValidTokenRowByPlaintext(string $plain): ?array + { + global $DB; + + $iterator = $DB->request([ + 'SELECT' => ['id', 'token', 'itemtype', 'items_id'], + 'FROM' => ShareToken::getTable(), + 'WHERE' => [ + 'token_hint' => $this->computeTokenHint($plain), + 'is_active' => 1, + 'OR' => [ + ['date_expiration' => null], + ['date_expiration' => ['>', QueryFunction::now()]], + ], + ], + ]); + + foreach ($iterator as $candidate) { + if (\hash_equals($this->decryptToken((string) $candidate['token']), $plain)) { + return $candidate; + } + } + + return null; + } + + /** + * Find an active, non-expired token row by its primary key. + * + * Used on session refresh to avoid keeping the plaintext token in $_SESSION. + * + * @return array|null + */ + private function findValidTokenRowById(int $id): ?array + { + global $DB; + + return $DB->request([ + 'SELECT' => ['id', 'itemtype', 'items_id'], + 'FROM' => ShareToken::getTable(), + 'WHERE' => [ + 'id' => $id, + 'is_active' => 1, + 'OR' => [ + ['date_expiration' => null], + ['date_expiration' => ['>', QueryFunction::now()]], + ], + ], + 'LIMIT' => 1, + ])->current(); + } + + /** + * Validate the underlying item and record the access in $_SESSION. + * + * @param array $row Token row containing at least `id`, `itemtype`, `items_id`. + */ + private function openSessionAccessFromRow(array $row): (CommonDBTM&ShareableInterface)|null + { + $item = \getItemForItemtype($row['itemtype']); + + if ( + !($item instanceof ShareableInterface) + || !($item instanceof CommonDBTM) + || !$item->getFromDB((int) $row['items_id']) + || ( + $item->maybeDeleted() + && (!$item->useDeletedToLockIfDynamic() || !$item->isDynamic()) + && $item->isDeleted() + ) + ) { + return null; + } + + // allow access for the next 5 minutes + $_SESSION[self::SESSION_KEY][$item::class][$item->getID()] = [ + 'sharetoken_id' => (int) $row['id'], + 'expires_at' => \date('Y-m-d H:i:s', strtotime('+5 minutes', strtotime($_SESSION['glpi_currenttime']))), + ]; + + return $item; + } + + /** + * Generate a cryptographically secure random token. + */ + public function generateToken(): string + { + return bin2hex(random_bytes(32)); + } + + /** + * Decrypt a stored token. + * + * The DB stores tokens encrypted with the GLPI security key. Callers that need + * to expose the clear value (e.g. build a share URL or compare against a token + * submitted by a client) must decrypt explicitly through this helper. + */ + public function decryptToken(string $ciphertext): string + { + return (string) (new GLPIKey())->decrypt($ciphertext); + } + + /** + * Compute the deterministic, non-secret lookup hint for a plain token. + * + * Truncated SHA-256 (16 hex chars). The plain token has 256 bits of entropy + * from `random_bytes(32)`, so truncation does not enable preimage attacks; + * the hint exists only to filter the DB before the constant-time + * decrypt-and-compare done by `ShareTokenManager`. + */ + public function computeTokenHint(string $plain_token): string + { + return substr(hash('sha256', $plain_token), 0, 16); + } + + /** + * Get all tokens for a given item. + * + * @param class-string $itemtype The item class name + * @param int $items_id The item ID + * + * @return array> + */ + public function getTokensForItem(string $itemtype, int $items_id): array + { + global $DB; + + $results = []; + $iterator = $DB->request([ + 'FROM' => ShareToken::getTable(), + 'WHERE' => [ + 'itemtype' => $itemtype, + 'items_id' => $items_id, + ], + 'ORDER' => 'date_creation DESC', + ]); + + foreach ($iterator as $row) { + $results[] = $row; + } + + return $results; + } } diff --git a/src/Glpi/ShareToken.php b/src/Glpi/ShareToken.php index 2cede83dc13..c110f810232 100644 --- a/src/Glpi/ShareToken.php +++ b/src/Glpi/ShareToken.php @@ -35,6 +35,8 @@ namespace Glpi; use CommonDBChild; +use Glpi\Security\ShareTokenManager; +use GLPIKey; use Session; /** @@ -59,8 +61,11 @@ public static function getTypeName($nb = 0): string public function prepareInputForAdd($input) { - // Token cannot be manually defined, it must always be a randomly generaed value. - $input['token'] = $this->generateToken(); + // Token cannot be manually defined, it must always be a randomly generated value. + $manager = new ShareTokenManager(); + $plain = $manager->generateToken(); + $input['token'] = (new GLPIKey())->encrypt($plain); + $input['token_hint'] = $manager->computeTokenHint($plain); if (!isset($input['users_id'])) { $input['users_id'] = Session::getLoginUserID() ?: 0; @@ -68,41 +73,4 @@ public function prepareInputForAdd($input) return parent::prepareInputForAdd($input); } - - /** - * Get all tokens for a given item. - * - * @param class-string<\CommonDBTM> $itemtype The item class name - * @param int $items_id The item ID - * - * @return array> - */ - public static function getTokensForItem(string $itemtype, int $items_id): array - { - global $DB; - - $results = []; - $iterator = $DB->request([ - 'FROM' => self::getTable(), - 'WHERE' => [ - 'itemtype' => $itemtype, - 'items_id' => $items_id, - ], - 'ORDER' => 'date_creation DESC', - ]); - - foreach ($iterator as $row) { - $results[] = $row; - } - - return $results; - } - - /** - * Generate a cryptographically secure random token. - */ - private function generateToken(): string - { - return bin2hex(random_bytes(32)); - } } diff --git a/src/Glpi/Urgency.php b/src/Glpi/Urgency.php index 39bccfe2586..75d90268664 100644 --- a/src/Glpi/Urgency.php +++ b/src/Glpi/Urgency.php @@ -43,6 +43,9 @@ enum Urgency: int case HIGH = 4; case VERY_HIGH = 5; + /** + * @return array + */ public static function getUrgencyValuesForDropdown(): array { return [ @@ -53,4 +56,21 @@ public static function getUrgencyValuesForDropdown(): array self::VERY_HIGH->value => __('Very high'), ]; } + + /** + * @return array + */ + public static function getEnabledUrgencyValuesForDropdown(): array + { + global $CFG_GLPI; + + return array_filter( + array_combine( + array_map(fn($case) => $case->value, self::cases()), + array_map(fn($case) => \CommonITILObject::getUrgencyName($case->value), self::cases()), + ), + fn($key) => (($CFG_GLPI['urgency_mask'] & (1 << $key)) > 0), + ARRAY_FILTER_USE_KEY + ); + } } diff --git a/src/Group_Ticket.php b/src/Group_Ticket.php index 4f83698e467..7c0f234e56c 100644 --- a/src/Group_Ticket.php +++ b/src/Group_Ticket.php @@ -47,4 +47,12 @@ class Group_Ticket extends CommonITILActor public static ?string $items_id_1 = 'tickets_id'; public static ?string $itemtype_2 = Group::class; public static ?string $items_id_2 = 'groups_id'; + + #[Override] + public function post_purgeItem() + { + Item_Ola::computeGroupAssigneeRemoval((int) $this->fields['tickets_id'], (int) $this->fields['groups_id']); + + parent::post_purgeItem(); + } } diff --git a/src/Html.php b/src/Html.php index 8205a3b878b..142b212ff06 100644 --- a/src/Html.php +++ b/src/Html.php @@ -2863,7 +2863,7 @@ public static function showDatesTimelineGraph($options = []) // format dates foreach ($options['dates'] as &$data) { $data['date'] = $data['timestamp'] !== null - ? date("Y-m-d H:i:s", $data['timestamp']) + ? date("Y-m-d H:i:s", (int) $data['timestamp']) : null; } diff --git a/src/ITILTemplate.php b/src/ITILTemplate.php index dbd7a3f78ec..6e3b6f03d18 100644 --- a/src/ITILTemplate.php +++ b/src/ITILTemplate.php @@ -177,16 +177,6 @@ public function getFromDBWithData($ID, $withtypeandcategory = true) = Html::computeGenericDateTimeSearch($this->predefined['time_to_own'], false); } - // Compute internal_time_to_resolve - if (isset($this->predefined['internal_time_to_resolve'])) { - $this->predefined['internal_time_to_resolve'] - = Html::computeGenericDateTimeSearch($this->predefined['internal_time_to_resolve'], false); - } - if (isset($this->predefined['internal_time_to_own'])) { - $this->predefined['internal_time_to_own'] - = Html::computeGenericDateTimeSearch($this->predefined['internal_time_to_own'], false); - } - // Compute date if (isset($this->predefined['date'])) { $this->predefined['date'] diff --git a/src/ITILTemplatePredefinedField.php b/src/ITILTemplatePredefinedField.php index c32d600d354..946cce27592 100644 --- a/src/ITILTemplatePredefinedField.php +++ b/src/ITILTemplatePredefinedField.php @@ -235,6 +235,8 @@ public static function getMultiplePredefinedValues(): array $itil_object->getSearchOptionIDByField('field', 'name', 'glpi_documents'), $itil_object->getSearchOptionIDByField('field', 'items_id', $itemstable), $itil_object->getSearchOptionIDByField('field', 'name', 'glpi_tasktemplates'), + 190, + 191, ]; return $fields; diff --git a/src/Infocom.php b/src/Infocom.php index cda9aa0f609..bd4cf03eff8 100644 --- a/src/Infocom.php +++ b/src/Infocom.php @@ -734,7 +734,8 @@ public static function cronInfocom($task = null) $not_deleted_items = array_filter($items, static fn($item) => $item['is_deleted'] === 0); $deleted_expired_items = array_filter($items, static fn($item) => $item['is_deleted'] === 1 && $item['warrantyexpiration'] < $_SESSION['glpi_currenttime']); if ( - NotificationEvent::raiseEvent("alert", new self(), [ + count($not_deleted_items) > 0 + && NotificationEvent::raiseEvent("alert", new self(), [ 'entities_id' => $entity, 'items' => $not_deleted_items, ]) diff --git a/src/Item_Ola.php b/src/Item_Ola.php new file mode 100644 index 00000000000..ce98263fd5e --- /dev/null +++ b/src/Item_Ola.php @@ -0,0 +1,513 @@ +. + * + * --------------------------------------------------------------------- + */ + +use function Safe\strtotime; + +/** + * @phpstan-import-type OLAFields from OLA + * @phpstan-type ItemOlaData array{ + * items_olas_id?: int, + * name: string, + * entities_id: int, + * is_recursive: bool, + * type: int, + * comment: string, + * number_time: int, + * use_ticket_calendar: bool, + * calendars_id: int, + * date_mod: string, + * definition_time: string, + * end_of_working_day: string, + * date_creation: string, + * slms_id: int, + * olas_id: int, + * ola_type: SLM::TTR|SLM::TTO, + * start_time: string, + * due_time: string, + * end_time: ?string, + * waiting_time: string, + * waiting_start: ?string, + * is_late: string, + * class: string, + * item: Ticket, + * nextaction: false|OlaLevel_Ticket|SlaLevel_Ticket, + * level: false|LevelAgreementLevel, + * group_name: string} + */ +class Item_Ola extends CommonDBRelation +{ + public static ?string $itemtype_1 = 'itemtype'; // Only Ticket at the moment + public static ?string $items_id_1 = 'items_id'; + + public static ?string $itemtype_2 = OLA::class; + public static ?string $items_id_2 = 'olas_id'; + + /** + * @param string $itemtype class-string + * @param int $items_id + * @param int[] $request_olas_ids + * @return int[] + */ + public static function filterInputOlas(string $itemtype, int $items_id, array $request_olas_ids): array + { + if ($itemtype !== Ticket::class) { + throw new RuntimeException('Item_Ola only works for Ticket at the moment.'); + } + + /** @var Ticket $item */ + $item = getItemForItemtype($itemtype); + if ($item === false || !$item->getFromDB($items_id)) { + throw new RuntimeException($itemtype . ' not found #' . $items_id); + } + + $ola_data = $item->getOlasData(); + $current_not_completed_olas_ids = array_column( + array_filter($ola_data, fn($ola_data) => $ola_data['end_time'] === null), + 'olas_id' + ); + $request_olas_ids = array_filter( + $request_olas_ids, + static fn($tested_olas_id) => !in_array($tested_olas_id, $current_not_completed_olas_ids) + ); + // remove ola currently assigned, remove ola one by one because array_diff doesn't take into account the duplicates in $request_olas_ids + $current_olas_ids = array_column($item->getOlasData(), 'olas_id'); + $toadd_olas_ids = []; + foreach ($request_olas_ids as $requested_olas_id) { + // check if already in current olas + if (!in_array($requested_olas_id, $current_olas_ids)) { + $toadd_olas_ids[] = $requested_olas_id; + } else { + // remove first occurrence of $requested_olas_id in $_current_olas_ids + $key = array_search($requested_olas_id, $current_olas_ids); + if ($key !== false) { + unset($current_olas_ids[$key]); + } + } + } + + return $toadd_olas_ids; + } + + /** + * Prepare the input for add + * + * add start_time, due_time and is_late values. + * + * @param array{start_time: string, olas_id: int, itemtype: class-string, items_id: int} $input + * @return array|false + */ + public function prepareInputForAdd($input) + { + if (in_array(['due_time'], array_keys($input))) { + throw new RuntimeException('due_time is not allowed in the input. Values is computed.'); + } + + // get the related ola (cannot use getConnexityItem() ou getOnePeer() because it is not in the database yet) + $_ola = new OLA(); + if (!$_ola->getFromDB($input[static::$items_id_2])) { + throw new RuntimeException('OLA not found #' . $input[static::$items_id_2]); + } + + return parent::prepareInputForAdd([ + 'due_time' => $_ola->computeDate($input['start_time']), + 'start_time' => $input['start_time'], + 'is_late' => false, + 'waiting_time' => 0, + ] + $input); + } + + /** + * Compute the OLA data for a ticket + * + * @param Ticket $ticket + * @param int $olas_id must exist in the database + * @param int $items_olas_id id of Item_Ola to compute + */ + public static function compute(Ticket $ticket, int $olas_id, int $items_olas_id): void + { + $item_ola = new self(); + if (!$item_ola->getFromDBByCrit([ + 'items_id' => $ticket->getID(), + 'itemtype' => $ticket::class, + 'olas_id' => $olas_id, + 'id' => $items_olas_id, + ])) { + throw new RuntimeException('Item_Ola not found for ticket #' . $ticket->getID() . ', OLA #' . $olas_id . ', items_olas_id #' . $items_olas_id); + }; + + $calendars_id = $ticket->getCalendar(); + $ola = $item_ola->getOla(); + $ola->setTicketCalendar($calendars_id); + + $item_ola_data = $item_ola->fields; + $item_ola_data['id'] = $item_ola->getID(); // for final CommonDBRelation::update + + // update waiting_time (to do before due_time) + // update waiting_time for TTR & TTO + // TTO waiting_time is added only if the OLA group is not assigned to the ticket + + // ticket status changed + $ola_need_waiting_computation = ( + $item_ola_data['ola_type'] === SLM::TTR + || ( + $item_ola_data['ola_type'] === SLM::TTO + && !$ticket->haveAGroup(CommonITILActor::ASSIGN, [$ola->fields['groups_id']]) + ) + ); + if (in_array('status', $ticket->updates) && $ola_need_waiting_computation) { + // - status changed to WAITING using $ticket->fields['begin_waiting_date'] + if ($ticket->fields['status'] == CommonITILObject::WAITING && !empty($ticket->fields['begin_waiting_date'])) { + $item_ola_data['waiting_start'] = $ticket->fields['begin_waiting_date']; + } + + // - Changing from WAITING to another 'active' status (not solved or closed) + if ( + // From WAITING + ( + $ticket->oldvalues['status'] == CommonITILObject::WAITING && !empty($item_ola_data['waiting_start']) + ) + ) { + $item_ola_data['waiting_time'] += $ola->getActiveTimeBetween( + $item_ola_data['waiting_start'], + $_SESSION["glpi_currenttime"] + ); + $item_ola_data['waiting_start'] = null; // 0 ou null ? + } + } + + // - update due_time + // update due_time (former internal_time_to_own, internal_time_to_resolve) + $item_ola_data['due_time'] = $ola->computeDate( + $item_ola_data['start_time'], + $item_ola_data['waiting_time'] + ); + + // - update end_time (if not already set) + // for TTO, endtime is when the ticket is assigned to the dedicated group. + // - except if update is triggered by a rule + if (is_null($item_ola_data['end_time'])) { + if ($item_ola_data['ola_type'] === SLM::TTO) { + if ( + // current user is in the OLA group + self::isCurrentUserInOlaGroup((int) $ola->fields['groups_id']) + + ) { + $item_ola_data['end_time'] = Session::getCurrentTime(); + } + } + + // For TTR, end_time is when the ticket is closed + // set it only if it is not already set + if ($item_ola_data['ola_type'] === SLM::TTR) { + if ($ticket->isClosed() || $ticket->isSolved()) { + $item_ola_data['end_time'] = Session::getCurrentTime(); + } + } + } + + // update current object + foreach ($item_ola_data as $field => $value) { + $item_ola->fields[$field] = $value; + } + + // - update is_late + $item_ola_data['is_late'] = (int) $item_ola->isLate($ticket); + + if (!(new Item_Ola())->update($item_ola_data)) { + throw new Exception('Failed to update item_ola'); + } + + // since dates may be changed, rebuild the levels todo + $ticket->manageOlaLevel($item_ola->fields['olas_id']); + } + + /** + * When the ola dedicated group is removed from a ticket, the ola is considered completed. + * + * The $groups_id_removed is the group id that has been removed from the ticket assignees. + * Be sure that the group was previously assigned to the ticket. + */ + public static function computeGroupAssigneeRemoval(int $tickets_id, int $groups_id_removed): void + { + /** @var DBmysql $DB */ + global $DB; + + // find all items_olas OLA related to this group & ticket + $items_olas_to_update_results = $DB->request([ + 'SELECT' => [ + Item_Ola::getTable() . '.id', + Item_Ola::getTable() . '.olas_id', + ], + 'FROM' => Item_Ola::getTable(), + 'INNER JOIN' => [ + OLA::getTable() => [ + 'FKEY' => [ + Item_Ola::getTable() => 'olas_id', + OLA::getTable() => 'id', + ], + ], + ], + 'WHERE' => [ + 'items_id' => $tickets_id, + 'itemtype' => Ticket::class, + 'groups_id' => $groups_id_removed, + 'end_time' => null, + ], + ]); + + foreach ($items_olas_to_update_results as $item_ola_row) { + $ticket = new Ticket(); + if (!$ticket->getFromDB($tickets_id)) { + throw new Exception('Ticket related to Ticket_group not found'); + } + + if (!(new self())->update( + [ + 'end_time' => Session::getCurrentTime(), + 'id' => $item_ola_row['id'], + ] + ) + ) { + throw new Exception('Failed to update end_time on Item_Ola #' . $item_ola_row['id']); + } + + Item_Ola::compute($ticket, (int) $item_ola_row['olas_id'], (int) $item_ola_row['id']); + } + } + + + public function getOla(): OLA + { + $item = $this->getConnexityItem(self::$itemtype_2, self::$items_id_2); + if ($item instanceof OLA) { + return $item; + } + + throw new RuntimeException('Linked OLA not found'); + } + + /** + * Get data from Item_Ola + linked OLA for a Ticket + * @param Ticket $ticket + * + * @return array + */ + public function getDataFromDBForTicket(Ticket $ticket): array + { + /** array ola + item_ola datas */ + $merged_data = []; + // each $item_ola_data row contains linked OLA fields + items_olas_id in 'linkid' field + $olas_data = iterator_to_array(self::getListForItem($ticket), false); + + // merge data from ola dans items_ola + foreach ($olas_data as $ola_data) { + $merged_data[] = $this->fillItemOlaData($ola_data, $ticket); + } + + return $this->sort($merged_data); + } + + /** + * @param Ticket $ticket + * @param array $olas_ids + * @return array + */ + public function getDataFromOlasIdsForTicket(Ticket $ticket, array $olas_ids): array + { + /** array ola + item_ola datas */ + $merged_data = []; + $ola = new OLA(); + + // get ola data from DB - each $item_ola_data row contains linked OLA fields + items_olas_id in 'linkid' field + $olas_in_db_data = array_values(iterator_to_array(self::getListForItem($ticket), false)); + + // complete with olas not associated to ticket - not yet in items_ola + $fetched_olas_ids = array_column($olas_in_db_data, 'id'); + $olas_not_yet_fetched = array_diff(array_values($olas_ids), $fetched_olas_ids); + $olas_missing_data = $olas_not_yet_fetched === [] ? [] : $ola->find(['id' => $olas_not_yet_fetched]); + + $olas_data = array_merge($olas_in_db_data, $olas_missing_data); + + // merge data from ola dans items_ola + foreach ($olas_data as $ola_data) { + $merged_data[] = $this->fillItemOlaData($ola_data, $ticket); + } + + return $this->sort($merged_data); + } + + private function isLate(Ticket $ticket): bool + { + $now_timestamp = Session::getCurrentTime() ? strtotime(Session::getCurrentTime()) : time(); + $due_time_timestamp = strtotime($this->fields['due_time']); + $end_time_timestamp = is_null($this->fields['end_time']) ? null : strtotime($this->fields['end_time']); + + // Ticket is WAITING : never late + if ($ticket->fields['status'] == CommonITILObject::WAITING) { + return false; + } + + // end_time is after due_time + if (!is_null($end_time_timestamp) && $end_time_timestamp > $due_time_timestamp) { + return true; + } + + // end time is not set, due_time is in the past + if (is_null($end_time_timestamp) && $due_time_timestamp < $now_timestamp) { + return true; + } + + return false; + } + + /** + * @param OLAFields $ola_data fields from ola + possibly 'linkid' field representing items_olas_id + * @param Ticket $ticket + * + * If 'linkid' is set, it will be used to populate the data from Item_Ola otherwise it will be filled with default values. + * + * @return ItemOlaData + */ + private function fillItemOlaData(array $ola_data, Ticket $ticket): array + { + $_ola = new OLA(); + $_group = new Group(); + $_group->getFromDB($ola_data['groups_id']); + $group_name = $_group->getName(); + // start with the ola data + $_merged_data = $ola_data; + + // data defaults for item_ola + $_merged_data['itemtype'] = $ticket::class; + $_merged_data['items_id'] = $ticket->getID(); + $_merged_data['olas_id'] = $ola_data['id']; + $_merged_data['start_time'] = 0; + $_merged_data['due_time'] = 0; + $_merged_data['end_time'] = 0; + $_merged_data['waiting_time'] = 0; + $_merged_data['waiting_start'] = 0; + $_merged_data['items_olas_id'] = 0; + // add data for template + $_merged_data['class'] = OLA::class; + $_merged_data['item'] = $ticket; // object, not just fields, functions used in template + $_merged_data['nextaction'] = $_ola->getNextActionForTicket($ticket, $_merged_data['type']); + $_merged_data['level'] = $_ola->getLevelFromAction($_merged_data['nextaction']); + $_merged_data['group_name'] = $group_name; + + // if linkid is set (items_olas exists), use it to populate the data + if (isset($ola_data['linkid'])) { + $item_Ola = new static(); + if (!$item_Ola->getFromDB($_merged_data['linkid'])) { + throw new LogicException('Item_Ola not found for linkid ' . $_merged_data['linkid']); + } + + $_merged_data = array_merge($_merged_data, $item_Ola->fields); + $_merged_data['items_olas_id'] = $ola_data['linkid']; + $_merged_data['olas_id'] = $ola_data['id']; + if ($ola_data['type'] !== $item_Ola->fields['ola_type']) { + throw new LogicException('inconsistent type for Item_Ola #' . $item_Ola->getID()); + } + } + + if (isset($_merged_data['id'])) { + unset($_merged_data['id']); // removed to avoid confusion with items_olas_id in template, both olas_id and items_olas_id are defined above + } + + return $_merged_data; + } + + /** + * Ola Cron Tasks + * + * - recompute ola data is_late field + * - refresh ola levels_todo + * - closes ticket (via compute()->doLevelForTicket()) + * + * update items_ola which has no end_time of tickets + * @used-by CronTask + * + * @return int 1 if at least one item_ola has been processed, 0 otherwise + */ + public static function cronOlaTicket(CronTask $cronTask): int + { + $items_olas = new static(); + $ios = $items_olas->find(['end_time' => null, 'itemtype' => Ticket::class]); + + OLA::deleteAllLevelsToDo(); // todo levels are rebuild in Item_Ola::compute() + + $processed = 0; + foreach ($ios as $item_ola) { + $itil = getItemForItemtype($item_ola['itemtype']); + if (!$itil instanceof Ticket) { + throw new RuntimeException('Item_Ola cron only works for Ticket at the moment. Implemetation needed.'); + } + $itil->getFromDB($item_ola['items_id']); + static::compute($itil, (int) $item_ola['olas_id'], (int) $item_ola['id']); + $processed++; + } + + $cronTask->setVolume($processed); + + return (int) ($processed > 0); + } + + /** + * Sort the merged data by group then type (tto, then ttr) + * + * @phpstan-param list $merged_data + * @phpstan-return list + */ + private function sort(array $merged_data): array + { + usort($merged_data, function ($line_1, $line_2) { + // group_name ordering + $groupComparison = strcmp($line_1['group_name'], $line_2['group_name']); + if ($groupComparison !== 0) { + return $groupComparison; + } + + // type ordering (priorité à tto sur ttr) + $typePriority = [SLM::TTO => 1, SLM::TTR => 2]; + return $typePriority[$line_1['type']] <=> $typePriority[$line_2['type']]; + }); + + return $merged_data; + } + + private static function isCurrentUserInOlaGroup(int $groups_id): bool + { + return in_array($groups_id, $_SESSION["glpigroups"]); + } +} diff --git a/src/Item_Rack.php b/src/Item_Rack.php index 06ead2f3335..55dd2db3fdd 100644 --- a/src/Item_Rack.php +++ b/src/Item_Rack.php @@ -177,7 +177,10 @@ private static function showItemsGraph(Rack $rack, iterable $items): void $canedit = $rack->canEdit($rack->getID()); $link = new self(); - $data = []; + $data = [ + Rack::FRONT => [], + Rack::REAR => [], + ]; //all rows; empty for ($i = (int) $rack->fields['number_units']; $i > 0; --$i) { $data[Rack::FRONT][$i] = false; diff --git a/src/LevelAgreement.php b/src/LevelAgreement.php index 669e8283199..88bab889602 100644 --- a/src/LevelAgreement.php +++ b/src/LevelAgreement.php @@ -78,9 +78,9 @@ abstract public function getAddConfirmation(): array; /** * Get table fields * - * @param int $subtype of OLA/SLA, can be SLM::TTO or SLM::TTR + * @param SLM::TTO|SLM::TTR $subtype * - * @return array of 'date' and 'sla' field names + * @return array{0: string, 1: string} 'date' and 'sla' field names */ public static function getFieldNames($subtype) { @@ -89,12 +89,12 @@ public static function getFieldNames($subtype) switch ($subtype) { case SLM::TTO: - $dateField = static::$prefixticket . 'time_to_own'; + $dateField = 'time_to_own'; $laField = static::$prefix . 's_id_tto'; break; case SLM::TTR: - $dateField = static::$prefixticket . 'time_to_resolve'; + $dateField = 'time_to_resolve'; $laField = static::$prefix . 's_id_ttr'; break; } @@ -137,13 +137,9 @@ public function post_getEmpty() $this->fields['definition_time'] = 'hour'; } + #[Override] public function showForm($ID, array $options = []) { - $rowspan = 3; - if ($ID > 0) { - $rowspan = 5; - } - // Get SLM object $slm = new SLM(); if (isset($options['parent'])) { @@ -161,86 +157,16 @@ public function showForm($ID, array $options = []) // force itemtype of parent static::$itemtype = get_class($slm); - $this->check(-1, CREATE, $options); - } - - $this->showFormHeader($options); - echo ""; - echo "" . __s('Name') . ""; - echo ""; - echo Html::input("name", ['value' => $this->fields["name"]]); - echo "" . __s('Comments') . ""; - echo " - "; - echo ""; - - echo ""; - echo "" . __s('SLM') . ""; - echo ""; - echo $slm->getLink(); - echo ""; - echo ""; - - if ($ID > 0) { - echo ""; - echo "" . __s('Last update') . ""; - echo "" . htmlescape($this->fields["date_mod"] ? Html::convDateTime($this->fields["date_mod"]) : __('Never')); - echo ""; + if ($ID == 0 || $ID == -1) { + $options[static::$items_id] = $options['parent']->fields["id"]; + $this->check(-1, CREATE, $options); + } } - echo "" . _sn('Type', 'Types', 1) . ""; - echo ""; - self::getTypeDropdown(['value' => $this->fields["type"]]); - echo ""; - echo ""; - - echo "" . __s('Maximum time') . ""; - echo ""; - Dropdown::showNumber("number_time", ['value' => $this->fields["number_time"], - 'min' => 0, - 'max' => 1000, + TemplateRenderer::getInstance()->display('/pages/service-levels/levelagreement.html.twig', [ + 'item' => $this, + 'params' => $options, ]); - $possible_values = self::getDefinitionTimeValues(); - $rand = Dropdown::showFromArray( - 'definition_time', - $possible_values, - ['value' => $this->fields["definition_time"], - 'on_change' => 'appearhideendofworking()', - ] - ); - - echo Html::scriptBlock( - <<"; - - echo ""; - echo "
" . __s('End of working day') . "
"; - echo ""; - - echo ""; - $this->showFormWarning(); - echo ""; - echo ""; - - $this->showFormButtons($options); return true; } @@ -306,7 +232,6 @@ public function getLevelFromAction($nextaction) * * since 10.0 * - * @param Ticket $ticket * @param SLM::TTO|SLM::TTR $type * * @return false|OlaLevel_Ticket|SlaLevel_Ticket @@ -388,7 +313,8 @@ function viewAddEditLa{{ instID }}{{ rand }}(item_id = -1) { } elseif ($calendar->getFromDB($slm->fields['calendars_id'])) { $link = $calendar->getLink(); } - $entries[] = [ + + $entry = [ 'itemtype' => static::class, 'id' => $val['id'], 'row_class' => 'cursor-pointer', @@ -400,9 +326,19 @@ function viewAddEditLa{{ instID }}{{ rand }}(item_id = -1) { ]), 'calendar' => $link, ]; + + // ola have a groups_id field, not sla + if ($la->isField('groups_id')) { + $group = new Group(); + $group->getFromDB($la->fields['groups_id']); + $group_name = $group->getName(); + $entry['group'] = $group_name; + } + + $entries[] = $entry; } - TemplateRenderer::getInstance()->display('components/datatable.html.twig', [ + $twig_params = [ 'datatable_id' => 'levelagreement' . $instID, 'is_tab' => true, 'nofilter' => true, @@ -424,7 +360,14 @@ function viewAddEditLa{{ instID }}{{ rand }}(item_id = -1) { 'num_displayed' => count($entries), 'container' => 'mass' . static::class . mt_rand(), ], - ]); + ]; + + // ola have a groups_id field, not sla + if ($la->isField('groups_id')) { + $twig_params['columns']['group'] = Group::getTypeName(1); + } + + TemplateRenderer::getInstance()->display('components/datatable.html.twig', $twig_params); } /** @@ -435,7 +378,12 @@ public function showRulesList() { global $DB; - $fk = static::getFieldNames($this->fields['type'])[1]; + $field = match (static::class) { + SLA::class => static::getFieldNames($this->fields['type'])[1], + OLA::class => 'olas_id', + default => throw new RuntimeException('Unexpected LevelAgreement class : ' . static::class), + }; + $rule = new RuleTicket(); $canedit = self::canUpdate(); @@ -444,7 +392,7 @@ public function showRulesList() 'DISTINCT' => true, 'FROM' => 'glpi_ruleactions', 'WHERE' => [ - 'field' => $fk, + 'field' => $field, 'value' => $this->getID(), ], ])); @@ -507,13 +455,17 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) return ''; } + /** + * @param SLM $item + * @param int $tabnum + * @param int $withtemplate + */ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { - switch (true) { - case $item instanceof SLM: - self::showForSLM($item); - break; + if ($item::class === 'SLM') { + self::showForSLM($item); } + return true; } @@ -522,33 +474,11 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ * * @param int $tickets_id * @param int $type - * @return false|iterable - * @used-by templates/components/itilobject/service_levels.html.twig + * @return false */ public function getDataForTicket($tickets_id, $type) { - global $DB; - - [, $field] = static::getFieldNames($type); - - $iterator = $DB->request([ - 'SELECT' => [static::getTable() . '.id'], - 'FROM' => static::getTable(), - 'INNER JOIN' => [ - 'glpi_tickets' => [ - 'FKEY' => [ - static::getTable() => 'id', - 'glpi_tickets' => $field, - ], - ], - ], - 'WHERE' => ['glpi_tickets.id' => $tickets_id], - 'LIMIT' => 1, - ]); - - if (count($iterator)) { - return self::getFromIter($iterator); - } + Toolbox::deprecated('The `' . __FUNCTION__ . '` method is deprecated, use Ticket::getOlasData() | Ticket::getSlasData().'); return false; } @@ -908,68 +838,7 @@ public function prepareInputForUpdate($input) } /** - * Add a level to do for a ticket - * - * Add an entry in slalevels_tickets | olalevels_tickets table - * The level is set by $levels_id parameter or the current level set in slalevels_id_ttr | olalevels_id_ttr (if set) - * - * @param Ticket $ticket Ticket object - * @param int $levels_id SlaLevel or OlaLevel ID - * - * @return void - **/ - public function addLevelToDo(Ticket $ticket, $levels_id = 0) - { - $pre = static::$prefix; - - if (!$levels_id && isset($ticket->fields[$pre . 'levels_id_ttr'])) { - $levels_id = $ticket->fields[$pre . "levels_id_ttr"]; - } - - if ($levels_id) { - $toadd = []; - - // Compute start date - if ($pre === "ola") { - // OLA have their own start date which is set when the OLA is added to the ticket - if ( - (int) $this->fields['type'] === SLM::TTO - && $ticket->fields['ola_tto_begin_date'] !== null - ) { - $date_field = "ola_tto_begin_date"; - } elseif ( - (int) $this->fields['type'] === SLM::TTR - && $ticket->fields['ola_ttr_begin_date'] !== null - ) { - $date_field = "ola_ttr_begin_date"; - } else { - // Fall back to default date in case the specific date fields - // are not set (which may be the case for tickets created - // before their addition) - $date_field = 'date'; - } - } else { - // SLA are based on the ticket opening date - $date_field = 'date'; - } - - $date = $this->computeExecutionDate( - $ticket->fields[$date_field], - $levels_id, - $ticket->fields[$pre . '_waiting_duration'] - ); - if ($date !== null) { - $toadd['date'] = $date; - $toadd[$pre . 'levels_id'] = $levels_id; - $toadd['tickets_id'] = $ticket->fields["id"]; - $levelticket = getItemForItemtype(static::$levelticketclass); - $levelticket->add($toadd); - } - } - } - - /** - * remove a level to do for a ticket + * Remove all levels to do for a ticket * * @param Ticket $ticket object * @@ -995,33 +864,6 @@ public static function deleteLevelsToDo(Ticket $ticket) } } - public function cleanDBonPurge() - { - global $DB; - - // Clean levels - $fk = getForeignKeyFieldForItemType(static::class); - $level = getItemForItemtype(static::$levelclass); - $level->deleteByCriteria([$fk => $this->getID()]); - - // Update tickets : clean SLA/OLA - [, $laField] = static::getFieldNames($this->fields['type']); - $iterator = $DB->request([ - 'SELECT' => 'id', - 'FROM' => 'glpi_tickets', - 'WHERE' => [$laField => $this->fields['id']], - ]); - - if (count($iterator)) { - $ticket = new Ticket(); - foreach ($iterator as $data) { - $ticket->deleteLevelAgreement(static::class, $data['id'], $this->fields['type']); - } - } - - Rule::cleanForItemAction($this); - } - public function post_clone($source, $history) { // Clone levels @@ -1055,7 +897,7 @@ public function getLevelTicketClass(): string } /** - * Remove level of previously assigned level agreements for a given ticket + * Remove levels todo of the current level agreement for a given ticket * * @param int $tickets_id * diff --git a/src/NotificationEventMailing.php b/src/NotificationEventMailing.php index db8b58a04c9..78ce5a502d9 100644 --- a/src/NotificationEventMailing.php +++ b/src/NotificationEventMailing.php @@ -339,6 +339,13 @@ public static function send(array $data) $custom_height = intval($hmatches[1]); } + if ($custom_width !== null && $custom_width <= 0) { + $custom_width = null; + } + if ($custom_height !== null && $custom_height <= 0) { + $custom_height = null; + } + if ($custom_height === null && $custom_width === null) { // no custom size, use original file $image_path = GLPI_DOC_DIR . "/" . $doc->fields['filepath']; @@ -357,17 +364,27 @@ public static function send(array $data) $initial_height = $img_infos[1]; if ($custom_height === null) { - $custom_height = $initial_height * $custom_width / $initial_width; + $custom_height = max( + 1, + (int) round($initial_height * $custom_width / $initial_width) + ); } else { - $custom_width = $initial_width * $custom_height / $initial_height; + $custom_width = max( + 1, + (int) round($initial_width * $custom_height / $initial_height) + ); } } - $image_path = Document::getResizedImagePath( - GLPI_DOC_DIR . "/" . $doc->fields['filepath'], - $custom_width, - $custom_height - ); + if ($custom_width <= 0 || $custom_height <= 0) { + $image_path = GLPI_DOC_DIR . "/" . $doc->fields['filepath']; + } else { + $image_path = Document::getResizedImagePath( + GLPI_DOC_DIR . "/" . $doc->fields['filepath'], + $custom_width, + $custom_height + ); + } } $mail->embedFromPath($image_path, $doc->fields['filename']); diff --git a/src/NotificationTargetCommonITILObject.php b/src/NotificationTargetCommonITILObject.php index 7ab4b8a2dbd..70ecf1160a2 100644 --- a/src/NotificationTargetCommonITILObject.php +++ b/src/NotificationTargetCommonITILObject.php @@ -999,25 +999,29 @@ public function addAdditionalTargets($event = '') $this->addTarget(Notification::AUTHOR, _n('Requester', 'Requesters', 1)); $this->addTarget(Notification::RECIPIENT, __('Writer')); } elseif ($event != 'alertnotclosed') { + $item_type_name = $this->obj instanceof CommonITILObject + ? $this->obj->getTypeName(1) + : _n('Item', 'Items', 1); + $this->addTarget(Notification::RECIPIENT, __('Writer')); $this->addTarget(Notification::SUPPLIER, Supplier::getTypeName(1)); $this->addTarget( Notification::SUPERVISOR_ASSIGN_GROUP, - __('Manager of the group in charge of the ticket') + sprintf(__('Manager of the group in charge of the %s'), $item_type_name) ); $this->addTarget( Notification::ASSIGN_GROUP_WITHOUT_SUPERVISOR, - __("Group in charge of the ticket except manager users") + sprintf(__("Group in charge of the %s except manager users"), $item_type_name) ); $this->addTarget(Notification::SUPERVISOR_REQUESTER_GROUP, __('Requester group manager')); $this->addTarget( Notification::REQUESTER_GROUP_WITHOUT_SUPERVISOR, __("Requester group except manager users") ); - $this->addTarget(Notification::ASSIGN_TECH, __('Technician in charge of the ticket')); + $this->addTarget(Notification::ASSIGN_TECH, sprintf(__('Technician in charge of the %s'), $item_type_name)); $this->addTarget(Notification::REQUESTER_GROUP, _n('Requester group', 'Requester groups', 1)); $this->addTarget(Notification::AUTHOR, _n('Requester', 'Requesters', 1)); - $this->addTarget(Notification::ASSIGN_GROUP, __('Group in charge of the ticket')); + $this->addTarget(Notification::ASSIGN_GROUP, sprintf(__('Group in charge of the %s'), $item_type_name)); $this->addTarget(Notification::OBSERVER_GROUP, _n('Observer group', 'Observer groups', 1)); $this->addTarget(Notification::OBSERVER, _n('Observer', 'Observers', 1)); $this->addTarget(Notification::SUPERVISOR_OBSERVER_GROUP, __('Observer group manager')); @@ -1204,7 +1208,7 @@ public function addDataForTemplate($event, $options = []) $objettype = strtolower($this->obj::class); // Get data from ITIL objects - if ($event != 'alertnotclosed') { + if ($event != 'alertnotclosed' && !is_null($this->obj)) { $this->data = $this->getDataForObject($this->obj, $options); } else { if ( @@ -1218,6 +1222,7 @@ public function addDataForTemplate($event, $options = []) } $item = getItemForItemtype($objettype); if ($item instanceof CommonITILObject) { + /** @var T $item */ $objettypes = Toolbox::strtolower(getPlural($objettype)); $items = []; foreach ($options['items'] as $object) { @@ -1257,9 +1262,9 @@ public function addDataForTemplate($event, $options = []) /** * Get data from an item * - * @param CommonITILObject $item Object instance - * @param array $options Options - * @param bool $simple (false by default) + * @param T $item Object instance + * @param array $options Options + * @param bool $simple (false by default) * * @return array **/ @@ -1906,7 +1911,6 @@ public function getDataForObject(CommonITILObject $item, array $options, $simple $data['timelineitems'][] = $tmptimelineitem; } - /** @var CommonITILObject $item */ $inquest = $item::getSatisfactionClassInstance(); if ($inquest !== null) { $data['##satisfaction.type##'] = ''; diff --git a/src/NotificationTargetProblem.php b/src/NotificationTargetProblem.php index 2e8d1975166..b45fbecd377 100644 --- a/src/NotificationTargetProblem.php +++ b/src/NotificationTargetProblem.php @@ -57,7 +57,7 @@ public function getEvents() } #[Override] - public function getDataForObject(CommonDBTM $item, array $options, $simple = false) + public function getDataForObject(CommonITILObject $item, array $options, $simple = false) { // Common ITIL data $data = parent::getDataForObject($item, $options, $simple); diff --git a/src/NotificationTargetTicket.php b/src/NotificationTargetTicket.php index bb0c2e36d56..9029ba7994e 100644 --- a/src/NotificationTargetTicket.php +++ b/src/NotificationTargetTicket.php @@ -151,7 +151,7 @@ public function getEvents() } #[Override] - public function getDataForObject(CommonDBTM $item, array $options, $simple = false) + public function getDataForObject(CommonITILObject $item, array $options, $simple = false) { // Common ITIL data $data = parent::getDataForObject($item, $options, $simple); @@ -218,20 +218,70 @@ public function getDataForObject(CommonDBTM $item, array $options, $simple = fal } $data['##ticket.sla##'] = $data['##ticket.sla_ttr##']; + // OLA data + $data['ola_tto'] = []; + $olas_tto = $item->getOlasTTOData(); + + $data['ola_ttr'] = []; + $olas_ttr = $item->getOlasTTRData(); + + // backward compatibility ola_tto/ttr + // ticket.ola_tto & ticket.ola_ttr field are replaced by an array of OLA data, see below $data['##ticket.ola_tto##'] = ''; - if ($item->fields['olas_id_tto']) { + if (isset($item->fields['olas_id_tto']) && is_numeric($item->fields['olas_id_tto'])) { $data['##ticket.ola_tto##'] = Dropdown::getDropdownName( 'glpi_olas', $item->fields['olas_id_tto'] ); } $data['##ticket.ola_ttr##'] = ''; - if ($item->fields['olas_id_ttr']) { + if (isset($item->fields['olas_id_ttr']) && is_numeric($item->fields['olas_id_ttr'])) { $data['##ticket.ola_ttr##'] = Dropdown::getDropdownName( 'glpi_olas', $item->fields['olas_id_ttr'] ); } + $_separator = ' / '; + $_fn_remove_last_separator = static function ($ola_names) use ($_separator) { + if (str_ends_with($ola_names, $_separator)) { + return substr($ola_names, 0, -strlen($_separator)); + } + return $ola_names; + }; + + // OLA TTO/TTR data - new version (multiple OLA per ticket) + foreach ($olas_tto as $ola_tto) { + $tmp['##ticket.ola_tto.name##'] = $ola_tto['name']; + $tmp['##ticket.ola_tto.comment##'] = $ola_tto['comment']; + $tmp['##ticket.ola_tto.group##'] = $ola_tto['group_name']; + $tmp['##ticket.ola_tto.start_time##'] = $ola_tto['start_time']; + $tmp['##ticket.ola_tto.due_time##'] = $ola_tto['due_time']; + $tmp['##ticket.ola_tto.end_time##'] = $ola_tto['end_time']; + $tmp['##ticket.ola_tto.waiting_time##'] = $ola_tto['waiting_time']; + + // backward compatibility + $data['##ticket.ola_tto##'] .= $ola_tto['name'] . $_separator; + // new format + $data['ola_tto'][] = $tmp; + + } + $data['##ticket.ola_tto##'] = $_fn_remove_last_separator($data['##ticket.ola_tto##']); + + foreach ($olas_ttr as $ola_ttr) { + $tmp['##ticket.ola_ttr.name##'] = $ola_ttr['name']; + $tmp['##ticket.ola_ttr.comment##'] = $ola_ttr['comment']; + $tmp['##ticket.ola_ttr.group##'] = $ola_ttr['group_name']; + $tmp['##ticket.ola_ttr.start_time##'] = $ola_ttr['start_time']; + $tmp['##ticket.ola_ttr.due_time##'] = $ola_ttr['due_time']; + $tmp['##ticket.ola_ttr.end_time##'] = $ola_ttr['end_time']; + $tmp['##ticket.ola_ttr.waiting_time##'] = $ola_ttr['waiting_time']; + + // backward compatibility + $data['##ticket.ola_ttr##'] .= $ola_ttr['name'] . $_separator; + + $data['ola_ttr'][] = $tmp; + } + $data['##ticket.ola_ttr##'] = $_fn_remove_last_separator($data['##ticket.ola_ttr##']); $data['##ticket.location##'] = ''; if ($item->fields['locations_id']) { @@ -525,16 +575,23 @@ public function getTags() __('SLA'), __('Time to resolve') ), - 'ticket.ola_tto' => sprintf( - __('%1$s / %2$s'), - __('OLA'), - __('Internal time to own') - ), - 'ticket.ola_ttr' => sprintf( - __('%1$s / %2$s'), - __('OLA'), - __('Internal time to resolve') - ), + + 'ticket.ola_tto.name' => __('OLA name'), + 'ticket.ola_tto.comment' => __('OLA comment'), + 'ticket.ola_tto.group' => __('Group assigned to OLA'), + 'ticket.ola_tto.start_time' => __('OLA start time'), + 'ticket.ola_tto.due_time' => __('OLA due time'), + 'ticket.ola_tto.end_time' => __('OLA end time'), + 'ticket.ola_tto.waiting_time' => __('OLA waiting duration'), + + 'ticket.ola_ttr.name' => __('OLA name'), + 'ticket.ola_ttr.comment' => __('OLA comment'), + 'ticket.ola_ttr.group' => __('Group assigned to OLA'), + 'ticket.ola_ttr.start_time' => __('OLA start time'), + 'ticket.ola_ttr.due_time' => __('OLA due time'), + 'ticket.ola_ttr.end_time' => __('OLA end time'), + 'ticket.ola_ttr.waiting_time' => __('OLA waiting duration'), + 'ticket.externalid' => __('External ID'), 'ticket.requesttype' => RequestType::getTypeName(1), 'ticket.itemtype' => __('Item type'), diff --git a/src/OLA.php b/src/OLA.php index c15409199ee..af1806497fd 100644 --- a/src/OLA.php +++ b/src/OLA.php @@ -36,31 +36,168 @@ /** * OLA Class * @since 9.2 + * @phpstan-type OLAFields array{id: int, name: string, entities_id: int, is_recursive: int, type: SLM::TTR|SLM::TTO, comment: string, number_time: int, use_ticket_calendar: int, calendars_id: int, date_mod: string, definition_time: string, end_of_working_day: int, date_creation: string, slms_id: int, groups_id: int} + * @property OLAFields $fields **/ class OLA extends LevelAgreement { - protected static string $prefix = 'ola'; - protected static string $prefixticket = 'internal_'; - protected static $levelclass = 'OlaLevel'; - protected static $levelticketclass = 'OlaLevel_Ticket'; - protected static array $forward_entity_to = ['OlaLevel']; + protected static string $prefix = 'ola'; + protected static $levelclass = OlaLevel::class; + protected static $levelticketclass = OlaLevel_Ticket::class; + protected static array $forward_entity_to = [OlaLevel::class]; + /** + * @param array $_olas_id + * @return array{0: array, 1: array} + */ + public static function splitIdsByType(array $_olas_id): array + { + if ($_olas_id === []) { + return [SLM::TTR => [],SLM::TTO => []]; + } + $_ola = new static(); + $all_ids_ttr = array_column($_ola->find(['type' => SLM::TTR]), 'id'); + $all_ids_tto = array_column($_ola->find(['type' => SLM::TTO]), 'id'); + + $input[SLM::TTR] = array_intersect($_olas_id, $all_ids_ttr); + $input[SLM::TTO] = array_intersect($_olas_id, $all_ids_tto); + + return $input; + } + + #[Override] + public function prepareInputForAdd($input) + { + $groups_id = (int) ($input['groups_id'] ?? 0); + + return $this->validateGroupInput($groups_id) ? parent::prepareInputForAdd($input) : false; + } + + #[Override] + public function prepareInputForUpdate($input) + { + $groups_id = (int) ($input['groups_id'] ?? 0); + + return !isset($input['groups_id']) || $this->validateGroupInput($groups_id) + ? parent::prepareInputForUpdate($input) + : false; + } + + + /** + * Remove OLA associations + * + * - remove ola's levels + * - remove Item_Olas (deleteLevelAgreement()) + * - OlaLevel_Ticket (deleteLevelAgreement() (levels todo)) + * - remove rules + */ + public function cleanDBonPurge() + { + // Clean levels + $ola_fk = getForeignKeyFieldForItemType(static::class); + /** @var OlaLevel $level */ + $level = getItemForItemtype(static::$levelclass); + $level->deleteByCriteria([$ola_fk => $this->getID()]); + + // Clean levels todo + $ticket = new Ticket(); + $ticket->deleteLevelAgreement(static::class, $this->getID(), $this->fields['type']); + + Rule::cleanForItemAction($this); + } + + #[Override()] + public static function getFieldNames($subtype) + { + throw new LogicException(__FUNCTION__ . '() is not supported by OLA - no olas field in tickets - fix the code'); + } + + /** + * Add a level to do for a ticket + * + * Add an entry in slalevels_tickets | olalevels_tickets table + * The level is set by $levels_id parameter + * + * @param int $levels_id + * @param int $olas_id + * + * @return void + **/ + public function addLevelToDo(Ticket $ticket, $levels_id, $olas_id) + { + $items_ola = new Item_Ola(); + if (!$items_ola->getFromDBByCrit(['olas_id' => $olas_id, 'items_id' => $ticket->fields['id'], 'itemtype' => Ticket::class])) { + throw new LogicException('Item_ola not found'); + } + $ola = new OLA(); + if (!$ola->getFromDB($olas_id)) { + throw new LogicException('OLA not found #' . $olas_id); + } + + $start_date = $items_ola->fields['start_time']; + $waiting_duration = $items_ola->fields['waiting_time']; + + $date = $this->computeExecutionDate( + $start_date, + $levels_id, + $waiting_duration + ); + + $_ticket = new Ticket(); + if ( + $_ticket->getFromDB($ticket->getID()) + && $this->levelCanBeAddedInLevelsTodo($ticket, $items_ola) + && $date !== null + ) { + $toadd = []; + $toadd['date'] = $date; + $toadd['olalevels_id'] = $levels_id; + $toadd['tickets_id'] = $ticket->fields["id"]; + /** @var OlaLevel_Ticket $levelticket */ + $levelticket = getItemForItemtype(static::$levelticketclass); + $levelticket->add($toadd); + } + } + + #[Override] + public static function deleteLevelsToDo(Ticket $ticket) + { + /** @var OlaLevel_Ticket $levelticket */ + $levelticket = getItemForItemtype(static::$levelticketclass); + $levelticket->deleteByCriteria(['tickets_id' => $ticket->fields['id']]); + } + + /** + * remove all levels to do + **/ + public static function deleteAllLevelsToDo(): void + { + /** @var OlaLevel_Ticket $levelticket */ + $levelticket = getItemForItemtype(static::$levelticketclass); + $levelticket->deleteByCriteria([new QueryExpression('true')]); + } + + #[Override] public static function getTypeName($nb = 0) { // Acronym, no plural return __('OLA'); } + #[Override] public static function getSectorizedDetails(): array { return ['config', SLM::class, self::class]; } + #[Override] public static function getLogDefaultServiceName(): string { return 'setup'; } + #[Override] public static function getIcon() { return SLM::getIcon(); @@ -81,4 +218,60 @@ public function getAddConfirmation(): array __("Escalations defined in the OLA will be triggered under this new date."), ]; } + + /** + * Check if the level can be added in levels todo + * + * It means that the ola is not completed + */ + private function levelCanBeAddedInLevelsTodo(Ticket $ticket, Item_Ola $items_ola): bool + { + if ( + $ticket->isDeleted() + || $ticket->fields['status'] == CommonITILObject::CLOSED + || $ticket->fields['status'] == CommonITILObject::SOLVED + || !is_null($items_ola->fields['end_time']) + ) { + return false; + } + + return true; + } + + /** + * Validate groups_id value + * + * - group must be set + * - group must be allowed to be assigned to a ticket + */ + private function validateGroupInput(int $groups_id): bool + { + if (0 === $groups_id) { + Session::addMessageAfterRedirect( + __s('You must select a group to associate with the OLA.'), + false, + ERROR + ); + return false; + } + + if (!$this->canGroupBeAssociated($groups_id)) { + Session::addMessageAfterRedirect( + sprintf( + __s('The group #%d is not allowed to be associated with an OLA. group.is_assign must be set to 1'), + $groups_id + ), + false, + ERROR + ); + return false; + } + + return true; + } + + private function canGroupBeAssociated(int $groups_id): bool + { + return (new Group())->getFromDBByCrit(['id' => $groups_id, 'is_assign' => 1]); + } } diff --git a/src/OlaLevel_Ticket.php b/src/OlaLevel_Ticket.php index 8a1458860d7..a05962df44c 100644 --- a/src/OlaLevel_Ticket.php +++ b/src/OlaLevel_Ticket.php @@ -38,9 +38,12 @@ */ -/// Class OLALevel +/** + * Main purpose of this table/object is to store temporary OLA levels for tickets while replaying them. + */ class OlaLevel_Ticket extends CommonDBTM { + #[Override] public static function getTypeName($nb = 0) { return __('OLA level for Ticket'); @@ -49,10 +52,10 @@ public static function getTypeName($nb = 0) /** * Retrieve an item from the database * - * @param int $ID ID of the item to get - * @param SLM::TTR|SLM::TTO $olaType * * @since 9.1 2 mandatory parameters + * @param int $ID ID of the item to get + * @param SLM::TTR|SLM::TTO $olaType * * @return bool **/ @@ -93,10 +96,10 @@ public function getFromDBForTicket($ID, $olaType) /** * Delete entries for a ticket * - * @param int $tickets_id Ticket ID - * @param SLM::TTR|SLM::TTO $olaType Type of OLA * * @since 9.1 2 parameters mandatory + * @param int $tickets_id Ticket ID + * @param SLM::TTR|SLM::TTO $olaType Type of OLA * * @return void **/ @@ -150,168 +153,128 @@ public static function cronInfo($name) } /** - * Cron for ticket's automatic close + * Execute a specific OLAlevel for a ticket and add next level to todo * - * @param $task : CronTask object + * @since 9.1 2 parameters mandatory + * @since 11.? $olaType parameter removed + * @param array{id: int, tickets_id: int, olalevels_id:int, olas_id: int} $data data of an entry of olalevels_tickets * - * @return int (0 : nothing done - 1 : done) - * @used-by CronTask + * @return void **/ - public static function cronOlaTicket(CronTask $task) + public static function doLevelForTicket(array $data) { - global $DB; + $ticket = new Ticket(); + $olalevelticket = new self(); + $items_ola = new Item_Ola(); - $tot = 0; - $now = Session::getCurrentTime(); + // cleanup, remove unrelevant db entries + // - ticket not found (or deleted) + if (!$ticket->getFromDB($data['tickets_id']) || $ticket->isDeleted()) { + $olalevelticket->delete(['id' => $data['id']]); - $iterator = $DB->request([ - 'SELECT' => [ - 'glpi_olalevels_tickets.*', - 'glpi_olas.type AS type', - ], - 'FROM' => 'glpi_olalevels_tickets', - 'LEFT JOIN' => [ - 'glpi_olalevels' => [ - 'ON' => [ - 'glpi_olalevels_tickets' => 'olalevels_id', - 'glpi_olalevels' => 'id', - ], - ], - 'glpi_olas' => [ - 'ON' => [ - 'glpi_olalevels' => 'olas_id', - 'glpi_olas' => 'id', - ], - ], - ], - 'WHERE' => [ - 'glpi_olalevels_tickets.date' => ['<', $now], - ], - ]); + return; + } - foreach ($iterator as $data) { - $tot++; - self::doLevelForTicket($data, $data['type']); + // - ticket is closed + if ($ticket->fields['status'] == CommonITILObject::CLOSED) { + // Drop line when status is closed + $olalevelticket->delete(['id' => $data['id']]); + + return; } - $task->setVolume($tot); - return ($tot > 0 ? 1 : 0); - } + if ($ticket->fields['status'] == CommonITILObject::SOLVED) { + // If status = solved : keep the line in case of solution not validated - /** - * Do a specific OLAlevel for a ticket - * - * @param array $data data of an entry of olalevels_tickets - * @param SLM::TTR|SLM::TTO $olaType Type of OLA - * - * @since 9.1 2 parameters mandatory - * - * @return void - **/ - public static function doLevelForTicket(array $data, $olaType) - { - $ticket = new Ticket(); - $olalevelticket = new self(); + return; + } + + // - no associated ola not found + if (!$items_ola->getFromDBByCrit([ + 'olas_id' => (int) $data['olas_id'], + 'items_id' => (int) $ticket->fields['id'], + 'itemtype' => Ticket::class, + ])) { + $olalevelticket->delete(['id' => $data['id']]); + + return; + } + + // No execution for tto if ticket has been taken into account + if ($items_ola->fields['end_time']) { + $olalevelticket->delete(['id' => $data['id']]); + + return; + } + + // search all actors of a ticket + foreach ($ticket->getUsers(CommonITILActor::REQUESTER) as $user) { + $ticket->fields['_users_id_requester'][] = $user['users_id']; + } + foreach ($ticket->getUsers(CommonITILActor::ASSIGN) as $user) { + $ticket->fields['_users_id_assign'][] = $user['users_id']; + } + foreach ($ticket->getUsers(CommonITILActor::OBSERVER) as $user) { + $ticket->fields['_users_id_observer'][] = $user['users_id']; + } + + foreach ($ticket->getGroups(CommonITILActor::REQUESTER) as $group) { + $ticket->fields['_groups_id_requester'][] = $group['groups_id']; + } + foreach ($ticket->getGroups(CommonITILActor::ASSIGN) as $group) { + $ticket->fields['_groups_id_assign'][] = $group['groups_id']; + } + foreach ($ticket->getGroups(CommonITILActor::OBSERVER) as $group) { + $ticket->fields['_groups_id_observer'][] = $group['groups_id']; + } + + foreach ($ticket->getSuppliers(CommonITILActor::ASSIGN) as $supplier) { + $ticket->fields['_suppliers_id_assign'][] = $supplier['suppliers_id']; + } + + $olalevel = new OlaLevel(); + $ola = new OLA(); + + $input = [ + 'id' => $ticket->getID(), + '_auto_update' => true, + ]; - // existing ticket and not deleted if ( - $ticket->getFromDB($data['tickets_id']) - && !$ticket->isDeleted() + $olalevel->getRuleWithCriteriasAndActions($data['olalevels_id'], true, true) + && $ola->getFromDB($data['olas_id']) ) { - // search all actors of a ticket - foreach ($ticket->getUsers(CommonITILActor::REQUESTER) as $user) { - $ticket->fields['_users_id_requester'][] = $user['users_id']; - } - foreach ($ticket->getUsers(CommonITILActor::ASSIGN) as $user) { - $ticket->fields['_users_id_assign'][] = $user['users_id']; - } - foreach ($ticket->getUsers(CommonITILActor::OBSERVER) as $user) { - $ticket->fields['_users_id_observer'][] = $user['users_id']; + $doit = true; + if (count($olalevel->criterias)) { + $doit = $olalevel->checkCriterias($ticket->fields); } - - foreach ($ticket->getGroups(CommonITILActor::REQUESTER) as $group) { - $ticket->fields['_groups_id_requester'][] = $group['groups_id']; - } - foreach ($ticket->getGroups(CommonITILActor::ASSIGN) as $group) { - $ticket->fields['_groups_id_assign'][] = $group['groups_id']; - } - foreach ($ticket->getGroups(CommonITILActor::OBSERVER) as $group) { - $ticket->fields['_groups_id_observer'][] = $group['groups_id']; - } - - foreach ($ticket->getSuppliers(CommonITILActor::ASSIGN) as $supplier) { - $ticket->fields['_suppliers_id_assign'][] = $supplier['suppliers_id']; + // Process rules + if ($doit) { + $input = $olalevel->executeActions($input, [], $ticket->fields); } + } - $olalevel = new OlaLevel(); - $ola = new OLA(); - // Check if ola datas are OK - [, $olaField] = OLA::getFieldNames($olaType); - if (($ticket->fields[$olaField] > 0)) { - if ($ticket->fields['status'] == CommonITILObject::CLOSED) { - // Drop line when status is closed - $olalevelticket->delete(['id' => $data['id']]); - } elseif ($ticket->fields['status'] != CommonITILObject::SOLVED) { - // No execution if ticket has been taken into account - if ( - !(($olaType == SLM::TTO) - && ($ticket->fields['takeintoaccount_delay_stat'] > 0)) - ) { - // If status = solved : keep the line in case of solution not validated - $input = [ - 'id' => $ticket->getID(), - '_auto_update' => true, - ]; - - if ( - $olalevel->getRuleWithCriteriasAndActions($data['olalevels_id'], true, true) - && $ola->getFromDB($ticket->fields[$olaField]) - ) { - $doit = true; - if (count($olalevel->criterias)) { - $doit = $olalevel->checkCriterias($ticket->fields); - } - // Process rules - if ($doit) { - $input = $olalevel->executeActions($input, [], $ticket->fields); - } - } - - // Put next level in todo list - if ( - $next = $olalevel->getNextOlaLevel( - $ticket->fields[$olaField], - $data['olalevels_id'] - ) - ) { - $ola->addLevelToDo($ticket, $next); - } - // Action done : drop the line - $olalevelticket->delete(['id' => $data['id']]); - - $ticket->update($input); - } else { - // Drop line - $olalevelticket->delete(['id' => $data['id']]); - } - } - } else { - // Drop line - $olalevelticket->delete(['id' => $data['id']]); - } - } else { - // Drop line - $olalevelticket->delete(['id' => $data['id']]); + // Put next level in todo list + if ( + $next = $olalevel->getNextOlaLevel( + $data['olas_id'], + $data['olalevels_id'] + ) + ) { + $ola->addLevelToDo($ticket, $next, $data['olas_id']); } + + $olalevelticket->delete(['id' => $data['id']]); + $ticket->update($input); } /** * Replay all task needed for a specific ticket * - * @param int $tickets_id Ticket ID - * @param SLM::TTR|SLM::TTO $olaType Type of ola * * @since 9.1 2 parameters mandatory + * @param int $tickets_id Ticket ID + * @param SLM::TTR|SLM::TTO $olaType Type of ola * * @return void */ @@ -321,7 +284,10 @@ public static function replayForTicket($tickets_id, $olaType) $now = Session::getCurrentTime(); $criteria = [ - 'SELECT' => 'glpi_olalevels_tickets.*', + 'SELECT' => [ + 'glpi_olalevels_tickets.*', + 'glpi_olas.id AS olas_id', + ], 'FROM' => 'glpi_olalevels_tickets', 'LEFT JOIN' => [ 'glpi_olalevels' => [ @@ -354,7 +320,7 @@ public static function replayForTicket($tickets_id, $olaType) // Possible infinite loop. Trying to apply exact same SLA assignment. break; } - self::doLevelForTicket($data, $olaType); + self::doLevelForTicket($data); $last_escalation = $data['id']; } } while ($number === 1); diff --git a/src/Rule.php b/src/Rule.php index e6d59e88a69..c645f39d6bf 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -3293,7 +3293,6 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb, $item::class); case SLA::class: - case OLA::class: if ($_SESSION['glpishow_count_on_tabs']) { $nb = countElementsInTable( 'glpi_ruleactions', @@ -3304,6 +3303,17 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) } return self::createTabEntry(self::getTypeName($nb), $nb, $item::class); + case OLA::class: + if ($_SESSION['glpishow_count_on_tabs']) { + $nb = countElementsInTable( + 'glpi_ruleactions', + [ 'field' => 'olas_id', + 'value' => $item->getID(), + ] + ); + } + return self::createTabEntry(self::getTypeName($nb), $nb, $item::class); + default: if ($item instanceof self) { $ong = []; diff --git a/src/RuleCommonITILObject.php b/src/RuleCommonITILObject.php index 133f93b6a99..b7d60b6212d 100644 --- a/src/RuleCommonITILObject.php +++ b/src/RuleCommonITILObject.php @@ -338,8 +338,8 @@ public function executeActions($output, $params, array $input = []) $output["items_id"] = []; } $output["items_id"][Appliance::class][] = $value; - } else { - $output[$actions[$action->fields["field"]]["appendto"]][] = $value; + } elseif (isset($actions[$action->fields["field"]]["appendto"])) { + $output[ $actions[$action->fields["field"]]["appendto"] ] [] = $value; } // Special case of users_id_requester diff --git a/src/RuleTicket.php b/src/RuleTicket.php index e659e5ffe76..59a4ea19d22 100644 --- a/src/RuleTicket.php +++ b/src/RuleTicket.php @@ -70,12 +70,10 @@ public function executeActions($output, $params, array $input = []) break; case "assign": - // Special case of slas_id_ttr & slas_id_tto & olas_id_ttr & olas_id_tto + // slas_id_ttr & slas_id_tto if ( $action->fields["field"] === 'slas_id_ttr' || $action->fields["field"] === 'slas_id_tto' - || $action->fields["field"] === 'olas_id_ttr' - || $action->fields["field"] === 'olas_id_tto' ) { $output['_' . $action->fields["field"]] = $action->fields["value"]; } @@ -108,6 +106,13 @@ public function executeActions($output, $params, array $input = []) $output["_projects_id"][] = $value; } + // olas_id - assign an ola + if ($action->fields["field"] === 'olas_id') { + $output['_la_update'] = true; + $output['_la_append'] = true; + $output['_olas_id'][] = $action->fields["value"]; + } + break; case 'fromuser': @@ -248,28 +253,14 @@ public function getCriterias() $criterias['slas_id_tto']['type'] = 'dropdown'; $criterias['slas_id_tto']['condition'] = ['glpi_slas.type' => SLM::TTO]; - $criterias['olas_id_ttr']['table'] = 'glpi_olas'; - $criterias['olas_id_ttr']['field'] = 'name'; - $criterias['olas_id_ttr']['name'] = sprintf( - __('%1$s %2$s'), - __('OLA'), - __('Time to resolve') - ); - $criterias['olas_id_ttr']['linkfield'] = 'olas_id_ttr'; - $criterias['olas_id_ttr']['type'] = 'dropdown'; - $criterias['olas_id_ttr']['condition'] = ['glpi_olas.type' => SLM::TTR]; - - $criterias['olas_id_tto']['table'] = 'glpi_olas'; - $criterias['olas_id_tto']['field'] = 'name'; - $criterias['olas_id_tto']['name'] = sprintf( - __('%1$s %2$s'), - __('OLA'), - __('Time to own') - ); - $criterias['olas_id_tto']['linkfield'] = 'olas_id_tto'; - $criterias['olas_id_tto']['type'] = 'dropdown'; - $criterias['olas_id_tto']['condition'] = ['glpi_olas.type' => SLM::TTO]; + // associated ola + $criterias['_olas_id_rule_criteria']['type'] = 'dropdown'; + $criterias['_olas_id_rule_criteria']['table'] = 'glpi_olas'; + $criterias['_olas_id_rule_criteria']['field'] = 'name'; + $criterias['_olas_id_rule_criteria']['name'] = __('OLA'); + $criterias['_olas_id_rule_criteria']['linkfield'] = '_olas_id'; + // requester location $criterias['_locations_id_of_requester']['table'] = 'glpi_locations'; $criterias['_locations_id_of_requester']['field'] = 'completename'; $criterias['_locations_id_of_requester']['name'] = __('Requester location'); @@ -329,7 +320,7 @@ public function getActions() $actions['slas_id_ttr']['condition'] = ['glpi_slas.type' => SLM::TTR]; // empty ttr - $actions['time_to_resolve']['name'] = __('Time to resolve'); + $actions['time_to_resolve']['name'] = __('Remove Time To Resolve value'); $actions['time_to_resolve']['type'] = 'yesno'; $actions['time_to_resolve']['force_actions'] = ['delete']; @@ -346,43 +337,17 @@ public function getActions() $actions['slas_id_tto']['condition'] = ['glpi_slas.type' => SLM::TTO]; // empty sla tto - $actions['time_to_own']['name'] = __('Time to own'); + $actions['time_to_own']['name'] = __('Remove Time To Own value'); $actions['time_to_own']['type'] = 'yesno'; $actions['time_to_own']['force_actions'] = ['delete']; - // assign (existing) ola ttr - $actions['olas_id_ttr']['table'] = 'glpi_olas'; - $actions['olas_id_ttr']['field'] = 'name'; - $actions['olas_id_ttr']['name'] = sprintf( - __('%1$s %2$s'), - __('OLA'), - __('Time to resolve') - ); - $actions['olas_id_ttr']['linkfield'] = 'olas_id_ttr'; - $actions['olas_id_ttr']['type'] = 'dropdown'; - $actions['olas_id_ttr']['condition'] = ['glpi_olas.type' => SLM::TTR]; - - // empty ola ttr - $actions['internal_time_to_resolve']['name'] = __('Internal time to resolve'); - $actions['internal_time_to_resolve']['type'] = 'yesno'; - $actions['internal_time_to_resolve']['force_actions'] = ['delete']; - // assign (existing) ola tto - $actions['olas_id_tto']['table'] = 'glpi_olas'; - $actions['olas_id_tto']['field'] = 'name'; - $actions['olas_id_tto']['name'] = sprintf( - __('%1$s %2$s'), - __('OLA'), - __('Time to own') - ); - $actions['olas_id_tto']['linkfield'] = 'olas_id_tto'; - $actions['olas_id_tto']['type'] = 'dropdown'; - $actions['olas_id_tto']['condition'] = ['glpi_olas.type' => SLM::TTO]; - - // set ola tto value - $actions['internal_time_to_own']['name'] = __('Internal Time to own'); - $actions['internal_time_to_own']['type'] = 'yesno'; - $actions['internal_time_to_own']['force_actions'] = ['delete']; + $actions['olas_id']['type'] = 'dropdown'; + $actions['olas_id']['table'] = 'glpi_olas'; + $actions['olas_id']['field'] = 'name'; + $actions['olas_id']['name'] = 'OLA TTR/TTO'; + $actions['olas_id']['force_actions'] = ['append']; + $actions['olas_id']['permitseveral'] = ['append']; // assign a location $actions['locations_id']['name'] = Location::getTypeName(1); diff --git a/src/SLA.php b/src/SLA.php index c3980b3ecc2..11455772ab5 100644 --- a/src/SLA.php +++ b/src/SLA.php @@ -40,36 +40,192 @@ /** * SLA Class + * + * @property array{id: int, name: string, entities_id: int, is_recursive: int, type: SLM::TTR|SLM::TTO, comment: string, number_time: int, use_ticket_calendar: int, calendars_id: int, date_mod: string, definition_time: string, end_of_working_day: int, date_creation: string, slms_id: int} $fields **/ class SLA extends LevelAgreement { protected static string $prefix = 'sla'; - protected static string $prefixticket = ''; protected static $levelclass = SlaLevel::class; protected static $levelticketclass = SlaLevel_Ticket::class; protected static array $forward_entity_to = [SlaLevel::class]; + /** + * @param Ticket $ticket + * @param int $levels_id + * @return void + */ + public function addLevelToDo(Ticket $ticket, $levels_id = 0) + { + $pre = static::$prefix; + + if (!$levels_id && isset($ticket->fields[$pre . 'levels_id_ttr'])) { + $levels_id = $ticket->fields[$pre . "levels_id_ttr"]; + } + + if ($levels_id) { + + $date = $this->computeExecutionDate( + $ticket->fields['date'], + $levels_id, + $ticket->fields[$pre . '_waiting_duration'] + ); + + $toadd = []; + if ($date !== null) { + $toadd['date'] = $date; + $toadd[$pre . 'levels_id'] = $levels_id; + $toadd['tickets_id'] = $ticket->fields["id"]; + $levelticket = getItemForItemtype(static::$levelticketclass); + $levelticket->add($toadd); + } + } + } + + /** + * remove a level to do for a ticket + * + * @param Ticket $ticket object + * + * @return void + **/ + public static function deleteLevelsToDo(Ticket $ticket) + { + /** @var DBmysql $DB */ + global $DB; + + $ticketfield = static::$prefix . "levels_id_ttr"; + + if ($ticket->fields[$ticketfield] > 0) { + $levelticket = getItemForItemtype(static::$levelticketclass); + $iterator = $DB->request([ + 'SELECT' => 'id', + 'FROM' => $levelticket::getTable(), + 'WHERE' => ['tickets_id' => $ticket->fields['id']], + ]); + + foreach ($iterator as $data) { + $levelticket->delete(['id' => $data['id']]); + } + } + } + + /** + * @param int $levels_id + * @return void + */ + public function addLevelToDo(Ticket $ticket, $levels_id = 0) + { + $pre = static::$prefix; + + if (!$levels_id && isset($ticket->fields[$pre . 'levels_id_ttr'])) { + $levels_id = $ticket->fields[$pre . "levels_id_ttr"]; + } + + if ($levels_id) { + + $date = $this->computeExecutionDate( + $ticket->fields['date'], + $levels_id, + $ticket->fields[$pre . '_waiting_duration'] + ); + + $toadd = []; + if ($date !== null) { + $toadd['date'] = $date; + $toadd[$pre . 'levels_id'] = $levels_id; + $toadd['tickets_id'] = $ticket->fields["id"]; + /** @var SlaLevel_Ticket $levelticket */ + $levelticket = getItemForItemtype(static::$levelticketclass); + $levelticket->add($toadd); + } + } + } + + /** + * remove a level to do for a ticket + * + * @param Ticket $ticket object + * + * @return void + **/ + #[Override] + public static function deleteLevelsToDo(Ticket $ticket) + { + /** @var DBmysql $DB */ + global $DB; + + $ticketfield = static::$prefix . "levels_id_ttr"; + + if ($ticket->fields[$ticketfield] > 0) { + /** @var SlaLevel_Ticket $levelticket */ + $levelticket = getItemForItemtype(static::$levelticketclass); + $iterator = $DB->request([ + 'SELECT' => 'id', + 'FROM' => $levelticket::getTable(), + 'WHERE' => ['tickets_id' => $ticket->fields['id']], + ]); + + foreach ($iterator as $data) { + $levelticket->delete(['id' => $data['id']]); + } + } + } + + #[Override] public static function getTypeName($nb = 0) { // Acronym, no plural return __('SLA'); } + #[Override] public static function getSectorizedDetails(): array { return ['config', SLM::class, self::class]; } + #[Override] public static function getLogDefaultServiceName(): string { return 'setup'; } + #[Override] public static function getIcon() { return SLM::getIcon(); } + public function cleanDBonPurge() + { + /** @var DBmysql $DB */ + global $DB; + + // Clean levels + $fk = getForeignKeyFieldForItemType(static::class); + /** @var SlaLevel_Ticket $level */ + $level = getItemForItemtype(static::$levelclass); + $level->deleteByCriteria([$fk => $this->getID()]); + + // Update tickets : clean SLA + [, $laField] = static::getFieldNames($this->fields['type']); + $iterator = $DB->request([ + 'SELECT' => 'id', + 'FROM' => 'glpi_tickets', + 'WHERE' => [$laField => $this->fields['id']], + ]); + + if (count($iterator)) { + $ticket = new Ticket(); + foreach ($iterator as $data) { + $ticket->deleteLevelAgreement(static::class, $data['id'], $this->fields['type']); + } + } + + Rule::cleanForItemAction($this); + } + public function showFormWarning() {} public function getAddConfirmation(): array diff --git a/src/SlaLevel_Ticket.php b/src/SlaLevel_Ticket.php index 9423a775efc..fed7716cf78 100644 --- a/src/SlaLevel_Ticket.php +++ b/src/SlaLevel_Ticket.php @@ -33,12 +33,17 @@ * --------------------------------------------------------------------- */ +/** + * Table to store slalevels to be processed. + * `date` field contains the date when the level has to processed + */ /** * Table to store slalevels to be processed. * `date` field contains the date when the level has to processed */ class SlaLevel_Ticket extends CommonDBTM { + #[Override] public static function getTypeName($nb = 0) { return __('SLA level for Ticket'); @@ -47,10 +52,10 @@ public static function getTypeName($nb = 0) /** * Retrieve an item from the database * - * @param int $ID of the item to get - * @param SLM::TTR|SLM::TTO $slaType * * @since 9.1 2 mandatory parameters + * @param int $ID of the item to get + * @param SLM::TTR|SLM::TTO $slaType * * @return bool * @used-by LevelAgreement::getNextActionForTicket() @@ -92,10 +97,10 @@ public function getFromDBForTicket($ID, $slaType) /** * Delete entries for a ticket * - * @param int $tickets_id Ticket ID - * @param SLM::TTR|SLM::TTO $slaType Type of SLA * * @since 9.1 2 parameters mandatory + * @param int $tickets_id Ticket ID + * @param SLM::TTR|SLM::TTO $slaType Type of SLA * * @return void **/ @@ -200,10 +205,10 @@ public static function cronSlaTicket(CronTask $task) /** * Do a specific SLAlevel for a ticket * - * @param array $data data of an entry of slalevels_tickets - * @param SLM::TTR|SLM::TTO $slaType Type of SLA * * @since 9.1 2 parameters mandatory + * @param array $data data of an entry of slalevels_tickets + * @param SLM::TTR|SLM::TTO $slaType Type of SLA * * @return void **/ @@ -315,10 +320,10 @@ public static function doLevelForTicket(array $data, $slaType) * * Replay level stored in slalevels_tickets | olalevels_tickets * - * @param int $tickets_id - * @param SLM::TTR|SLM::TTO $slaType * * @since 9.1 2 parameters mandatory + * @param int $tickets_id + * @param SLM::TTR|SLM::TTO $slaType * * @return void */ diff --git a/src/Ticket.php b/src/Ticket.php index aa0199e7d87..d78addd82f6 100644 --- a/src/Ticket.php +++ b/src/Ticket.php @@ -47,6 +47,7 @@ use Glpi\Search\DefaultSearchRequestInterface; use Glpi\Urgency; use Safe\DateTime; +use Safe\Exceptions\DatetimeException; use function Safe\preg_match; use function Safe\preg_match_all; @@ -55,6 +56,16 @@ /** * Ticket Class + * + * Additionnal fields for $input add/update + * - _olas_id : array of olas to associate with the ticket, used only if _la_update field is set + * - _la_update : flag to know if _olas_id field must be handled. Do not handle unless it's true + * - _la_append : flag to know if _olas_id must be appended to the existing olas without removing the existing ones + * + * - _groups_id_assign : assign the ticket to a group + * - _users_id_assign : assign the ticket to a user + * + * @phpstan-import-type ItemOlaData from Item_Ola **/ class Ticket extends CommonITILObject implements DefaultSearchRequestInterface { @@ -410,10 +421,10 @@ public function isAlreadyTakenIntoAccount() /** * Get Datas to be added for SLA add * - * @param int $slas_id SLA id - * @param int $entities_id entity ID of the ticket - * @param string $date begin date of the ticket - * @param int $type type of SLA + * @param int $slas_id SLA id + * @param int $entities_id entity ID of the ticket + * @param string $date begin date of the ticket + * @param SLM::TTO|SLM::TTR $type type of SLA * * @since 9.1 (before getDatasToAddSla without type parameter) * @@ -446,109 +457,70 @@ public function getDatasToAddSLA($slas_id, $entities_id, $date, $type) $data[$slaField] = 0; $data['sla_waiting_duration'] = 0; } - return $data; - } - - /** - * Get Datas to be added for OLA add - * - * @param int $olas_id OLA id - * @param int $entities_id entity ID of the ticket - * @param string $date begin date of the ticket - * @param int $type type of OLA - * - * @since 9.2 (before getDatasToAddOla without type parameter) - * - * @return array of datas to add in ticket - **/ - public function getDatasToAddOLA($olas_id, $entities_id, $date, $type) - { - [$dateField, $olaField] = OLA::getFieldNames($type); - - $data = []; - - $ola = new OLA(); - if ($ola->getFromDB($olas_id)) { - $calendars_id = Entity::getUsedConfig( - 'calendars_strategy', - $entities_id, - 'calendars_id', - 0 - ); - $ola->setTicketCalendar($calendars_id); - if ($ola->fields['type'] == SLM::TTR) { - $data["olalevels_id_ttr"] = OlaLevel::getFirstOlaLevel($olas_id); - $data['ola_ttr_begin_date'] = $date; - } elseif ($ola->fields['type'] == SLM::TTO) { - $data['ola_tto_begin_date'] = $date; - } - // Compute time_to_own - $data['ola_waiting_duration'] = (int) ($this->fields['ola_waiting_duration'] ?? 0); - $data[$dateField] = $ola->computeDate($date, $data['ola_waiting_duration']); - } else { - $data["olalevels_id_ttr"] = 0; - $data[$olaField] = 0; - $data['ola_waiting_duration'] = 0; - } return $data; } - /** * Delete Level Agreement for the ticket * - * @since 9.2 + * - update relataed fields in ticket ($laType = SLA only) + * - remove Item_Olas + * - OlaLevel_Ticket * - * @param string $laType (SLA | OLA) + * @param class-string $laType * @param int $la_id the sla/ola id * @param SLM::TTR|SLM::TTO $subtype (SLM::TTR | SLM::TTO) TODO: use a real type (enum) * @param bool $delete_date (default false) * * @return bool - **/ + * @since 9.2 + */ public function deleteLevelAgreement($laType, $la_id, $subtype, $delete_date = false) { - switch ($laType) { - case "SLA": - $prefix = "sla"; - $prefix_ticket = ""; - $level_ticket = new SlaLevel_Ticket(); - break; - case "OLA": - $prefix = "ola"; - $prefix_ticket = "internal_"; - $level_ticket = new OlaLevel_Ticket(); - break; - default: - return false; - } + if ($laType === 'OLA') { + // delete Item_Ola, there is just one item_ola for one ticket + ola + $item_ola = new Item_Ola(); + if (!$item_ola->deleteByCriteria(['olas_id' => (int) $la_id])) { + throw new RuntimeException('Unable to delete Item_Ola #' . $la_id); + } - $input = []; - switch ($subtype) { - case SLM::TTR: - $input[$prefix . 's_id_ttr'] = 0; - if ($delete_date) { - $input[$prefix_ticket . 'time_to_resolve'] = ''; - } - break; + // delete level agreement level to do + $level_ticket = new OlaLevel_Ticket(); + $level_ticket->deleteForTicket($la_id, $subtype); - case SLM::TTO: - $input[$prefix . 's_id_tto'] = 0; - if ($delete_date) { - $input[$prefix_ticket . 'time_to_own'] = ''; - } - break; - default: - return false; - } + return true; + } elseif ($laType === 'SLA') { + switch ($subtype) { + case SLM::TTR: + $input['slas_id_ttr'] = 0; + if ($delete_date) { + $input['time_to_resolve'] = ''; + } + break; - $input[$prefix . '_waiting_duration'] = 0; - $input['id'] = $la_id; + case SLM::TTO: + $input['slas_id_tto'] = 0; + if ($delete_date) { + $input['time_to_own'] = ''; + } + break; + default: + return false; + } + + $input['sla_waiting_duration'] = 0; + $input['id'] = $la_id; + + $level_ticket = new SlaLevel_Ticket(); - $level_ticket->deleteForTicket($la_id, $subtype); + $level_ticket->deleteForTicket($la_id, $subtype); + + return $this->update($input); + } else { + throw new Exception('invalid level type'); + } - return $this->update($input); } @@ -749,13 +721,9 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) case OLA::class: $nb = countElementsInTable( - 'glpi_tickets', + Item_Ola::getTable(), [ - 'OR' => [ - 'olas_id_tto' => $item->getID(), - 'olas_id_ttr' => $item->getID(), - ], - 'is_deleted' => 0, + 'olas_id' => $item->getID(), ] ); break; @@ -792,14 +760,6 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) if ($item instanceof self) { $ong = []; - // enquete si statut clos - $satisfaction = new TicketSatisfaction(); - if ( - $satisfaction->getFromDB($item->getID()) - && $item->fields['status'] == self::CLOSED - ) { - $ong[3] = TicketSatisfaction::createTabEntry(__('Satisfaction'), 0, static::class); - } if ($item->canView()) { $ong[4] = static::createTabEntry(__('Statistics'), 0, null, 'ti ti-chart-pie'); } @@ -816,10 +776,6 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ switch (get_class($item)) { case self::class: switch ($tabnum) { - case 3: - self::showSatisfactionTabContent($item); - break; - case 4: $item->showStats(); break; @@ -956,7 +912,10 @@ public function cleanDBonPurge() public function prepareInputForUpdate($input) { + $this->failsWithOlaRemovedFields($input); + $input = $this->transformActorsInput($input); + $input = $this->transformOlasInputForBackwardCompatibility($input); // Get ticket : need for comparison $this->getFromDB($input['id']); @@ -1039,12 +998,9 @@ public function prepareInputForUpdate($input) foreach ([SLM::TTR, SLM::TTO] as $slmType) { [$dateField, $slaField] = SLA::getFieldNames($slmType); unset($input[$dateField], $input[$slaField]); - [$dateField, $olaField] = OLA::getFieldNames($slmType); - unset($input[$dateField], $input[$olaField]); } } - //must be handled here for tickets. @see CommonITILObject::prepareInputForUpdate() $input = $this->handleTemplateFields($input); if ($input === false) { return false; @@ -1098,7 +1054,7 @@ public function prepareInputForUpdate($input) * * @since 9.1 * - * @param int $type + * @param SLM::TTO|SLM::TTR $type * @param array $input * @param array $manual_slas_id * @@ -1185,102 +1141,11 @@ public function slaAffect($type, &$input, $manual_slas_id) } } - /** - * OLA affect by rules : reset internal_time_to_resolve and internal_time_to_own - * Manual OLA defined : reset internal_time_to_resolve and internal_time_to_own - * No manual OLA and due date defined : reset auto OLA - * - * @since 9.1 - * - * @param int $type - * @param array $input - * @param array $manual_olas_id - * - * @return void - */ - public function olaAffect($type, &$input, $manual_olas_id) - { - - [$dateField, $olaField] = OLA::getFieldNames($type); - - // Restore olas - if ( - isset($manual_olas_id[$type]) - && !isset($input['_' . $olaField]) - ) { - $input[$olaField] = $manual_olas_id[$type]; - } - - // Ticket update - if (isset($this->fields['id']) && $this->fields['id'] > 0) { - if ( - !isset($manual_olas_id[$type]) - && isset($input[$olaField]) && ($input[$olaField] > 0) - && ($input[$olaField] != $this->fields[$olaField]) - ) { - if (isset($input[$dateField])) { - // Unset due date - unset($input[$dateField]); - } - } - - if ( - isset($input[$olaField]) && ($input[$olaField] > 0) - && ($input[$olaField] != $this->fields[$olaField] - || isset($input['_' . $olaField])) - ) { - $date = $_SESSION['glpi_currenttime']; - - // Get datas to initialize OLA and set it - $ola_data = $this->getDatasToAddOLA( - $input[$olaField], - $this->fields['entities_id'], - $date, - $type - ); - if (count($ola_data)) { - foreach ($ola_data as $key => $val) { - $input[$key] = $val; - } - } - } - } else { // Ticket add - if ( - !isset($manual_olas_id[$type]) - && isset($input[$dateField]) && ($input[$dateField] != 'NULL') - ) { - // Valid due date - if ($input[$dateField] >= $input['date']) { - if (isset($input[$olaField])) { - unset($input[$olaField]); - } - } else { - // Unset due date - unset($input[$dateField]); - } - } - - if (isset($input[$olaField]) && ($input[$olaField] > 0)) { - // Get datas to initialize OLA and set it - $ola_data = $this->getDatasToAddOLA( - $input[$olaField], - $input['entities_id'], - $input['date'], - $type - ); - if (count($ola_data)) { - foreach ($ola_data as $key => $val) { - $input[$key] = $val; - } - } - } - } - } - - /** * Manage SLA level escalation * + * Set escalation level for the ticket (or delete it) + * * @since 9.1 * * @param int $slas_id @@ -1316,25 +1181,28 @@ public function manageSlaLevel($slas_id) /** * Manage OLA level escalation * - * @since 9.1 + * - add level in todo table XLaLevel_Ticket + * - replayForTicket * - * @param int $slas_id + * Method requires levels to be cleared before - @see OLA::delete(All)LevelsToDo($ticket) + * @param int $olas_id * * @return void + * + * @since 9.1 */ - public function manageOlaLevel($slas_id) + public function manageOlaLevel($olas_id) { - // Add first level in working table - $olalevels_id = OlaLevel::getFirstOlaLevel($slas_id); + $olalevels_id = OlaLevel::getFirstOlaLevel($olas_id); $ola = new OLA(); - $in_db = $ola->getFromDB($slas_id); + $in_db = $ola->getFromDB($olas_id); if (!$in_db) { return; } - if (!in_array($this->fields['status'], static::getReopenableStatusArray())) { + if ($olalevels_id && !in_array($this->fields['status'], static::getReopenableStatusArray())) { $ola->clearInvalidLevels($this->fields['id']); $calendars_id = Entity::getUsedConfig( 'calendars_strategy', @@ -1343,12 +1211,11 @@ public function manageOlaLevel($slas_id) 0 ); $ola->setTicketCalendar($calendars_id); - $ola->addLevelToDo($this, $olalevels_id); + $ola->addLevelToDo($this, $olalevels_id, $olas_id); } OlaLevel_Ticket::replayForTicket($this->getID(), $ola->fields['type']); } - public function pre_updateInDB() { @@ -1426,6 +1293,16 @@ public function post_updateItem($history = true) { global $CFG_GLPI; + // $new_assigned_groups & $new_assigned_users are used to recompute OLA + $new_assigned_groups = $this->input['_groups_id_assign'] ?? []; + if (!is_array($new_assigned_groups)) { + $new_assigned_groups = [$new_assigned_groups]; + } + $new_assigned_users = $this->input['_users_id_assign'] ?? []; + if (!is_array($new_assigned_users)) { + $new_assigned_users = [$new_assigned_users]; + } + parent::post_updateItem($history); // Put same status on duplicated tickets when solving or closing (autoclose on solve) @@ -1446,7 +1323,7 @@ public function post_updateItem($history = true) $donotif = true; } - // Manage SLA / OLA Level : add actions + // Update SLA foreach ([SLM::TTR, SLM::TTO] as $slmType) { [$dateField, $slaField] = SLA::getFieldNames($slmType); if ( @@ -1455,18 +1332,15 @@ public function post_updateItem($history = true) ) { $this->manageSlaLevel($this->fields[$slaField]); } + } - [$dateField, $olaField] = OLA::getFieldNames($slmType); - if ( - in_array($olaField, $this->updates) - && ($this->fields[$olaField] > 0) - ) { - $this->manageOlaLevel($this->fields[$olaField]); - } + $this->updateOlaAssociations(false); + if (!isset($this->input['_auto_update'])) { + $this->recomputeOlas($new_assigned_groups, $new_assigned_users); } + // Update Ticket Tco if (count($this->updates)) { - // Update Ticket Tco if ( in_array("actiontime", $this->updates) || in_array("cost_time", $this->updates) @@ -1555,12 +1429,16 @@ public function post_updateItem($history = true) public function prepareInputForAdd($input) { + $this->failsWithOlaRemovedFields($input); + // Standard clean datas $input = parent::prepareInputForAdd($input); if ($input === false) { return false; } + $input = $this->transformOlasInputForBackwardCompatibility($input); + if (!isset($input["requesttypes_id"])) { $input["requesttypes_id"] = RequestType::getDefault('helpdesk'); } @@ -1612,8 +1490,6 @@ public function prepareInputForAdd($input) foreach ([SLM::TTR, SLM::TTO] as $slmType) { [$dateField, $slaField] = SLA::getFieldNames($slmType); unset($input[$dateField], $input[$slaField]); - [$dateField, $olaField] = OLA::getFieldNames($slmType); - unset($input[$dateField], $input[$olaField]); } } @@ -1780,12 +1656,10 @@ public function post_addItem() if (isset($this->input[$slaField]) && ($this->input[$slaField] > 0)) { $this->manageSlaLevel($this->input[$slaField]); } - [$dateField, $olaField] = OLA::getFieldNames($slmType); - if (isset($this->input[$olaField]) && ($this->input[$olaField] > 0)) { - $this->manageOlaLevel($this->input[$olaField]); - } } + $this->updateOlaAssociations(true); + // Add project task link if needed if (isset($this->input['_projecttasks_id'])) { $projecttask = new ProjectTask(); @@ -2007,6 +1881,7 @@ public function updateDateMod($ID, $no_stat_computation = false, $users_id_lastu && !$this->isAlreadyTakenIntoAccount() && ($this->canTakeIntoAccount() || isCommandLine()) ) { + $this->recomputeOlas(); $this->update( [ 'id' => $ID, @@ -2018,6 +1893,7 @@ public function updateDateMod($ID, $no_stat_computation = false, $users_id_lastu } parent::updateDateMod($ID, $no_stat_computation, $users_id_lastupdater); + $this->recomputeOlas(); } } @@ -2550,7 +2426,6 @@ public static function processMassiveActionsForOneItemtype( parent::processMassiveActionsForOneItemtype($ma, $item, $ids); } - public function rawSearchOptions() { global $DB; @@ -2610,68 +2485,8 @@ public function rawSearchOptions() 'computation' => self::generateSLAOLAComputation('time_to_own'), ]; - $tab[] = [ - 'id' => '180', - 'table' => $this->getTable(), - 'field' => 'internal_time_to_resolve', - 'name' => __('Internal time to resolve'), - 'datatype' => 'datetime', - 'maybefuture' => true, - 'massiveaction' => false, - 'additionalfields' => ['solvedate', 'status'], - ]; - - $tab[] = [ - 'id' => '181', - 'table' => $this->getTable(), - 'field' => 'internal_time_to_resolve', - 'name' => __('Internal time to resolve + Progress'), - 'massiveaction' => false, - 'nosearch' => true, - 'additionalfields' => ['status'], - ]; - - $tab[] = [ - 'id' => '182', - 'table' => $this->getTable(), - 'field' => 'ola_ttr_is_late', - 'name' => __('Internal time to resolve exceeded'), - 'datatype' => 'bool', - 'massiveaction' => false, - 'computation' => self::generateSLAOLAComputation('internal_time_to_resolve'), - ]; - - $tab[] = [ - 'id' => '185', - 'table' => $this->getTable(), - 'field' => 'internal_time_to_own', - 'name' => __('Internal time to own'), - 'datatype' => 'datetime', - 'maybefuture' => true, - 'massiveaction' => false, - 'additionalfields' => ['date', 'status', 'takeintoaccount_delay_stat', 'takeintoaccountdate'], - ]; - - $tab[] = [ - 'id' => '186', - 'table' => $this->getTable(), - 'field' => 'internal_time_to_own', - 'name' => __('Internal time to own + Progress'), - 'massiveaction' => false, - 'nosearch' => true, - 'additionalfields' => ['status'], - ]; - - $tab[] = [ - 'id' => '187', - 'table' => 'glpi_tickets', - 'field' => 'ola_tto_is_late', - 'name' => __('Internal time to own exceeded'), - 'datatype' => 'bool', - 'massiveaction' => false, - 'computation' => self::generateSLAOLAComputation('internal_time_to_own'), - ]; - + // Minimal OLA/SLA TTO/TTR due time + // Label is wrong, nothing to do with escalation level, and nothing to do with future (can give date in the past) $max_date = new QueryExpression('99999999'); $tab[] = [ 'id' => '188', @@ -2682,33 +2497,30 @@ public function rawSearchOptions() 'usehaving' => true, 'maybefuture' => true, 'massiveaction' => false, - // Get least value from TTO/TTR fields: + // Get the minimal date + // SLA // - use TTO fields only if ticket not already taken into account, // - use TTR fields only if ticket not already solved, + // OLA + // - no check, we rely on field value. + // // - replace NULL or not kept values with 99999999 to be sure that they will not be returned by the LEAST function, // - replace 99999999 by empty string to keep only valid values. - 'computation' => QueryFunction::replace( + 'computation' => QueryFunction::replace( expression: QueryFunction::least([ QueryFunction::if( condition: ['TABLE.takeintoaccount_delay_stat' => ['<=', 0]], true_expression: QueryFunction::coalesce(['TABLE.time_to_own', $max_date]), false_expression: $max_date ), - QueryFunction::if( - condition: ['TABLE.takeintoaccount_delay_stat' => ['<=', 0]], - true_expression: QueryFunction::coalesce(['TABLE.internal_time_to_own', $max_date]), - false_expression: $max_date - ), + QueryFunction::coalesce([ + new QueryExpression((new QuerySubQuery(['FROM' => Item_Ola::getTable(), 'SELECT' => QueryFunction::min('due_time')]))->getQuery()), $max_date, + ]), QueryFunction::if( condition: ['TABLE.solvedate' => null], true_expression: QueryFunction::coalesce(['TABLE.time_to_resolve', $max_date]), false_expression: $max_date ), - QueryFunction::if( - condition: ['TABLE.solvedate' => null], - true_expression: QueryFunction::coalesce(['TABLE.internal_time_to_resolve', $max_date]), - false_expression: $max_date - ), ]), search: new QueryExpression($DB::quoteValue($max_date)), replace: new QueryExpression($DB::quoteValue('')) @@ -2767,6 +2579,7 @@ public function rawSearchOptions() $tab = array_merge($tab, $this->getSearchOptionsActors()); + // --- SLA $tab[] = [ 'id' => 'sla', 'name' => __('SLA'), @@ -2818,39 +2631,173 @@ public function rawSearchOptions() 'forcegroupby' => true, ]; + // --- OLA $tab[] = [ 'id' => 'ola', 'name' => __('OLA'), ]; + // associated OLA TTO names $tab[] = [ 'id' => '190', 'table' => 'glpi_olas', 'field' => 'name', - 'linkfield' => 'olas_id_tto', - 'name' => __('OLA') . ' ' . __('Internal time to own'), + 'name' => __('OLA') . ' ' . __('time to own'), 'massiveaction' => false, 'datatype' => 'dropdown', 'joinparams' => [ - 'condition' => ['NEWTABLE.type' => SLM::TTO], + 'beforejoin' => [ + 'table' => Item_Ola::getTable(), + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTO], + ], + ], + ], + 'forcegroupby' => true, + 'condition' => ['type' => SLM::TTO], + ]; + + // associated OLA TTO due time + $tab[] = [ + 'id' => '185', + 'table' => Item_Ola::getTable(), + 'field' => 'due_time', + 'name' => __('OLA') . ' ' . __('time to own') . ' - ' . __('due time'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'additionalfields' => ['TABLE.status', 'TABLE.date', 'olas_id', 'waiting_time', 'end_time'], + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTO,], ], - 'condition' => ['glpi_olas.type' => SLM::TTO], + 'forcegroupby' => true, ]; + // OLA TTO due time (+ Progress) + // because filtering on items_olas is required, by type, we need to use a beforejoin + $tab[] = [ + 'id' => '186', + 'table' => Item_Ola::getTable(), + 'field' => 'due_time', + 'name' => __('OLA') . ' ' . __('time to own') . ' - ' . __('due time + progress'), + 'massiveaction' => false, + 'nosearch' => true, + 'additionalfields' => ['TABLE.status', 'TABLE.date', 'olas_id', 'waiting_time', 'end_time'], + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTO,], + ], + 'forcegroupby' => true, + ]; + + // OLA TTO exceeded + $tab[] = [ + 'id' => '187', + 'table' => Item_Ola::getTable(), + 'field' => 'is_late', + 'name' => __('OLA') . ' ' . __('time to own exceeded'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'computation' => self::generateSLAOLAComputation('internal_time_to_own'), + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTO,], + ], + 'forcegroupby' => true, + 'usehaving' => true, // needed because of generateSLAOLAComputation() use a group function (max). + ]; + + // associated OLA TTR names $tab[] = [ 'id' => '191', 'table' => 'glpi_olas', 'field' => 'name', - 'linkfield' => 'olas_id_ttr', - 'name' => __('OLA') . ' ' . __('Internal time to resolve'), + 'name' => __('OLA') . ' ' . __('time to resolve'), 'massiveaction' => false, 'datatype' => 'dropdown', 'joinparams' => [ - 'condition' => ['NEWTABLE.type' => SLM::TTR], + 'beforejoin' => [ + 'table' => Item_Ola::getTable(), + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTR], + ], + ], ], - 'condition' => ['glpi_olas.type' => SLM::TTR], + 'forcegroupby' => true, + 'condition' => ['type' => SLM::TTR], ]; + // associated OLA TTR due time + $tab[] = [ + 'id' => '180', + 'table' => Item_Ola::getTable(), + 'field' => 'due_time', + 'name' => __('OLA') . ' ' . __('time to resolve') . ' - ' . __('due time'), + 'massiveaction' => false, + 'datatype' => 'datetime', + 'additionalfields' => ['TABLE.status', 'TABLE.date', 'olas_id', 'waiting_time', 'end_time'], + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTR,], + ], + 'forcegroupby' => true, + ]; + + // OLA TTR exceeded + $tab[] = [ + 'id' => '182', + 'table' => Item_Ola::getTable(), + 'field' => 'is_late', + 'name' => __('OLA') . ' ' . __('time to resolve exceeded'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'computation' => self::generateSLAOLAComputation('internal_time_to_resolve'), + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTR,], + ], + 'forcegroupby' => true, + 'usehaving' => true, // needed because of generateSLAOLAComputation() use a group function (max). + ]; + + // Ola levels + // OLA TTR due time (+ Progress) + $tab[] = [ + 'id' => '181', + 'table' => Item_Ola::getTable(), + 'field' => 'due_time', + 'datatype' => 'datetime', + 'name' => __('OLA') . ' ' . __('time to resolve') . ' - ' . __('due time + progress'), + 'massiveaction' => false, + 'nosearch' => true, + 'additionalfields' => ['TABLE.status', 'TABLE.date', 'olas_id', 'waiting_time', 'end_time'], + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTR,], + ], + 'forcegroupby' => true, + ]; + + // OLA TTR exceeded + $tab[] = [ + 'id' => '182', + 'table' => Item_Ola::getTable(), + 'field' => 'is_late', + 'name' => __('OLA') . ' ' . __('time to resolve exceeded'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'computation' => self::generateSLAOLAComputation('internal_time_to_resolve'), + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'condition' => ['NEWTABLE.ola_type' => SLM::TTR,], + ], + 'forcegroupby' => true, + 'usehaving' => true, // needed because of generateSLAOLAComputation() use a group function (max). + ]; + + // Ola levels $tab[] = [ 'id' => '192', 'table' => 'glpi_olalevels', @@ -3173,7 +3120,6 @@ public function rawSearchOptions() return $tab; } - public static function getSpecificValueToDisplay($field, $values, array $options = []) { @@ -3198,6 +3144,7 @@ public static function getSpecificValueToDisplay($field, $values, array $options return htmlescape(sprintf(__('%s hours %s minutes'), floor($time / 3600), floor(($time % 3600) / 60))); } + return parent::getSpecificValueToDisplay($field, $values, $options); } @@ -3217,7 +3164,6 @@ public static function getSpecificValueToSelect($field, $name = '', $values = '' return parent::getSpecificValueToSelect($field, $name, $values, $options); } - /** * Dropdown of ticket type * @@ -3495,10 +3441,9 @@ public static function getDefaultValues($entity = 0) 'time_to_own' => 'NULL', 'slas_id_tto' => 0, 'slas_id_ttr' => 0, - 'internal_time_to_resolve' => 'NULL', - 'internal_time_to_own' => 'NULL', - 'olas_id_tto' => 0, - 'olas_id_ttr' => 0, + '_olas_id' => [], + '_olas_id_ttr' => [], // needed to handle field in Ticket::setPredefinedFields(), no other uses + '_olas_id_tto' => [], // needed to handle field in Ticket::setPredefinedFields(), no other uses '_add_validation' => 0, '_validation_targets' => [], 'type' => $type, @@ -3631,6 +3576,7 @@ public function showForm($ID, array $options = []) //Allow overriding the default values $options['_skip_promoted_fields'] = true; } + $options['_olas_id'] = []; // $this->setPredefinedFields() needs it } // Default check @@ -3698,6 +3644,7 @@ public function showForm($ID, array $options = []) ); // override current fields in options with template fields and return the array of these predefined fields + $this->fields['_olas_id'] = array_column($this->getOlasData(), 'olas_id'); $predefined_fields = $this->setPredefinedFields($tt, $options, self::getDefaultValues()); // check right used for this ticket @@ -3727,8 +3674,11 @@ public function showForm($ID, array $options = []) $canpriority = false; } - $sla = new SLA(); - $ola = new OLA(); + $ttr_sla = new SLA(); + $ttr_sla->getFromDB($this->fields['slas_id_ttr']); + + $tto_sla = new SLA(); + $tto_sla->getFromDB($this->fields['slas_id_tto']); if ($this->isNewItem()) { $options['_canupdate'] = Session::haveRight('ticket', CREATE); @@ -3764,8 +3714,8 @@ public function showForm($ID, array $options = []) 'itiltemplate' => $tt, 'predefined_fields' => Toolbox::prepareArrayForInput($predefined_fields), 'item_commonitilobject' => $item_ticket, - 'sla' => $sla, - 'ola' => $ola, + 'ttr_sla' => $ttr_sla, + 'tto_sla' => $tto_sla, 'canupdate' => $canupdate, 'can_requester' => $can_requester, 'canpriority' => $canpriority, @@ -3779,7 +3729,7 @@ public function showForm($ID, array $options = []) 'show_tickets_properties_on_helpdesk', Session::getActiveEntity(), ), - 'survey' => $this->getSatisfactionSurveyForHelpdesk(), + 'survey' => $this->getSatisfactionSurvey(), ]); return true; @@ -4981,6 +4931,15 @@ public static function getCommonCriteria() ], ]; + $criteria['LEFT JOIN'][Item_Ola::getTable()] = [ + 'ON' => [ + self::getTable() => 'id', + Item_Ola::getTable() => 'items_id', + ['AND' => ['glpi_items_olas.itemtype' => ['=', Ticket::class]], + ], + ], + ]; + return $criteria; } @@ -5372,15 +5331,14 @@ function ($img_matches) use ($tags, $filename) { /** * Get correct Calendar: Entity or Sla * - * @since 0.90.4 * @since 10.0.4 $slm_type parameter added * - * @param int $slm_type Type of SLA, can be SLM::TTO or SLM::TTR + * @param SLM::TTO|SLM::TTR $slm_type Type of SLA, can be SLM::TTO or SLM::TTR * **/ public function getCalendar(int $slm_type = SLM::TTR) { - [$date_field, $sla_field] = SLA::getFieldNames($slm_type); + [, $sla_field] = SLA::getFieldNames($slm_type); if (isset($this->fields[$sla_field]) && $this->fields[$sla_field] > 0) { $sla = new SLA(); @@ -5425,46 +5383,53 @@ public function getValueToSelect($field_id_or_search_options, $name = '', $value return parent::getValueToSelect($field_id_or_search_options, $name, $values, $options); } + /** + * @return void + */ public function showStatsDates() { - $now = time(); - $date_creation = strtotime($this->fields['date']); + // function to get strtotime safely, do not use if string is supposed to always be defined + $safe_get_strtime = static function ($date_string): ?int { + try { + return strtotime((string) $date_string); + } catch (DatetimeException $e) { + return null; + } + }; + $now = Session::getCurrentTime() ? strtotime(Session::getCurrentTime()) : time(); + $date_creation = $safe_get_strtime($this->fields['date']); // Tickets created before 10.0.4 do not have takeintoaccountdate field, use old and incorrect computation for those cases - $date_takeintoaccount = 0; - if ($this->fields['takeintoaccountdate'] !== null) { - $date_takeintoaccount = strtotime($this->fields['takeintoaccountdate']); - } elseif ($this->fields['takeintoaccount_delay_stat'] > 0) { + $date_takeintoaccount = $safe_get_strtime($this->fields['takeintoaccountdate']); + if ($date_takeintoaccount === 0 && $this->fields['takeintoaccount_delay_stat'] > 0 && $date_creation > 0) { $date_takeintoaccount = $date_creation + $this->fields['takeintoaccount_delay_stat']; } + $time_to_own = $safe_get_strtime($this->fields['time_to_own']); - $internal_time_to_own = !empty($this->fields['internal_time_to_own']) - ? strtotime($this->fields['internal_time_to_own']) - : null; - $time_to_own = !empty($this->fields['time_to_own']) - ? strtotime($this->fields['time_to_own']) - : null; - $internal_time_to_resolve = !empty($this->fields['internal_time_to_resolve']) - ? strtotime($this->fields['internal_time_to_resolve']) - : null; - $time_to_resolve = !empty($this->fields['time_to_resolve']) - ? strtotime($this->fields['time_to_resolve']) - : null; - $solvedate = !empty($this->fields['solvedate']) - ? strtotime($this->fields['solvedate']) - : null; - $closedate = !empty($this->fields['closedate']) - ? strtotime($this->fields['closedate']) - : null; + $dates_olas = []; + $ola = new OLA(); + foreach ($this->getOlasData() as $ola_data) { + $ola->getFromDB($ola_data['olas_id']); + + $due_time = $safe_get_strtime($ola_data['due_time']); + $key = $due_time . '_ola_' . $ola_data['olas_id'] . '_due_time'; + $label = __('OLA') . ' ' . OLA::getOneTypeName($ola_data['type']) . ' ' . $ola_data['name'] . ' ' . __('due time'); + $label .= "getLinkURL()}\">getName()}\">"; + + $dates_olas[$key] = [ + 'timestamp' => $due_time, + 'label' => $label, + 'class' => $ola_data['is_late'] ? 'passed' : 'checked', + ]; + } + $time_to_resolve = $safe_get_strtime($this->fields['time_to_resolve']); + $solvedate = $safe_get_strtime($this->fields['solvedate']); + $closedate = $safe_get_strtime($this->fields['closedate']); $goal_takeintoaccount = ($date_takeintoaccount > 0 ? $date_takeintoaccount : $now); $goal_solvedate = ($solvedate > 0 ? $solvedate : $now); $sla = new SLA(); - $ola = new OLA(); - $sla_tto_link - = $sla_ttr_link - = $ola_tto_link - = $ola_ttr_link = ""; + $sla_tto_link = $sla_ttr_link = ""; if ($sla->getFromDB($this->fields['slas_id_tto'])) { $sla_tto_link = " @@ -5474,16 +5439,9 @@ public function showStatsDates() $sla_ttr_link = " "; } - if ($ola->getFromDB($this->fields['olas_id_tto'])) { - $ola_tto_link = " - "; - } - if ($ola->getFromDB($this->fields['olas_id_ttr'])) { - $ola_ttr_link = " - "; - } - $dates = [ + $dates = $dates_olas; + $dates = array_merge($dates, [ $date_creation . '_date_creation' => [ 'timestamp' => $date_creation, 'label' => __('Opening date'), @@ -5494,14 +5452,6 @@ public function showStatsDates() 'label' => __('Take into account'), 'class' => 'checked', ], - $internal_time_to_own . '_internal_time_to_own' => [ - 'timestamp' => $internal_time_to_own, - 'label' => __('Internal time to own') . " " . $ola_tto_link, - 'class' => ($internal_time_to_own < $goal_takeintoaccount - ? 'passed' : '') . " " - . ($date_takeintoaccount != '' - ? 'checked' : ''), - ], $time_to_own . '_time_to_own' => [ 'timestamp' => $time_to_own, 'label' => __('Time to own') . " " . $sla_tto_link, @@ -5510,14 +5460,6 @@ public function showStatsDates() . ($date_takeintoaccount != '' ? 'checked' : ''), ], - $internal_time_to_resolve . '_internal_time_to_resolve' => [ - 'timestamp' => $internal_time_to_resolve, - 'label' => __('Internal time to resolve') . " " . $ola_ttr_link, - 'class' => ($internal_time_to_resolve < $goal_solvedate - ? 'passed' : '') . " " - . ($solvedate != '' - ? 'checked' : ''), - ], $time_to_resolve . '_time_to_resolve' => [ 'timestamp' => $time_to_resolve, 'label' => __('Time to resolve') . " " . $sla_ttr_link, @@ -5536,7 +5478,7 @@ public function showStatsDates() 'label' => __('Closing date'), 'class' => 'end', ], - ]; + ]); Html::showDatesTimelineGraph([ 'title' => _n('Date', 'Dates', Session::getPluralNumber()), @@ -5551,17 +5493,21 @@ protected function fillInputForBusinessRules(array &$input) // add SLA/OLA (for business rules) if (!$this->isNewItem()) { + // sla foreach ([SLM::TTR, SLM::TTO] as $slmType) { [$dateField, $slaField] = SLA::getFieldNames($slmType); if (!isset($input[$slaField]) && isset($this->fields[$slaField]) && $this->fields[$slaField] > 0) { $input[$slaField] = $this->fields[$slaField]; } - [$dateField, $olaField] = OLA::getFieldNames($slmType); - if (!isset($input[$olaField]) && isset($this->fields[$olaField]) && $this->fields[$olaField] > 0) { - $input[$olaField] = $this->fields[$olaField]; - } } } + + // ola - currently associated ola + // if $input_['_olas_id'] + _la_update is set, we are updating/adding ola + // otherwise, use the current associated ola data + $input['_olas_id_rule_criteria'] = (isset($input['_la_update']) && isset($input['_olas_id'])) + ? $input['_olas_id'] + : array_column($this->getOlasData(), 'olas_id'); } /** @@ -6196,17 +6142,11 @@ public function processRules(int $condition, array &$input, int $entid = -1): vo // Business Rules do not override manual SLA and OLA $manual_slas_id = []; - $manual_olas_id = []; foreach ([SLM::TTR, SLM::TTO] as $slmType) { [$dateField, $slaField] = SLA::getFieldNames($slmType); if (isset($input[$slaField]) && ($input[$slaField] > 0)) { $manual_slas_id[$slmType] = $input[$slaField]; } - - [$dateField, $olaField] = OLA::getFieldNames($slmType); - if (isset($input[$olaField]) && ($input[$olaField] > 0)) { - $manual_olas_id[$slmType] = $input[$olaField]; - } } parent::processRules($condition, $input, $entid); @@ -6221,14 +6161,12 @@ public function processRules(int $condition, array &$input, int $entid = -1): vo unset($input['_users_id_requester_notif']); } } - if (!isset($input['_skip_sla_assign']) || $input['_skip_sla_assign'] === false) { - // Manage SLA / OLA asignment - // Manual SLA / OLA defined : reset due date - // No manual SLA / OLA and due date defined : reset auto SLA / OLA - foreach ([SLM::TTR, SLM::TTO] as $slmType) { - $this->slaAffect($slmType, $input, $manual_slas_id); - $this->olaAffect($slmType, $input, $manual_olas_id); - } + + // Manage SLA asignment + // Manual SLA defined : reset due date + // No manual SLA and due date defined : reset auto SLA + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + $this->slaAffect($slmType, $input, $manual_slas_id); } } @@ -6292,12 +6230,7 @@ public static function getListForItemRestrict(CommonDBTM $item) break; case $item instanceof OLA: - $restrict[] = [ - 'OR' => [ - 'olas_id_tto' => $item->getID(), - 'olas_id_ttr' => $item->getID(), - ], - ]; + $restrict['glpi_items_olas.olas_id'] = $item->getID(); break; case $item instanceof Supplier: @@ -6348,6 +6281,230 @@ public static function getListForItemRestrict(CommonDBTM $item) return $restrict; } + /** + * Ticket Olas data from database if exists or an empty array + * + * Get currently associated Ola from database + * Data from ola + item_ola + custom data + * + * @return array + */ + public function getOlasData(): array + { + return $this->isNewItem() + ? [] + : (new Item_Ola())->getDataFromDBForTicket($this); + } + + /** + * Olas data from form input + * + * Unlike getOlasData(), it relies on $this->fields['_olas_id'] field because the field could be filled by a template. + * + * @return array + * + * @used-by templates/components/itilobject/service_levels.html.twig + */ + public function getOlasDataFromField(): array + { + return (new Item_Ola())->getDataFromOlasIdsForTicket($this, $this->fields['_olas_id'] ?? []); + } + + /** + * @return array + */ + public function getOlasTTOData(): array + { + return array_values(array_filter($this->getOlasData(), fn(array $ola) => $ola['type'] === SLM::TTO)); + } + + /** + * @return array + */ + public function getOlasTTRData(): array + { + return array_values(array_filter($this->getOlasData(), fn(array $ola) => $ola['type'] === SLM::TTR)); + } + + /** + * Ticket slas + * + * Slas linked using slas_id_ttr and slas_id_tto + * Fields of Sla table + fields added below ('due_time', 'class', 'item', 'nextaction', 'level') + * + * used in template templates/components/itilobject/service_levels.html.twig + * + * @return array + */ + public function getSlasData(): array + { + $slas = []; + + $ttr = new SLA(); + if ($ttr->getFromDB($this->fields['slas_id_ttr'])) { + $sla = $ttr->fields; + + $sla['due_time'] = $this->fields['time_to_resolve']; + $sla['class'] = $ttr::class; // SLA::class + $sla['item'] = $this; // object, not just fields, functions used in template + $sla['nextaction'] = $ttr->getNextActionForTicket($this, $sla['type']); + $sla['level'] = $ttr->getLevelFromAction($sla['nextaction']); + + $slas[] = $sla; + } + + $tto = new SLA(); + if ($tto->getFromDB($this->fields['slas_id_tto'])) { + $sla = $tto->fields; + $sla['due_time'] = $this->fields['time_to_own']; + $sla['class'] = $tto::class; + $sla['item'] = $this; // object, not just fields, functions used in template + $sla['nextaction'] = $ttr->getNextActionForTicket($this, $sla['type']); + $sla['level'] = $tto->getLevelFromAction($sla['nextaction']); + + $slas[] = $sla; + } + + return $slas; + } + + private function updateOlaAssociations(bool $on_ticket_creation): void + { + // handle _olas_id fiels only if _la_update is set + if (!isset($this->input['_la_update'])) { + return; + } + // input['_olas_id'] must be an array if defined + if (isset($this->input['_olas_id']) && !is_array($this->input['_olas_id'])) { + throw new InvalidArgumentException('Input "_olas_id" must be an array.'); + } + + $request_olas_ids = $this->input['_olas_id'] ?? []; + $current_olas_ids = array_column($this->getOlasData(), 'olas_id'); + + $items_ola = new Item_Ola(); + // remove olas no more associated unless _la_append field is set + $removed_olas_ids = array_diff($current_olas_ids, $request_olas_ids); + if (isset($this->input['_la_append']) && $this->input['_la_append'] === true) { + // do not remove olas, just add new ones + $removed_olas_ids = []; + } + foreach ($removed_olas_ids as $olas_id) { + // find item_ola id + $items_ola->getFromDBByCrit([ + 'items_id' => $this->getID(), + 'itemtype' => static::class, + 'olas_id' => $olas_id, + ]); + + if (!$items_ola->delete(['id' => $items_ola->getID()])) { + throw new Exception("Failed to dissociate OLA #$olas_id from ticket #{$this->getID()}"); + } + } + + // add new olas + // deduplicate ola (but not completed) + $toadd_olas_ids = Item_Ola::filterInputOlas(static::class, $this->getID(), $request_olas_ids); + + $items_ola = new Item_Ola(); + foreach ($toadd_olas_ids as $olas_id) { + $ola = new OLA(); + $ola->getFromDB($olas_id); + // insert in association table items_ola + $ola_start_time = $on_ticket_creation ? $this->fields['date'] : Session::getCurrentTime(); + if ( + !$items_ola->add([ + 'items_id' => $this->getID(), + 'itemtype' => static::class, + 'olas_id' => $ola->getID(), + 'ola_type' => $ola->fields['type'], + // on creation, use date from ticket creation, on update use current time + 'start_time' => $ola_start_time, + 'waiting_start' => $this->fields['status'] === CommonITILObject::WAITING ? $ola_start_time : null, + ]) + ) { + throw new Exception("Failed to associate OLA #$olas_id to ticket #{$this->getID()}"); + } + } + + OLA::deleteLevelsToDo($this); + $current_olas_ids = array_column($this->getOlasData(), 'olas_id'); + foreach ($current_olas_ids as $olas_id) { + $this->manageOlaLevel($olas_id); + } + } + + /** + * Update Ola data + Manage OLA level + * + * @param int[] $new_assigned_groups + * @param int[] $new_assigned_users + */ + private function recomputeOlas(array $new_assigned_groups = [], array $new_assigned_users = []): void + { + // recompute all OLA for this ticket + OLA::deleteLevelsToDo($this); // todo levels are rebuild in Item_Ola::compute() + $olas = $this->getOlasData(); + foreach ($olas as $ola) { + Item_Ola::compute($this, (int) $ola['olas_id'], (int) $ola['items_olas_id']); + } + } + + /** + * Modify input for Ola to allow backward compatibility + * + * olas_id_tto, olas_id_ttr fields are not used anymore + * + * @param array $input + * @return array + */ + private function transformOlasInputForBackwardCompatibility(array $input): array + { + // no old/deprecated fields, return array unchanged + if (!isset($input['olas_id_tto']) && !isset($input['olas_id_ttr'])) { + return $input; + } + + // old and new fields set at the same time. (by a plugin not up to date) + // preserve previous behavior : replace olas set by legacy fields & ignore olas set by new field (_input['_olas_id']) + $input['_olas_id'] = []; + $input['_la_update'] = true; + + // remove old fields and add new fields + if (isset($input['olas_id_tto'])) { + Toolbox::deprecated('Passing `olas_id_tto` input to ticket is deprecated.' + . ' Use `_olas_id` (array) + `_la_update` instead.', version: "11.0"); + $input['_olas_id'][] = $input['olas_id_tto']; + unset($input['olas_id_tto']); + } + if (isset($input['olas_id_ttr'])) { + Toolbox::deprecated('Passing `olas_id_ttr` input to ticket is deprecated.' + . ' Use `_olas_id` (array) + `_la_update` instead.', version: "11.0"); + $input['_olas_id'][] = $input['olas_id_ttr']; + unset($input['olas_id_ttr']); + } + + return $input; + } + + /** + * @param array $input + */ + private function failsWithOlaRemovedFields(array $input): void + { + $ola_removed_inputs = ['ola_tto_begin_date', 'ola_ttr_begin_date', 'internal_time_to_resolve', 'internal_time_to_own', 'olalevels_id_ttr']; + if (!array_intersect($ola_removed_inputs, array_keys($input))) { + return; + } + + foreach ($ola_removed_inputs as $ola_removed_input) { + if (isset($input[$ola_removed_input])) { + // @todo cumuler les champs déprécier pour lever exception avec tous les champs. + throw new RuntimeException('Input field "' . $ola_removed_input . '" is not used anymore, Ola are only associated now, use "_olas_id" please update your code. see Ticket.php docbloc.'); + } + } + } + private function getSatisfactionSurveyForHelpdesk(): ?TicketSatisfaction { // On the "central" interface, the survey will be available in a diff --git a/src/TicketRecurrent.php b/src/TicketRecurrent.php index 5ad42ada8cc..de761ea47dc 100644 --- a/src/TicketRecurrent.php +++ b/src/TicketRecurrent.php @@ -76,15 +76,6 @@ public function handlePredefinedFields( ): array { $input = parent::handlePredefinedFields($predefined, $input); - // Compute internal_time_to_resolve if predefined based on create date - if (isset($predefined['internal_time_to_resolve'])) { - $input['internal_time_to_resolve'] = Html::computeGenericDateTimeSearch( - $predefined['internal_time_to_resolve'], - false, - $this->getCreateTime() - ); - } - return $input; } diff --git a/src/TicketTemplate.php b/src/TicketTemplate.php index 89eda9cbadf..27264d21683 100644 --- a/src/TicketTemplate.php +++ b/src/TicketTemplate.php @@ -102,31 +102,13 @@ public static function getExtraAllowedFields($withtypeandcategory = false, $with 'slas_id_ttr', 'glpi_slas' ) => 'slas_id_ttr', - $itil_object->getSearchOptionIDByField( - 'field', - 'olas_id_tto', - 'glpi_olas' - ) => 'olas_id_tto', - $itil_object->getSearchOptionIDByField( - 'field', - 'olas_id_ttr', - 'glpi_olas' - ) => 'olas_id_ttr', + 190 => '_olas_id_tto', + 191 => '_olas_id_ttr', $itil_object->getSearchOptionIDByField( 'field', 'time_to_own', 'glpi_tickets' ) => 'time_to_own', - $itil_object->getSearchOptionIDByField( - 'field', - 'internal_time_to_resolve', - 'glpi_tickets' - ) => 'internal_time_to_resolve', - $itil_object->getSearchOptionIDByField( - 'field', - 'internal_time_to_own', - 'glpi_tickets' - ) => 'internal_time_to_own', $itil_object->getSearchOptionIDByField( 'field', 'global_validation', diff --git a/src/Toolbox.php b/src/Toolbox.php index 495e28cddc1..5eaf08ab8e7 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -68,6 +68,7 @@ use Safe\Exceptions\UrlException; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\HttpException; use function Safe\base64_decode; @@ -79,10 +80,10 @@ use function Safe\curl_init; use function Safe\error_log; use function Safe\fclose; -use function Safe\file_get_contents; use function Safe\filemtime; use function Safe\finfo_open; use function Safe\fopen; +use function Safe\fread; use function Safe\fwrite; use function Safe\getimagesize; use function Safe\gzcompress; @@ -645,14 +646,18 @@ public static function getFileAsResponse( ); } - try { - $content = file_get_contents($path); - } catch (FilesystemException $e) { - throw new HttpException(500, $e->getMessage(), $e); - } + return new StreamedResponse( + function () use ($path) { + $file_stream = fopen($path, 'r'); - return new Response( - content: $content, + // Flush the response into small chunks to prevent loading the whole file contents into the memory. + // This is mandatory to prevent memory exhaustion when sending huge files. + while (!feof($file_stream)) { + echo fread($file_stream, 8192); + flush(); + } + fclose($file_stream); + }, status: 200, headers: $headers ); diff --git a/templates/components/dates_timeline.html.twig b/templates/components/dates_timeline.html.twig index bb5f8d73416..070d4fcde27 100644 --- a/templates/components/dates_timeline.html.twig +++ b/templates/components/dates_timeline.html.twig @@ -37,7 +37,7 @@
    {% for data in dates %} - {% if data['timestamp'] != 0 %} + {% if data['timestamp'] is not null %}
  •