Compare commits

..

4 Commits

Author SHA1 Message Date
Eric Espie
4ada74e63f Debug data within the form 2025-10-22 17:37:38 +02:00
Eric Espie
0ccb452ab7 merge 2025-10-22 16:55:09 +02:00
Benjamin Dalsass
0dae7346d1 N°8772 - Form dependencies manager implementation
- turbo implementation
2025-10-20 15:16:44 +02:00
Benjamin Dalsass
cdfded766f N°8772 - Form dependencies manager implementation 2025-10-17 09:03:45 +02:00
3821 changed files with 73249 additions and 215431 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

View File

@@ -1,9 +0,0 @@
# Developers
## PHP Code Styles
We use `PHP CS Fixer` to ensure code formating consistency across PHP codebase.
You can find the configuration and instructions to run it [here](../tests/php-code-style/README.md).
## PHP Static Analysis
We use `PHPStan` to ensure code quality and to detect potential bugs in our PHP codebase.
You can find the configuration and instructions to run it [here](../tests/php-static-analysis/README.md).

View File

@@ -7,8 +7,7 @@
'git4': 'grey', 'git4': 'grey',
'git5': 'grey', 'git5': 'grey',
'git6': 'grey', 'git6': 'grey',
'git7': 'grey', 'git7': 'grey'
'git8': 'grey'
}, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%% }, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
gitGraph gitGraph
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
@@ -87,25 +86,24 @@ gitGraph
commit id: "2024-01-17a" tag: "2.7.10" commit id: "2024-01-17a" tag: "2.7.10"
checkout support/3.0 checkout support/3.0
commit id: "2024-01-17b" tag: "3.0.4" commit id: "2024-01-17b" tag: "3.0.4"
checkout support/2.7
commit id: "2024-09-28" tag: "2.7.11"
checkout support/3.1
commit id: "2024-09-27" tag: "3.1.2"
checkout support/3.2 checkout support/3.2
commit id: "2024-06-25" tag: "3.2.0-beta1" type: REVERSE commit id: "2024-06-25" tag: "3.2.0-beta1" type: REVERSE
commit id: "2024-08-07" tag: "3.2.0" commit id: "2024-08-07" tag: "3.2.0"
checkout support/2.7 commit id: "2024-09-13" tag: "3.2.0-2"
commit id: "2025-02-07a" tag: "2.7.12"
checkout support/3.1 checkout support/3.1
commit id: "2025-02-07b" tag: "3.1.3" commit id: "2024-09-27" tag: "3.1.2"
checkout support/3.2
commit id: "2025-02-07c " tag: "3.2.1"
commit id: "2025-03-31 " tag: "3.2.1-1"
commit id: "2025-07-28 " tag: "3.2.2"
checkout support/2.7 checkout support/2.7
commit id: "2025-09-25" tag: "2.7.13" commit id: "2024-09-28" tag: "2.7.11"
checkout support/2.7
commit id: "2025-02-25" tag: "2.7.12"
checkout support/3.1
commit id: "2025-02-25 " tag: "3.1.3"
checkout support/3.2 checkout support/3.2
commit id: "2026-04-27 " tag: "3.2.3" commit id: "2025-02-25 " tag: "3.2.1"
commit id: "2025-04-08" tag: "3.2.1-1"
commit id: "2025-08-19" tag: "3.2.2-1"
checkout support/2.7
commit id: "2025-10-07" tag: "2.7.13"
``` ```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start). To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

View File

@@ -1,60 +0,0 @@
name: "Bug report"
description: "Report a bug that you identified in iTop"
type: bug
body:
- type: markdown
attributes:
value: |
Please explain why you're creating this issue :
- Are you willing to create a PR for the bug fix ? If so, we'll indicate in the issue if we're interested in it.
- Then, please describe how to reproduce the issue.
- type: dropdown
id: willing_to_pr
attributes:
label: Are you willing to create (at a later stage) a PR for that?
options:
- 'Yes'
- 'No'
validations:
required: true
- type: input
id: itop_version
attributes:
label: iTop version
description: "Complete iTop version (e.g., 3.2.3)"
validations:
required: false
- type: input
id: php_version
attributes:
label: PHP version
description: "Complete PHP version (e.g., 8.4.20)"
validations:
required: false
- type: textarea
id: reproduction_steps
attributes:
label: Reproduction procedure
description: |
Please explain step by step how to reproduce the issue on a standard iTop Community.
If it requires a custom datamodel, provide the minimal XML delta to reproduce it.
placeholder: |
1. First go there
2. Then do that
3. ...
4. Finally, see that... (what is expected and what is actually happening)
validations:
required: false
- type: upload
id: additional_info
attributes:
label: Additional information (if needed)
description: "Add/drag and drop screenshots, logs or any files that can be relevant for your issue."
validations:
required: false
accept: ".png, .jpg, .jpeg, .gif, .webp, .log, .txt, .json, .csv, .xml, .zip"

View File

@@ -1,47 +0,0 @@
name: "Enhancement suggestion"
description: "Suggest an improvement to iTop"
type: feature
body:
- type: markdown
attributes:
value: |
Please explain why you're creating this issue :
- Please describe what's your improvement proposition.
- Then tell us if you're willing to create a PR for this enhancement ? If so, we'll indicate in the issue if we're interested in it.
- type: textarea
id: enhancement_details
attributes:
label: Enhancement details
description: |
Please explain what you want to improve, and your proposition to make it better.
If it requires a custom datamodel, provide the minimal XML delta to reproduce it.
validations:
required: false
- type: input
id: itop_version
attributes:
label: iTop version (if appropriate)
description: "Complete iTop version (e.g., 3.2.3)"
validations:
required: false
- type: dropdown
id: willing_to_pr
attributes:
label: Are you willing to create (at a later stage) a PR for that?
options:
- 'Yes'
- 'No'
validations:
required: true
- type: upload
id: additional_info
attributes:
label: Additional information (if needed)
description: "Add/drag and drop screenshots, logs or any files that can be relevant for your issue."
validations:
required: false
accept: ".png, .jpg, .jpeg, .gif, .webp, .log, .txt, .json, .csv, .xml, .zip"

View File

@@ -1,81 +1,83 @@
<!-- <!--
IMPORTANT: Before creating your PR, please create an issue first to know if Combodo is interested in your contribution (not needed for translations PR).
Since we may refuse a PR, it's preferable to create an issue first, to avoid spending time coding something that won't be accepted.
Once you've done it, and we confirmed we're interested in it, please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏 IMPORTANT: Please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
Any PRs not following the guidelines or with missing information will not be considered. Any PRs not following the guidelines or with missing information will not be considered.
--> -->
## Base information ## Base information
| Question | Answer | Question | Answer
|----------------------------------------------------------------|-------- |---------------------------------------------------------------|--------
| Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket? | <!-- Put the URL --> | | Related to a SourceForge thead / Another PR / Combodo ticket? | <!-- Put the URL -->
| Type of change? | Bug fix / Enhancement / Translations | Type of change? | Bug fix / Enhancement / Translations
| Question | Answer |
|---------------------------------------------------------------------------------|--------------------------------------|
| Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket? | <!-- Put the URL --> |
| Type of change? | Bug fix / Enhancement / Translations |
## Symptom (bug) / Objective (enhancement) ## Symptom (bug) / Objective (enhancement)
<!-- <!--
If it's a bug If it's a bug
- Explain the symptom in details - Explain the symptom in details
- If possible put error messages, logs or screenshots (you can paste image directly in this editor). - If possible put error messages, logs or screenshots (you can paste image directly in this editor).
If it's an enhancement If it's an enhancement
- Describe what is blocking you, what is the objective with as many details as possible. - Describe what is blocking you, what is the objective with as much details as possible.
- Add screenshots if it's related to UI. - Add screenshots if it's related to UI.
--> -->
## Reproduction procedure (bug)
## Reproduction procedure (bug)
<!-- <!--
Please explain step by step how to reproduce the issue on a standard iTop Community. Remove this section only if it's NOT a bug.
Otherwise, explain step by step how to reproduce the issue on a standard iTop Community.
If it requires a custom datamodel, provide the minimal XML delta to reproduce it on a standard iTop Community. If it requires a custom datamodel, provide the minimal XML delta to reproduce it on a standard iTop Community.
--> -->
1. On iTop x.y.z <!-- Put complete iTop version (eg. 3.1.0-2) --> 1. On iTop x.y.z <!-- Put complete iTop version (eg. 3.1.0-2) -->
2. With PHP x.y.z <!-- Put complete PHP version (eg. 8.1.24) --> 2. With PHP x.y.z <!-- Put complete PHP version (eg. 8.1.24) -->
3. First go there 2. First go there
4. Then do that 2. Then do that
5. ... 3. ...
6. Finally, see that... (what is expected and what is actually happening) 4. Finally, see that...
## Reproduction procedure (enhancement - if needed)
<!--
Please explain how we can reproduce the feature/behavior you want to improve, and what's your proposition to make it better.
Add screenshots if it's related to UI.
If it requires a custom datamodel, provide the minimal XML delta to reproduce it on a standard iTop Community.
-->
## Cause (bug) ## Cause (bug)
<!-- <!--
Remove this section only if it's NOT a bug. Remove this section only if it's NOT a bug.
Otherwise, explain what is the cause of the issue (where in the code and why) Otherwise, explain what is the cause of the issue (where in the code and why)
--> -->
## Proposed solution (bug and enhancement)
## Proposed solution (bug and enhancement)
<!-- <!--
Explain in details how you are proposing to solve this: Explain in details how you are proposing to solve this:
- What did you do in the code and why - What did you do in the code and why
- If you changed something in the UI, put before / after screenshots (you can paste image directly in this editor) - If you changed something in the UI, put before / after screenshots (you can paste image directly in this editor)
--> -->
## Checklist before requesting a review
## Checklist before requesting a review
<!-- <!--
Don't remove these lines, check them once done. Don't remove these lines, check them once done.
--> -->
- [ ] I have performed a self-review of my code - [ ] I have performed a self-review of my code
- [ ] I have tested all changes I made on an iTop instance - [ ] I have tested all changes I made on an iTop instance
- [ ] I have added a unit test, otherwise I have explained why I couldn't - [ ] I have added a unit test, otherwise I have explained why I couldn't
- [ ] Is the PR clear and detailed enough so anyone can understand without digging in the code? - [ ] Is the PR clear and detailed enough so anyone can understand digging in the code?
## Checklist of things to do before PR is ready to merge
<!--
Things that needs to be done in the PR before it can be considered as ready to be merged
Examples:
- Changes requested in the review
- Unit test to add
- Dictionary entries to translate
- ...
-->
- [ ] ...
- [ ] ...
- [ ] ...

View File

@@ -1,13 +1,9 @@
name: Add PRs to Combodo PRs Dashboard name: Add PRs to Combodo PRs Dashboard
on: on:
pull_request: pull_request_target:
types: types:
- opened - opened
issues:
types:
- opened
workflow_call:
jobs: jobs:
add-to-project: add-to-project:
@@ -30,27 +26,18 @@ jobs:
fi fi
- name: Add internal tag if member of the organization - name: Add internal tag if member
if: env.is_member == 'true' if: env.is_member == 'true'
run: | run: |
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \ curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
-H "Accept: application/vnd.github.v3+json" \ -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels \ https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
-d '{"labels":["internal"]}' -d '{"labels":["internal"]}'
- name: Set PR author as assignee if member of the organization
if: env.is_member == 'true' && github.event_name == 'pull_request'
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/assignees \
-d '{"assignees":["${{ github.event.pull_request.user.login }}"]}'
env: env:
is_member: ${{ env.is_member }} is_member: ${{ env.is_member }}
- name: Add PR to the appropriate project - name: Add PR to the appropriate project
uses: actions/add-to-project@v2 uses: actions/add-to-project@v1.0.2
with: with:
project-url: ${{ env.project_url }} project-url: ${{ env.project_url }}
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }} github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}

3
.gitignore vendored
View File

@@ -58,9 +58,6 @@ tests/*/vendor/*
/tests/php-unit-tests/phpunit.xml /tests/php-unit-tests/phpunit.xml
/tests/php-unit-tests/postbuild_integration.xml /tests/php-unit-tests/postbuild_integration.xml
# PHP CS Fixer: Cache file
/.php-cs-fixer.cache
# Jetbrains # Jetbrains
/.idea/** /.idea/**

View File

@@ -199,7 +199,7 @@ class DatamodelsXmlFiles extends AbstractGlobFileVersionUpdater
libxml_clear_errors(); libxml_clear_errors();
$oFileXml->formatOutput = true; $oFileXml->formatOutput = true;
$oFileXml->preserveWhiteSpace = false; $oFileXml->preserveWhiteSpace = false;
$oFileXml->loadXML($sFileContent, LIBXML_BIGLINES); $oFileXml->loadXML($sFileContent);
$oFileItopFormat = new iTopDesignFormat($oFileXml); $oFileItopFormat = new iTopDesignFormat($oFileXml);

View File

@@ -1,16 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace PHPSTORM_META
{
override(\MetaModel::NewObject(0), map([
'' => '@',
]));
override(\MetaModel::GetObject(0), map([
'' => '@',
]));
}

View File

@@ -4,33 +4,41 @@ You want to contribute to iTop? Many thanks to you! 🎉 👍
Here are some guidelines that will help us integrate your work! Here are some guidelines that will help us integrate your work!
## Contributions ## Contributions
### Subjects
```
_ _ _ _ _ __ _
| | | | __ _ ___| | _| |_ ___ | |__ ___ _ __ / _| ___ ___| |_
| |_| |/ _` |/ __| |/ / __/ _ \| '_ \ / _ \ '__| |_ / _ \/ __| __|
| _ | (_| | (__| <| || (_) | |_) | __/ | | _| __/\__ \ |_
|_| |_|\__,_|\___|_|\_\\__\___/|_.__/ \___|_| |_| \___||___/\__|
```
This repository is part of Hacktoberfest. Contributions are welcome! Feel free to suggest improvements, add translation, fix bugs, or propose new features. Thank you for contributing !
### Subjects
You are welcome to create pull requests on any of those subjects: You are welcome to create pull requests on any of those subjects:
* 🐛 bug fix * 🐛 bug fix
* 🌐 translation / i18n / l10n * 🌐 translation / i18n / l10n
* 🚸 enhancement
But before creating a PR, please [create a corresponding issue][itop-issues] for review. If you want to implement a **new feature**, please [create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) for review.
We should review within two weeks, and get back to you to indicate if we're interested in your proposal or not. If you ever want to begin implementation, do so in a fork, and add a link to the corresponding commits in the ticket.
If you don't create an issue, you won't know if we're interested in your contribution, and you may spend time coding something that won't be accepted.
If you ever want to begin implementation, do so in a fork, and add a link to the corresponding commits in the issue.
For all **security related subjects**, please see our [security policy](SECURITY.md). For all **security related subjects**, please see our [security policy](SECURITY.md).
All **datamodel modification** should be done in an extension. Beware that such change would All **datamodel modification** should be done in an extension. Beware that such change would
impact all existing customers, and could prevent them from upgrading! impact all existing customers, and could prevent them from
upgrading!
Combodo has a long experience of datamodel changes: they are very disruptive! Combodo has a long experience of datamodel changes: they are very disruptive!
This is why we avoid them in iTop core, especially the changes on existing objects/fields. This is why we avoid them in iTop core, especially the changes on existing objects/fields.
If you have an idea you're sure would benefit to all of iTop users, you may If you have an idea you're sure would benefit to all of iTop users, you may
[create a corresponding issue][itop-issues] to submit it, but be warned that there are lots of good [create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) to submit it, but be warned that there are lots of good
reasons to refuse such changes. reasons to refuse such changes.
### 📄 License and copyright ### 📄 License and copyright
iTop is distributed under the AGPL-3.0 license (see the [license.txt] file). iTop is distributed under the AGPL-3.0 license (see the [license.txt] file).
The iTop repository is divided in three parts: iTop (mainly PHP/JS/XML sources and dictionaries), images, and third-party libraries. The iTop repository is divided in three parts: iTop (mainly PHP/JS/XML sources and dictionaries), images, and third-party libraries.
@@ -40,33 +48,48 @@ Anyhow, you are encouraged to signal your contribution by the mean of `@author`
If you want to use another license or keep the code ownership (copyright), you may [create an extension][wiki new ext]. If you want to use another license or keep the code ownership (copyright), you may [create an extension][wiki new ext].
[license.txt]: https://github.com/Combodo/iTop/blob/develop/license.txt [license.txt]: https://github.com/Combodo/iTop/blob/develop/license.txt
[itop-issues]: https://github.com/Combodo/iTop/issues
[wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension [wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension
## 🔀 iTop branch model ## 🔀 iTop branch model
When we first start with Git, we were using the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branch model. As When we first start with Git, we were using the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branch model. As
there was some confusions about branches to use for current developed release and previous maintained release, and also because we were there was some confusions about branches to use for current developed release and previous maintained release, and also because we were
using just a very few of the GitFlow commands, we decided to add just a little modification to this branch model : since April 2020 using just a very few of the GitFlow commands, we decided to add just a little modification to this branch model : since april 2020
we don't have a `master` branch anymore. we don't have a `master` branch anymore.
Here are the branches we use and their meaning : Here are the branches we use and their meaning :
- `develop`: ongoing development version - `develop`: ongoing development version
- `release/*`: if present, that means we are working on a alpha/beta/rc version for shipping
- `support/*`: maintenance branches for older versions - `support/*`: maintenance branches for older versions
For example, if no version is currently prepared for shipping we could have: For example, if no version is currently prepared for shipping we could have:
- `develop` containing future 3.3.0 version - `develop` containing future 3.1.0 version
- `support/3.2`: 3.2.x maintenance version - `support/3.0`: 3.0.x maintenance version
- `support/2.7`: 2.7.x maintenance version
- `support/2.6`: 2.6.x maintenance version
And when 3.3.0 will be out: In this example, when 3.1.0-beta is shipped that will become:
- `develop`: future 3.2.0 version
- `release/3.1.0`: 3.1.0-beta
- `support/3.0`: 3.0.x maintenance version
- `support/2.7`: 2.7.x maintenance version
- `support/2.6`: 2.6.x maintenance version
And when 3.1.0 final will be out:
- `develop`: future 3.2.0 version
- `support/3.1`: 3.1.x maintenance version (will host developments for 3.1.1)
- `support/3.0`: 3.0.x maintenance version
- `support/2.7`: 2.7.x maintenance version
- `support/2.6`: 2.6.x maintenance version
Also note that we have a "micro-version" concept : each of those versions have a very small amount of modifications. They are made from
`support/*` branches as well. For example 2.6.2-1 and 2.6.2-2 were made from the `support/2.6.2` branch.
- `develop`: future 3.4.0 version
- `support/3.3`: 3.3.x maintenance version (will host developments for 3.3.1)
- `support/3.2`: 3.2.x maintenance version
## Coding ## Coding
@@ -80,11 +103,12 @@ A [dedicated page](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3A
2. Create a branch in this fork, based on the develop branch 2. Create a branch in this fork, based on the develop branch
3. Code ! 3. Code !
Do create a dedicated branch for each modification you want to propose : if you don't, it will be very hard to merge back your work ! Do create a dedicated branch for each modification you want to propose : if you don't it will be very hard to merge back your work !
Most of the time you should base your developments on the develop branch. Most of the time you should based your developments on the develop branch.
That may be different if you want to fix a bug, please use develop anyway and ask in your PR if rebase is possible. That may be different if you want to fix a bug, please use develop anyway and ask in your PR if rebase is possible.
### 🎨 PHP styleguide ### 🎨 PHP styleguide
Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards). Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards).
@@ -93,7 +117,7 @@ Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acust
Please create tests that covers as much as possible the code you're submitting. Please create tests that covers as much as possible the code you're submitting.
Our tests are located in the `tests/` directory, containing a PHPUnit config file : `phpunit.xml.dist`. Our tests are located in the `test/` directory, containing a PHPUnit config file : `phpunit.xml.dist`.
### Git Commit Messages ### Git Commit Messages
@@ -127,12 +151,12 @@ When your code is working, please:
* Pull request description: mind to add all the information useful to understand why you're suggesting this modification and anything necessary to dive into your work. Especially: * Pull request description: mind to add all the information useful to understand why you're suggesting this modification and anything necessary to dive into your work. Especially:
- Bugfixes: exact steps to reproduce the bug (given/when/then), description of the bug cause and what solution is implemented - Bugfixes: exact steps to reproduce the bug (given/when/then), description of the bug cause and what solution is implemented
- Enhancements: use cases, implementation details if needed - Enhancements: use cases, implementation details if needed
* Mind to check the "[Allow edits from maintainers](https://docs.github.com/en/github-ae@latest/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)" option ! (note that if you are working with an org fork, this * Mind to check the "[Allow edits from maintainers](https://docs.github.com/en/github-ae@latest/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)" option ! (note that if you are working with an org fork, this option [won't be available](https://github.com/orgs/community/discussions/5634))
option [won't be available](https://github.com/orgs/community/discussions/5634))
## 🙏 We are thankful ## 🙏 We are thankful
We are thankful for all your contributions to the iTop universe! As a thank-you gift, we will send stickers to every iTop (& extensions) contributors! We are thankful for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors!
We have one sticker per contribution type. You might get multiple stickers with one contribution though :) We have one sticker per contribution type. You might get multiple stickers with one contribution though :)
@@ -146,6 +170,6 @@ We have one sticker per contribution type. You might get multiple stickers with
* Beta tester: Test and give feedback on beta releases * Beta tester: Test and give feedback on beta releases
* Extension developer: Develop and publish an extension * Extension developer: Develop and publish an extension
Here is the design of each sticker: Here is the design of each stickers for year 2024:
![iTop stickers](.doc/contributing-guide/contributing-stickers-side-by-side.png) ![iTop stickers 2025](.doc/contributing-guide/2025.contributing-stickers-side-by-side.png)

View File

@@ -53,7 +53,7 @@ iTop also offers mass import tools to help you become even more efficient.
[4]: https://www.itophub.io/wiki/page?id=latest:install:requirements [4]: https://www.itophub.io/wiki/page?id=latest:install:requirements
[5]: https://www.itophub.io/wiki [5]: https://www.itophub.io/wiki
[6]: https://store.itophub.io/en_US/ [6]: https://store.itophub.io/en_US/
[7]: .doc/itop-version-history.md [7]: itop-version-history.md
[10]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#configuration_management_cmdb [10]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#configuration_management_cmdb
[11]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#ticketing [11]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#ticketing
@@ -73,9 +73,6 @@ iTop development is sponsored, led, and supported by [Combodo][0].
[0]: https://www.combodo.com [0]: https://www.combodo.com
## Developers
You can find information and instructions about our quality tools and how to run them [here](.doc/developers.md).
## Contributors ## Contributors

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -30,52 +29,56 @@ class UserRightsBaseClassGUI extends cmdbAbstractObject
} }
} }
class URP_Profiles extends UserRightsBaseClassGUI class URP_Profiles extends UserRightsBaseClassGUI
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "addon/userrights,grant_by_profile,filter", "category" => "addon/userrights,grant_by_profile,filter",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"complementary_name_attcode" => ['description'], "complementary_name_attcode" => array('description'),
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_urp_profiles", "db_table" => "priv_urp_profiles",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("name", ["allowed_values" => null, "sql" => "name", "default_value" => null, "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("user_list", ["linked_class" => "URP_UserProfile", "ext_key_to_me" => "profileid", "ext_key_to_remote" => "userid", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("user_list", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"profileid", "ext_key_to_remote"=>"userid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['name', 'description', 'user_list']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['description']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['name','description']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('name','description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', ['name','description']); MetaModel::Init_SetZListItems('default_search', array ('name','description'));
} }
protected static $m_aCacheProfiles = null; protected static $m_aCacheProfiles = null;
public static function DoCreateProfile($sName, $sDescription) public static function DoCreateProfile($sName, $sDescription)
{ {
if (is_null(self::$m_aCacheProfiles)) { if (is_null(self::$m_aCacheProfiles))
self::$m_aCacheProfiles = []; {
self::$m_aCacheProfiles = array();
$oFilterAll = new DBObjectSearch('URP_Profiles'); $oFilterAll = new DBObjectSearch('URP_Profiles');
$oSet = new DBObjectSet($oFilterAll); $oSet = new DBObjectSet($oFilterAll);
while ($oProfile = $oSet->Fetch()) { while ($oProfile = $oSet->Fetch())
{
self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey(); self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey();
} }
} }
$sCacheKey = $sName; $sCacheKey = $sName;
if (isset(self::$m_aCacheProfiles[$sCacheKey])) { if (isset(self::$m_aCacheProfiles[$sCacheKey]))
{
return self::$m_aCacheProfiles[$sCacheKey]; return self::$m_aCacheProfiles[$sCacheKey];
} }
$oNewObj = MetaModel::NewObject("URP_Profiles"); $oNewObj = MetaModel::NewObject("URP_Profiles");
@@ -86,21 +89,27 @@ class URP_Profiles extends UserRightsBaseClassGUI
return $iId; return $iId;
} }
public function GetGrantAsHtml($oUserRights, $sClass, $sAction) function GetGrantAsHtml($oUserRights, $sClass, $sAction)
{ {
$bGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); $bGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction);
if (is_null($bGrant)) { if (is_null($bGrant))
return '<span class="ibo-user-rights ibo-is-failure">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>'; {
} elseif ($bGrant) { return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
return '<span class="ibo-user-rights ibo-is-success">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>'; }
} else { elseif ($bGrant)
return '<span class="ibo-user-rights ibo-is-failure">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>'; {
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
}
else
{
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
} }
} }
public function DoShowGrantSumary($oPage) function DoShowGrantSumary($oPage)
{
if ($this->GetRawName() == "Administrator")
{ {
if ($this->GetRawName() == "Administrator") {
// Looks dirty, but ok that's THE ONE // Looks dirty, but ok that's THE ONE
$oPage->p(Dict::S('UI:UserManagement:AdminProfile+')); $oPage->p(Dict::S('UI:UserManagement:AdminProfile+'));
return; return;
@@ -109,18 +118,21 @@ class URP_Profiles extends UserRightsBaseClassGUI
// Note: for sure, we assume that the instance is derived from UserRightsProfile // Note: for sure, we assume that the instance is derived from UserRightsProfile
$oUserRights = UserRights::GetModuleInstance(); $oUserRights = UserRights::GetModuleInstance();
$aDisplayData = []; $aDisplayData = array();
foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass) { foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass)
$aStimuli = []; {
foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) { $aStimuli = array();
foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus)
{
$bGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); $bGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode);
if ($bGrant === true) { if ($bGrant === true)
{
$aStimuli[] = '<span title="'.$sStimulusCode.': '.utils::EscapeHtml($oStimulus->GetDescription()).'">'.utils::EscapeHtml($oStimulus->GetLabel()).'</span>'; $aStimuli[] = '<span title="'.$sStimulusCode.': '.utils::EscapeHtml($oStimulus->GetDescription()).'">'.utils::EscapeHtml($oStimulus->GetLabel()).'</span>';
} }
} }
$sStimuli = implode(', ', $aStimuli); $sStimuli = implode(', ', $aStimuli);
$aDisplayData[] = [ $aDisplayData[] = array(
'class' => MetaModel::GetName($sClass), 'class' => MetaModel::GetName($sClass),
'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'r'), 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'r'),
'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'br'), 'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'br'),
@@ -129,22 +141,22 @@ class URP_Profiles extends UserRightsBaseClassGUI
'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'd'), 'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'd'),
'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'bd'), 'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'bd'),
'stimuli' => $sStimuli, 'stimuli' => $sStimuli,
]; );
} }
$aDisplayConfig = []; $aDisplayConfig = array();
$aDisplayConfig['class'] = ['label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')]; $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
$aDisplayConfig['read'] = ['label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')]; $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
$aDisplayConfig['bulkread'] = ['label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+')]; $aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+'));
$aDisplayConfig['write'] = ['label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+')]; $aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+'));
$aDisplayConfig['bulkwrite'] = ['label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+')]; $aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+'));
$aDisplayConfig['delete'] = ['label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+')]; $aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+'));
$aDisplayConfig['bulkdelete'] = ['label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+')]; $aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+'));
$aDisplayConfig['stimuli'] = ['label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+')]; $aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+'));
$oPage->table($aDisplayConfig, $aDisplayData); $oPage->table($aDisplayConfig, $aDisplayData);
} }
public function DisplayBareRelations(WebPage $oPage, $bEditMode = false) function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{ {
parent::DisplayBareRelations($oPage, $bEditMode); parent::DisplayBareRelations($oPage, $bEditMode);
@@ -154,9 +166,10 @@ class URP_Profiles extends UserRightsBaseClassGUI
public static function GetReadOnlyAttributes() public static function GetReadOnlyAttributes()
{ {
return ['name', 'description']; return array('name', 'description');
} }
// returns an array of id => array of column => php value(so-called "real value") // returns an array of id => array of column => php value(so-called "real value")
public static function GetPredefinedObjects() public static function GetPredefinedObjects()
{ {
@@ -168,13 +181,15 @@ class URP_Profiles extends UserRightsBaseClassGUI
protected function OnDelete() protected function OnDelete()
{ {
// Don't remove admin profile // Don't remove admin profile
if ($this->Get('name') === ADMIN_PROFILE_NAME) { if ($this->Get('name') === ADMIN_PROFILE_NAME)
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin')); throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin'));
} }
// Note: this may break the rule that says: "a user must have at least ONE profile" ! // Note: this may break the rule that says: "a user must have at least ONE profile" !
$oLnkSet = $this->Get('user_list'); $oLnkSet = $this->Get('user_list');
while ($oLnk = $oLnkSet->Fetch()) { while($oLnk = $oLnkSet->Fetch())
{
$oLnk->DBDelete(); $oLnk->DBDelete();
} }
} }
@@ -187,10 +202,11 @@ class URP_Profiles extends UserRightsBaseClassGUI
* @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used * @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used
* @return integer Flags: the binary combination of the flags applicable to this attribute * @return integer Flags: the binary combination of the flags applicable to this attribute
*/ */
public function GetAttributeFlags($sAttCode, &$aReasons = [], $sTargetState = '') public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
{ {
$iFlags = parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); $iFlags = parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
if (MetaModel::GetConfig()->Get('demo_mode')) { if (MetaModel::GetConfig()->Get('demo_mode'))
{
$aReasons[] = 'Sorry, profiles are read-only in the demonstration mode!'; $aReasons[] = 'Sorry, profiles are read-only in the demonstration mode!';
$iFlags |= OPT_ATT_READONLY; $iFlags |= OPT_ATT_READONLY;
} }
@@ -198,52 +214,52 @@ class URP_Profiles extends UserRightsBaseClassGUI
} }
} }
class URP_UserProfile extends UserRightsBaseClassGUI class URP_UserProfile extends UserRightsBaseClassGUI
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "addon/userrights,grant_by_profile,filter", "category" => "addon/userrights,grant_by_profile,filter",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => ["userlogin", "profile"], "name_attcode" => array("userlogin", "profile"),
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_urp_userprofile", "db_table" => "priv_urp_userprofile",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */ "is_link" => true, /** @since 3.1.0 N°6482 */
'uniqueness_rules' => [ 'uniqueness_rules' => array(
'no_duplicate' => [ 'no_duplicate' => array(
'attributes' => [ 'attributes' => array(
0 => 'userid', 0 => 'userid',
1 => 'profileid', 1 => 'profileid',
], ),
'filter' => '', 'filter' => '',
'disabled' => false, 'disabled' => false,
'is_blocking' => true, 'is_blocking' => true,
], ),
], ),
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", ["targetclass" => "User", "jointype" => "", "allowed_values" => null, "sql" => "userid", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass" => "User", "jointype" => "", "allowed_values" => null, "sql" => "userid", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", ["allowed_values" => null, "extkey_attcode" => 'userid', "target_attcode" => "login"])); MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values" => null, "extkey_attcode" => 'userid', "target_attcode" => "login")));
MetaModel::Init_AddAttribute(new AttributeExternalKey( MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid",
"profileid", array("targetclass" => "URP_Profiles", "jointype" => "", "allowed_values" => null, "sql" => "profileid", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array(), "allow_target_creation" => false)));
["targetclass" => "URP_Profiles", "jointype" => "", "allowed_values" => null, "sql" => "profileid", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => [], "allow_target_creation" => false] MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values" => null, "extkey_attcode" => 'profileid', "target_attcode" => "name")));
));
MetaModel::Init_AddAttribute(new AttributeExternalField("profile", ["allowed_values" => null, "extkey_attcode" => 'profileid', "target_attcode" => "name"]));
MetaModel::Init_AddAttribute(new AttributeString("reason", ["allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['userid', 'profileid', 'reason']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['userid', 'profileid', 'reason']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('userid', 'profileid', 'reason')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['userid', 'profileid']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', ['userid', 'profileid']); // Criteria of the advanced search form MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form
} }
public function CheckToDelete(&$oDeletionPlan) public function CheckToDelete(&$oDeletionPlan)
@@ -251,14 +267,15 @@ class URP_UserProfile extends UserRightsBaseClassGUI
if (MetaModel::GetConfig()->Get('demo_mode')) { if (MetaModel::GetConfig()->Get('demo_mode')) {
// Users deletion is NOT allowed in demo mode // Users deletion is NOT allowed in demo mode
$oDeletionPlan->AddToDelete($this, null); $oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, ['deletion not allowed in demo mode.'], true); $oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true);
$oDeletionPlan->ComputeResults(); $oDeletionPlan->ComputeResults();
return false; return false;
} }
try { try {
$this->CheckIfProfileIsAllowed(UR_ACTION_DELETE); $this->CheckIfProfileIsAllowed(UR_ACTION_DELETE);
} catch (SecurityException $e) { }
catch (SecurityException $e) {
// Users deletion is NOT allowed // Users deletion is NOT allowed
$oDeletionPlan->AddToDelete($this, null); $oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, [$e->getMessage()], true); $oDeletionPlan->SetDeletionIssues($this, [$e->getMessage()], true);
@@ -275,14 +292,15 @@ class URP_UserProfile extends UserRightsBaseClassGUI
if (MetaModel::GetConfig()->Get('demo_mode')) { if (MetaModel::GetConfig()->Get('demo_mode')) {
// Users deletion is NOT allowed in demo mode // Users deletion is NOT allowed in demo mode
$oDeletionPlan->AddToDelete($this, null); $oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, ['deletion not allowed in demo mode.'], true); $oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true);
$oDeletionPlan->ComputeResults(); $oDeletionPlan->ComputeResults();
return false; return false;
} }
try { try {
$this->CheckIfProfileIsAllowed(UR_ACTION_DELETE); $this->CheckIfProfileIsAllowed(UR_ACTION_DELETE);
} catch (SecurityException $e) { }
catch (SecurityException $e) {
// Users deletion is NOT allowed // Users deletion is NOT allowed
$oDeletionPlan->AddToDelete($this, null); $oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, [$e->getMessage()], true); $oDeletionPlan->SetDeletionIssues($this, [$e->getMessage()], true);
@@ -318,30 +336,29 @@ class URP_UserProfile extends UserRightsBaseClassGUI
protected function CheckIfProfileIsAllowed($iActionCode) protected function CheckIfProfileIsAllowed($iActionCode)
{ {
// When initializing or admin, we need to let everything pass trough // When initializing or admin, we need to let everything pass trough
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
return;
}
// Only administrators can manage administrators // Only administrators can manage administrators
$iOrigUserId = $this->GetOriginal('userid'); $iOrigUserId = $this->GetOriginal('userid');
if (!empty($iOrigUserId)) { if (!empty($iOrigUserId))
{
$oUser = MetaModel::GetObject('User', $iOrigUserId, true, true); $oUser = MetaModel::GetObject('User', $iOrigUserId, true, true);
if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator()) { if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator())
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted')); throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted'));
} }
} }
$oUser = MetaModel::GetObject('User', $this->Get('userid'), true, true); $oUser = MetaModel::GetObject('User', $this->Get('userid'), true, true);
if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator()) { if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator())
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted')); throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted'));
} }
if (!UserRights::IsActionAllowed(get_class($this), $iActionCode, DBObjectSet::FromObject($this)))
$oSet = new \ormLinkSet(get_class($oUser), 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class)); {
$oSet->AddItem(MetaModel::NewObject('URP_UserProfile', ['profileid' => $this->GetKey(), 'reason' => 'CheckIfProfileIsAllowed']));
if (!UserRights::IsActionAllowed(get_class($this), $iActionCode, $oSet)) {
throw new SecurityException(Dict::Format('UI:Error:ObjectCannotBeUpdated')); throw new SecurityException(Dict::Format('UI:Error:ObjectCannotBeUpdated'));
} }
if (!UserRights::IsAdministrator() && ($this->Get('profile') === ADMIN_PROFILE_NAME)) { if (!UserRights::IsAdministrator() && ($this->Get('profile') === ADMIN_PROFILE_NAME))
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin')); throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin'));
} }
} }
@@ -352,33 +369,33 @@ class URP_UserOrg extends UserRightsBaseClassGUI
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "addon/userrights,grant_by_profile", "category" => "addon/userrights,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => ["userlogin", "allowed_org_name"], "name_attcode" => array("userlogin", "allowed_org_name"),
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_urp_userorg", "db_table" => "priv_urp_userorg",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", ["targetclass" => "User", "jointype" => "", "allowed_values" => null, "sql" => "userid", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", ["allowed_values" => null, "extkey_attcode" => 'userid', "target_attcode" => "login"])); MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login")));
MetaModel::Init_AddAttribute(new AttributeExternalKey("allowed_org_id", ["targetclass" => "Organization", "jointype" => "", "allowed_values" => null, "sql" => "allowed_org_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("allowed_org_id", array("targetclass"=>"Organization", "jointype"=> "", "allowed_values"=>null, "sql"=>"allowed_org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("allowed_org_name", ["allowed_values" => null, "extkey_attcode" => 'allowed_org_id', "target_attcode" => "name"])); MetaModel::Init_AddAttribute(new AttributeExternalField("allowed_org_name", array("allowed_values"=>null, "extkey_attcode"=> 'allowed_org_id', "target_attcode"=>"name")));
MetaModel::Init_AddAttribute(new AttributeString("reason", ["allowed_values" => null, "sql" => "reason", "default_value" => null, "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['userid', 'allowed_org_id', 'reason']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('userid', 'allowed_org_id', 'reason')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['allowed_org_id', 'reason']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('allowed_org_id', 'reason')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['userid', 'allowed_org_id']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', ['userid', 'allowed_org_id']); // Criteria of the advanced search form MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form
} }
protected function OnInsert() protected function OnInsert()
@@ -401,32 +418,35 @@ class URP_UserOrg extends UserRightsBaseClassGUI
*/ */
protected function CheckIfOrgIsAllowed() protected function CheckIfOrgIsAllowed()
{ {
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
return;
}
$oUser = UserRights::GetUserObject(); $oUser = UserRights::GetUserObject();
$oAddon = UserRights::GetModuleInstance(); $oAddon = UserRights::GetModuleInstance();
$aOrgs = $oAddon->GetUserOrgs($oUser, ''); $aOrgs = $oAddon->GetUserOrgs($oUser, '');
if (count($aOrgs) > 0) { if (count($aOrgs) > 0)
{
$iOrigOrgId = $this->GetOriginal('allowed_org_id'); $iOrigOrgId = $this->GetOriginal('allowed_org_id');
if ((!empty($iOrigOrgId) && !in_array($iOrigOrgId, $aOrgs)) || !in_array($this->Get('allowed_org_id'), $aOrgs)) { if ((!empty($iOrigOrgId) && !in_array($iOrigOrgId, $aOrgs)) || !in_array($this->Get('allowed_org_id'), $aOrgs))
{
throw new SecurityException(Dict::Format('Class:User/Error:OrganizationNotAllowed')); throw new SecurityException(Dict::Format('Class:User/Error:OrganizationNotAllowed'));
} }
} }
} }
} }
class UserRightsProfile extends UserRightsAddOnAPI class UserRightsProfile extends UserRightsAddOnAPI
{ {
public static $m_aActionCodes = [ static public $m_aActionCodes = array(
UR_ACTION_READ => 'r', UR_ACTION_READ => 'r',
UR_ACTION_MODIFY => 'w', UR_ACTION_MODIFY => 'w',
UR_ACTION_DELETE => 'd', UR_ACTION_DELETE => 'd',
UR_ACTION_BULK_READ => 'br', UR_ACTION_BULK_READ => 'br',
UR_ACTION_BULK_MODIFY => 'bw', UR_ACTION_BULK_MODIFY => 'bw',
UR_ACTION_BULK_DELETE => 'bd', UR_ACTION_BULK_DELETE => 'bd',
]; );
/** /**
* @var array $aUsersProfilesList Cache of users' profiles. Hash array of user ID => [profile ID => profile friendlyname, profile ID => profile friendlyname, ...] * @var array $aUsersProfilesList Cache of users' profiles. Hash array of user ID => [profile ID => profile friendlyname, profile ID => profile friendlyname, ...]
@@ -452,7 +472,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oContact = MetaModel::NewObject('Person'); $oContact = MetaModel::NewObject('Person');
$oContact->Set('name', 'My last name'); $oContact->Set('name', 'My last name');
$oContact->Set('first_name', 'My first name'); $oContact->Set('first_name', 'My first name');
if (MetaModel::IsValidAttCode('Person', 'org_id')) { if (MetaModel::IsValidAttCode('Person', 'org_id'))
{
$oContact->Set('org_id', $iOrgId); $oContact->Set('org_id', $iOrgId);
} }
$oContact->Set('email', 'my.email@foo.org'); $oContact->Set('email', 'my.email@foo.org');
@@ -460,19 +481,24 @@ class UserRightsProfile extends UserRightsAddOnAPI
} }
} }
$oUser = new UserLocal(); $oUser = new UserLocal();
$oUser->Set('login', $sAdminUser); $oUser->Set('login', $sAdminUser);
$oUser->Set('password', $sAdminPwd); $oUser->Set('password', $sAdminPwd);
if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) { if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0))
{
$oUser->Set('contactid', $iContactId); $oUser->Set('contactid', $iContactId);
} }
$oUser->Set('language', $sLanguage); // Language was chosen during the installation $oUser->Set('language', $sLanguage); // Language was chosen during the installation
// Add this user to the very specific 'admin' profile // Add this user to the very specific 'admin' profile
$oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", ['name' => ADMIN_PROFILE_NAME], true /*all data*/); $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/);
if (is_object($oAdminProfile)) { if (is_object($oAdminProfile))
$oSet = new \ormLinkSet(UserLocal::class, 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class)); {
$oSet->AddItem(MetaModel::NewObject('URP_UserProfile', ['profileid' => $oAdminProfile->GetKey(), 'reason' => 'CreateAdministrator'])); $oUserProfile = new URP_UserProfile();
$oUserProfile->Set('profileid', $oAdminProfile->GetKey());
$oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile');
$oSet = DBObjectSet::FromObject($oUserProfile);
$oUser->Set('profile_list', $oSet); $oUser->Set('profile_list', $oSet);
} }
$iUserId = $oUser->DBInsertNoReload(); $iUserId = $oUser->DBInsertNoReload();
@@ -483,11 +509,11 @@ class UserRightsProfile extends UserRightsAddOnAPI
{ {
} }
protected $m_aUserOrgs = []; // userid -> array of orgid protected $m_aUserOrgs = array(); // userid -> array of orgid
protected $m_aAdministrators = null; // [user id] protected $m_aAdministrators = null; // [user id]
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read) // Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
protected $m_aObjectActionGrants = []; protected $m_aObjectActionGrants = array();
/** /**
* Read and cache organizations allowed to the given user * Read and cache organizations allowed to the given user
@@ -502,25 +528,31 @@ class UserRightsProfile extends UserRightsAddOnAPI
public function GetUserOrgs($oUser, $sClass) public function GetUserOrgs($oUser, $sClass)
{ {
$iUser = $oUser->GetKey(); $iUser = $oUser->GetKey();
if (!array_key_exists($iUser, $this->m_aUserOrgs)) { if (!array_key_exists($iUser, $this->m_aUserOrgs))
$this->m_aUserOrgs[$iUser] = []; {
$this->m_aUserOrgs[$iUser] = array();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization'); $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode !== false) { if ($sHierarchicalKeyCode !== false)
{
$sUserOrgQuery = 'SELECT UserOrg, Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' BELOW Root.id JOIN URP_UserOrg AS UserOrg ON UserOrg.allowed_org_id = Root.id WHERE UserOrg.userid = :userid'; $sUserOrgQuery = 'SELECT UserOrg, Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' BELOW Root.id JOIN URP_UserOrg AS UserOrg ON UserOrg.allowed_org_id = Root.id WHERE UserOrg.userid = :userid';
$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), [], ['userid' => $iUser]); $oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser));
while ($aRow = $oUserOrgSet->FetchAssoc()) { while ($aRow = $oUserOrgSet->FetchAssoc())
{
$oOrg = $aRow['Org']; $oOrg = $aRow['Org'];
$this->m_aUserOrgs[$iUser][] = $oOrg->GetKey(); $this->m_aUserOrgs[$iUser][] = $oOrg->GetKey();
} }
} else { }
else
{
$oSearch = new DBObjectSearch('URP_UserOrg'); $oSearch = new DBObjectSearch('URP_UserOrg');
$oSearch->AllowAllData(); $oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition); $oSearch->AddConditionExpression($oCondition);
$oUserOrgSet = new DBObjectSet($oSearch, [], ['userid' => $iUser]); $oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserOrg = $oUserOrgSet->Fetch()) { while ($oUserOrg = $oUserOrgSet->Fetch())
{
$this->m_aUserOrgs[$iUser][] = $oUserOrg->Get('allowed_org_id'); $this->m_aUserOrgs[$iUser][] = $oUserOrg->Get('allowed_org_id');
} }
} }
@@ -531,20 +563,21 @@ class UserRightsProfile extends UserRightsAddOnAPI
public function ResetCache() public function ResetCache()
{ {
// Loaded by Load cache // Loaded by Load cache
$this->m_aUserOrgs = []; $this->m_aUserOrgs = array();
// Cache // Cache
$this->m_aObjectActionGrants = []; $this->m_aObjectActionGrants = array();
$this->m_aAdministrators = null; $this->m_aAdministrators = null;
$this->aUsersProfilesList = [];
} }
public function LoadCache() public function LoadCache()
{ {
static $bSharedObjectInitialized = false; static $bSharedObjectInitialized = false;
if (!$bSharedObjectInitialized) { if (!$bSharedObjectInitialized)
{
$bSharedObjectInitialized = true; $bSharedObjectInitialized = true;
if (self::HasSharing()) { if (self::HasSharing())
{
SharedObject::InitSharedClassProperties(); SharedObject::InitSharedClassProperties();
} }
} }
@@ -582,40 +615,45 @@ class UserRightsProfile extends UserRightsAddOnAPI
*/ */
public function ListProfiles($oUser) public function ListProfiles($oUser)
{ {
$aRet = []; $aRet = array();
$oSearch = new DBObjectSearch('URP_UserProfile'); $oSearch = new DBObjectSearch('URP_UserProfile');
$oSearch->AllowAllData(); $oSearch->AllowAllData();
$oSearch->NoContextParameters(); $oSearch->NoContextParameters();
$oSearch->Addcondition('userid', $oUser->GetKey(), '='); $oSearch->Addcondition('userid', $oUser->GetKey(), '=');
$oProfiles = new DBObjectSet($oSearch); $oProfiles = new DBObjectSet($oSearch);
while ($oUserProfile = $oProfiles->Fetch()) { while ($oUserProfile = $oProfiles->Fetch())
{
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname'); $aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
} }
return $aRet; return $aRet;
} }
public function GetSelectFilter($oUser, $sClass, $aSettings = []) public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{ {
$this->LoadCache(); $this->LoadCache();
// Let us pass an administrator for bypassing the grant matrix check in order to test this method without the need to set up a complex profile // Let us pass an administrator for bypassing the grant matrix check in order to test this method without the need to set up a complex profile
// In the nominal case Administrators never end up here (since they completely bypass GetSelectFilter) // In the nominal case Administrators never end up here (since they completely bypass GetSelectFilter)
if (!static::IsAdministrator($oUser) && (MetaModel::HasCategory($sClass, 'silo') || MetaModel::HasCategory($sClass, 'bizmodel'))) { if (!static::IsAdministrator($oUser) && (MetaModel::HasCategory($sClass, 'silo') || MetaModel::HasCategory($sClass, 'bizmodel')))
{
// N°4354 - Categories 'silo' and 'bizmodel' do check the grant matrix. Whereas 'filter' always allows to read (but the result can be filtered) // N°4354 - Categories 'silo' and 'bizmodel' do check the grant matrix. Whereas 'filter' always allows to read (but the result can be filtered)
$aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ);
if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) { if ($aObjectPermissions['permission'] == UR_ALLOWED_NO)
{
return false; return false;
} }
} }
$oFilter = true; $oFilter = true;
$aConditions = []; $aConditions = array();
// Determine if this class is part of a silo and build the filter for it // Determine if this class is part of a silo and build the filter for it
$sAttCode = self::GetOwnerOrganizationAttCode($sClass); $sAttCode = self::GetOwnerOrganizationAttCode($sClass);
if (!is_null($sAttCode)) { if (!is_null($sAttCode))
{
$aUserOrgs = $this->GetUserOrgs($oUser, $sClass); $aUserOrgs = $this->GetUserOrgs($oUser, $sClass);
if (count($aUserOrgs) > 0) { if (count($aUserOrgs) > 0)
{
$oFilter = $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode); $oFilter = $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode);
} }
// else: No org means 'any org' // else: No org means 'any org'
@@ -624,15 +662,20 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Specific conditions to hide, for non-administrators, the Administrator Users, the Administrator Profile and related links // Specific conditions to hide, for non-administrators, the Administrator Users, the Administrator Profile and related links
// Note: when logged as an administrator, GetSelectFilter is completely bypassed. // Note: when logged as an administrator, GetSelectFilter is completely bypassed.
if ($this->AdministratorsAreHidden()) { if ($this->AdministratorsAreHidden())
if ($sClass == 'URP_Profiles') { {
if ($sClass == 'URP_Profiles')
{
$oExpression = new FieldExpression('id', $sClass); $oExpression = new FieldExpression('id', $sClass);
$oScalarExpr = new ScalarExpression(1); $oScalarExpr = new ScalarExpression(1);
$aConditions[] = new BinaryExpression($oExpression, '!=', $oScalarExpr); $aConditions[] = new BinaryExpression($oExpression, '!=', $oScalarExpr);
} elseif (($sClass == 'URP_UserProfile') || ($sClass == 'User') || (is_subclass_of($sClass, 'User'))) { }
else if (($sClass == 'URP_UserProfile') || ($sClass == 'User') || (is_subclass_of($sClass, 'User')))
{
$aAdministrators = $this->GetAdministrators(); $aAdministrators = $this->GetAdministrators();
if (count($aAdministrators) > 0) { if (count($aAdministrators) > 0)
{
$sAttCode = ($sClass == 'URP_UserProfile') ? 'userid' : 'id'; $sAttCode = ($sClass == 'URP_UserProfile') ? 'userid' : 'id';
$oExpression = new FieldExpression($sAttCode, $sClass); $oExpression = new FieldExpression($sAttCode, $sClass);
$oListExpr = ListExpression::FromScalars($aAdministrators); $oListExpr = ListExpression::FromScalars($aAdministrators);
@@ -642,14 +685,17 @@ class UserRightsProfile extends UserRightsAddOnAPI
} }
// Handling of the added conditions // Handling of the added conditions
if (count($aConditions) > 0) { if (count($aConditions) > 0)
if ($oFilter === true) { {
if($oFilter === true)
{
// No 'silo' filter, let's build a clean one // No 'silo' filter, let's build a clean one
$oFilter = new DBObjectSearch($sClass); $oFilter = new DBObjectSearch($sClass);
} }
// Add the conditions to the filter // Add the conditions to the filter
foreach ($aConditions as $oCondition) { foreach($aConditions as $oCondition)
{
$oFilter->AddConditionExpression($oCondition); $oFilter->AddConditionExpression($oCondition);
} }
} }
@@ -664,9 +710,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
*/ */
private function GetAdministrators() private function GetAdministrators()
{ {
if ($this->m_aAdministrators === null) { if ($this->m_aAdministrators === null)
{
// Find all administrators // Find all administrators
$this->m_aAdministrators = []; $this->m_aAdministrators = array();
$oAdministratorsFilter = new DBObjectSearch('User'); $oAdministratorsFilter = new DBObjectSearch('User');
$oLnkFilter = new DBObjectSearch('URP_UserProfile'); $oLnkFilter = new DBObjectSearch('URP_UserProfile');
$oExpression = new FieldExpression('profileid', 'URP_UserProfile'); $oExpression = new FieldExpression('profileid', 'URP_UserProfile');
@@ -676,8 +723,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oAdministratorsFilter->AddCondition_ReferencedBy($oLnkFilter, 'userid'); $oAdministratorsFilter->AddCondition_ReferencedBy($oLnkFilter, 'userid');
$oAdministratorsFilter->AllowAllData(true); // Mandatory to prevent infinite recursion !! $oAdministratorsFilter->AllowAllData(true); // Mandatory to prevent infinite recursion !!
$oSet = new DBObjectSet($oAdministratorsFilter); $oSet = new DBObjectSet($oAdministratorsFilter);
$oSet->OptimizeColumnLoad(['User' => ['login']]); $oSet->OptimizeColumnLoad(array('User' => array('login')));
while ($oUser = $oSet->Fetch()) { while($oUser = $oSet->Fetch())
{
$this->m_aAdministrators[] = $oUser->GetKey(); $this->m_aAdministrators[] = $oUser->GetKey();
} }
} }
@@ -693,6 +741,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
return ((bool)MetaModel::GetConfig()->Get('security.hide_administrators')); return ((bool)MetaModel::GetConfig()->Get('security.hide_administrators'));
} }
// This verb has been made public to allow the development of an accurate feedback for the current configuration // This verb has been made public to allow the development of an accurate feedback for the current configuration
public function GetProfileActionGrant($iProfile, $sClass, $sAction) public function GetProfileActionGrant($iProfile, $sClass, $sAction)
{ {
@@ -711,9 +760,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$iUser = $oUser->GetKey(); $iUser = $oUser->GetKey();
if (isset($this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode])){ if (isset($this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode])){
$aTest = $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode]; $aTest = $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode];
if (is_array($aTest)) { if (is_array($aTest)) return $aTest;
return $aTest;
}
} }
$sAction = self::$m_aActionCodes[$iActionCode]; $sAction = self::$m_aActionCodes[$iActionCode];
@@ -724,14 +771,20 @@ class UserRightsProfile extends UserRightsAddOnAPI
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser); $this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
} }
// Call the API of UserRights because it caches the list for us // Call the API of UserRights because it caches the list for us
foreach ($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) { foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile)
{
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); $bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
if (!is_null($bGrant)) { if (!is_null($bGrant))
if ($bGrant) { {
if (is_null($bStatus)) { if ($bGrant)
{
if (is_null($bStatus))
{
$bStatus = true; $bStatus = true;
} }
} else { }
else
{
$bStatus = false; $bStatus = false;
} }
} }
@@ -739,9 +792,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
$iPermission = $bStatus ? UR_ALLOWED_YES : UR_ALLOWED_NO; $iPermission = $bStatus ? UR_ALLOWED_YES : UR_ALLOWED_NO;
$aRes = [ $aRes = array(
'permission' => $iPermission, 'permission' => $iPermission,
]; );
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes; $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
return $aRes; return $aRes;
} }
@@ -756,13 +809,20 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Note: In most cases the object set is ignored because it was interesting to optimize for huge data sets // Note: In most cases the object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set // and acceptable to consider only the root class of the object set
if ($iPermission != UR_ALLOWED_YES) { if ($iPermission != UR_ALLOWED_YES)
{
// It is already NO for everyone... that's the final word! // It is already NO for everyone... that's the final word!
} elseif ($iActionCode == UR_ACTION_READ) { }
elseif ($iActionCode == UR_ACTION_READ)
{
// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
} elseif ($iActionCode == UR_ACTION_BULK_READ) { }
elseif ($iActionCode == UR_ACTION_BULK_READ)
{
// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
} elseif ($oInstanceSet) { }
elseif ($oInstanceSet)
{
// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
// We have to answer NO for objects shared for reading purposes // We have to answer NO for objects shared for reading purposes
if (self::HasSharing() && SharedObject::GetSharedClassProperties($sClass)) { if (self::HasSharing() && SharedObject::GetSharedClassProperties($sClass)) {
@@ -835,14 +895,20 @@ class UserRightsProfile extends UserRightsAddOnAPI
// and acceptable to consider only the root class of the object set // and acceptable to consider only the root class of the object set
$bStatus = null; $bStatus = null;
// Call the API of UserRights because it caches the list for us // Call the API of UserRights because it caches the list for us
foreach ($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) { foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile)
{
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); $bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
if (!is_null($bGrant)) { if (!is_null($bGrant))
if ($bGrant) { {
if (is_null($bStatus)) { if ($bGrant)
{
if (is_null($bStatus))
{
$bStatus = true; $bStatus = true;
} }
} else { }
else
{
$bStatus = false; $bStatus = false;
} }
} }
@@ -866,16 +932,22 @@ class UserRightsProfile extends UserRightsAddOnAPI
{ {
$sAttCode = null; $sAttCode = null;
$aCallSpec = [$sClass, 'MapContextParam']; $aCallSpec = array($sClass, 'MapContextParam');
if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization')) { if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization'))
{
$sAttCode = 'id'; $sAttCode = 'id';
} elseif (is_callable($aCallSpec)) { }
elseif (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter $sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) { if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
// Skip silently. The data model checker will tell you something about this... // Skip silently. The data model checker will tell you something about this...
$sAttCode = null; $sAttCode = null;
} }
} elseif (MetaModel::IsValidAttCode($sClass, 'org_id')) { }
elseif(MetaModel::IsValidAttCode($sClass, 'org_id'))
{
$sAttCode = 'org_id'; $sAttCode = 'org_id';
} }
@@ -888,11 +960,14 @@ class UserRightsProfile extends UserRightsAddOnAPI
protected static function HasSharing() protected static function HasSharing()
{ {
static $bHasSharing; static $bHasSharing;
if (!isset($bHasSharing)) { if (!isset($bHasSharing))
{
$bHasSharing = class_exists('SharedObject'); $bHasSharing = class_exists('SharedObject');
} }
return $bHasSharing; return $bHasSharing;
} }
} }
UserRights::SelectModule('UserRightsProfile'); UserRights::SelectModule('UserRightsProfile');

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -49,7 +48,8 @@ class DBSearchHelper
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); $oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode); $oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
} }
} catch (Exception $e) { }
catch (Exception $e) {
// If filtering fails just ignore it // If filtering fails just ignore it
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
/** /**
* Class ApplicationContext * Class ApplicationContext
* *
@@ -86,6 +86,7 @@ class PortalURLMaker implements iDBObjectURLMaker
} }
} }
/** /**
* Helper class to store and manipulate the parameters that make the application's context * Helper class to store and manipulate the parameters that make the application's context
* *
@@ -114,10 +115,11 @@ class ApplicationContext
*/ */
public function __construct($bReadContext = true) public function __construct($bReadContext = true)
{ {
$this->aNames = [ $this->aNames = array(
'org_id', 'menu', 'org_id', 'menu'
]; );
if ($bReadContext) { if ($bReadContext)
{
$this->ReadContext(); $this->ReadContext();
} }
@@ -131,29 +133,36 @@ class ApplicationContext
*/ */
protected function ReadContext() protected function ReadContext()
{ {
if (!isset(self::$aDefaultValues)) { if (!isset(self::$aDefaultValues))
self::$aDefaultValues = []; {
$aContext = utils::ReadParam('c', [], false, 'context_param'); self::$aDefaultValues = array();
foreach ($this->aNames as $sName) { $aContext = utils::ReadParam('c', array(), false, 'context_param');
foreach($this->aNames as $sName)
{
$sValue = isset($aContext[$sName]) ? $aContext[$sName] : ''; $sValue = isset($aContext[$sName]) ? $aContext[$sName] : '';
// TO DO: check if some of the context parameters are mandatory (or have default values) // TO DO: check if some of the context parameters are mandatory (or have default values)
if (!empty($sValue)) { if (!empty($sValue))
{
self::$aDefaultValues[$sName] = $sValue; self::$aDefaultValues[$sName] = $sValue;
} }
// Hmm, there must be a better (more generic) way to handle the case below: // Hmm, there must be a better (more generic) way to handle the case below:
// When there is only one possible (allowed) organization, the context must be // When there is only one possible (allowed) organization, the context must be
// fixed to this org unless there is only one organization in the system then // fixed to this org unless there is only one organization in the system then
// no filter is applied // no filter is applied
if ($sName == 'org_id') { if ($sName == 'org_id')
if (MetaModel::IsValidClass('Organization')) { {
if (MetaModel::IsValidClass('Organization'))
{
$oSearchFilter = new DBObjectSearch('Organization'); $oSearchFilter = new DBObjectSearch('Organization');
$oSet = new CMDBObjectSet($oSearchFilter); $oSet = new CMDBObjectSet($oSearchFilter);
$iCount = $oSet->CountWithLimit(2); $iCount = $oSet->CountWithLimit(2);
if ($iCount > 1) { if ($iCount > 1)
{
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true); $oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oSet = new CMDBObjectSet($oSearchFilter); $oSet = new CMDBObjectSet($oSearchFilter);
$iCount = $oSet->CountWithLimit(2); $iCount = $oSet->CountWithLimit(2);
if ($iCount == 1) { if ($iCount == 1)
{
// Only one possible value for org_id, set it in the context // Only one possible value for org_id, set it in the context
$oOrg = $oSet->Fetch(); $oOrg = $oSet->Fetch();
self::$aDefaultValues[$sName] = $oOrg->GetKey(); self::$aDefaultValues[$sName] = $oOrg->GetKey();
@@ -176,7 +185,8 @@ class ApplicationContext
*/ */
public function GetCurrentValue($sParamName, $defaultValue = '') public function GetCurrentValue($sParamName, $defaultValue = '')
{ {
if (isset($this->aValues[$sParamName])) { if (isset($this->aValues[$sParamName]))
{
return $this->aValues[$sParamName]; return $this->aValues[$sParamName];
} }
return $defaultValue; return $defaultValue;
@@ -195,8 +205,9 @@ class ApplicationContext
} }
// Build the query string with ampersand separated parameters // Build the query string with ampersand separated parameters
$aParams = []; $aParams = array();
foreach ($this->aValues as $sName => $sValue) { foreach($this->aValues as $sName => $sValue)
{
$aParams[] = "c[$sName]".'='.urlencode($sValue); $aParams[] = "c[$sName]".'='.urlencode($sValue);
} }
$sReturnValue = implode('&', $aParams); $sReturnValue = implode('&', $aParams);
@@ -267,8 +278,9 @@ class ApplicationContext
*/ */
public function GetAsHash() public function GetAsHash()
{ {
$aReturn = []; $aReturn = array();
foreach ($this->aValues as $sName => $sValue) { foreach($this->aValues as $sName => $sValue)
{
$aReturn["c[$sName]"] = $sValue; $aReturn["c[$sName]"] = $sValue;
} }
return $aReturn; return $aReturn;
@@ -289,7 +301,8 @@ class ApplicationContext
*/ */
public function Reset($sParamName) public function Reset($sParamName)
{ {
if (isset($this->aValues[$sParamName])) { if (isset($this->aValues[$sParamName]))
{
unset($this->aValues[$sParamName]); unset($this->aValues[$sParamName]);
} }
} }
@@ -305,16 +318,21 @@ class ApplicationContext
public function InitObjectFromContext(DBObject &$oObj) public function InitObjectFromContext(DBObject &$oObj)
{ {
$sClass = get_class($oObj); $sClass = get_class($oObj);
foreach ($this->GetNames() as $key) { foreach($this->GetNames() as $key)
$aCallSpec = [$sClass, 'MapContextParam']; {
if (is_callable($aCallSpec)) { $aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode)) { if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsWritable()) { if ($oAttDef->IsWritable())
{
$value = $this->GetCurrentValue($key, null); $value = $this->GetCurrentValue($key, null);
if (!is_null($value)) { if (!is_null($value))
{
$oObj->Set($sAttCode, $value); $oObj->Set($sAttCode, $value);
} }
} }
@@ -344,10 +362,14 @@ class ApplicationContext
*/ */
public static function GetUrlMakerClass() public static function GetUrlMakerClass()
{ {
if (is_null(self::$m_sUrlMakerClass)) { if (is_null(self::$m_sUrlMakerClass))
if (Session::IsSet('UrlMakerClass')) { {
if (Session::IsSet('UrlMakerClass'))
{
self::$m_sUrlMakerClass = Session::Get('UrlMakerClass'); self::$m_sUrlMakerClass = Session::Get('UrlMakerClass');
} else { }
else
{
self::$m_sUrlMakerClass = 'iTopStandardURLMaker'; self::$m_sUrlMakerClass = 'iTopStandardURLMaker';
} }
} }
@@ -372,7 +394,7 @@ class ApplicationContext
if (is_null($sUrlMakerClass)) { if (is_null($sUrlMakerClass)) {
$sUrlMakerClass = self::GetUrlMakerClass(); $sUrlMakerClass = self::GetUrlMakerClass();
} }
$sUrl = call_user_func([$sUrlMakerClass, 'MakeObjectUrl'], $sObjClass, $sObjKey); $sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
if (utils::StrLen($sUrl) > 0) { if (utils::StrLen($sUrl) > 0) {
if ($bWithNavigationContext) { if ($bWithNavigationContext) {
return $sUrl.$oAppContext->GetForLink(true); return $sUrl.$oAppContext->GetForLink(true);
@@ -390,10 +412,13 @@ class ApplicationContext
*/ */
protected static function LoadPluginProperties() protected static function LoadPluginProperties()
{ {
if (Session::IsSet('PluginProperties')) { if (Session::IsSet('PluginProperties'))
{
self::$m_aPluginProperties = Session::Get('PluginProperties'); self::$m_aPluginProperties = Session::Get('PluginProperties');
} else { }
self::$m_aPluginProperties = []; else
{
self::$m_aPluginProperties = array();
} }
} }
@@ -406,9 +431,7 @@ class ApplicationContext
*/ */
public static function SetPluginProperty($sPluginClass, $sProperty, $value) public static function SetPluginProperty($sPluginClass, $sProperty, $value)
{ {
if (is_null(self::$m_aPluginProperties)) { if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
self::LoadPluginProperties();
}
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value; self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
Session::Set(['PluginProperties', $sPluginClass, $sProperty], $value); Session::Set(['PluginProperties', $sPluginClass, $sProperty], $value);
@@ -421,14 +444,15 @@ class ApplicationContext
*/ */
public static function GetPluginProperties($sPluginClass) public static function GetPluginProperties($sPluginClass)
{ {
if (is_null(self::$m_aPluginProperties)) { if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
self::LoadPluginProperties();
}
if (array_key_exists($sPluginClass, self::$m_aPluginProperties)) { if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
{
return self::$m_aPluginProperties[$sPluginClass]; return self::$m_aPluginProperties[$sPluginClass];
} else { }
return []; else
{
return array();
} }
} }

View File

@@ -74,3 +74,7 @@ require_once(APPROOT.'application/applicationextension/rest/iRestInputSanitizer.
require_once(APPROOT.'application/applicationextension/rest/iRestServiceProvider.php'); require_once(APPROOT.'application/applicationextension/rest/iRestServiceProvider.php');
require_once(APPROOT.'application/applicationextension/rest/RestResult.php'); require_once(APPROOT.'application/applicationextension/rest/RestResult.php');
require_once(APPROOT.'application/applicationextension/rest/RestUtils.php'); require_once(APPROOT.'application/applicationextension/rest/RestUtils.php');

View File

@@ -42,7 +42,7 @@ abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
*/ */
public function EnumUsedAttributes($oObject) public function EnumUsedAttributes($oObject)
{ {
return []; return array();
} }
/** /**
@@ -66,7 +66,7 @@ abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
*/ */
public function EnumAllowedActions(DBObjectSet $oSet) public function EnumAllowedActions(DBObjectSet $oSet)
{ {
return []; return array();
} }
} }

View File

@@ -33,7 +33,7 @@ abstract class ApplicationPopupMenuItem
$this->sLabel = $sLabel; $this->sLabel = $sLabel;
$this->sTooltip = ''; $this->sTooltip = '';
$this->sIconClass = ''; $this->sIconClass = '';
$this->aCssClasses = []; $this->aCssClasses = array();
} }
/** /**
@@ -89,6 +89,7 @@ abstract class ApplicationPopupMenuItem
$this->aCssClasses[] = $sCssClass; $this->aCssClasses[] = $sCssClass;
} }
/** /**
* @param $sTooltip * @param $sTooltip
* *
@@ -144,6 +145,6 @@ abstract class ApplicationPopupMenuItem
/** @ignore */ /** @ignore */
public function GetLinkedScripts() public function GetLinkedScripts()
{ {
return []; return array();
} }
} }

View File

@@ -9,4 +9,5 @@
*/ */
class JSButtonItem extends JSPopupMenuItem class JSButtonItem extends JSPopupMenuItem
{ {
} }

View File

@@ -28,7 +28,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
* @param array $aIncludeJSFiles An array of file URLs to be included (once) to provide some JS libraries for the page. * @param array $aIncludeJSFiles An array of file URLs to be included (once) to provide some JS libraries for the page.
* @api * @api
*/ */
public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = []) public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array())
{ {
parent::__construct($sUID, $sLabel); parent::__construct($sUID, $sLabel);
$this->sJsCode = $sJSCode; $this->sJsCode = $sJSCode;
@@ -40,14 +40,14 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
public function GetMenuItem() public function GetMenuItem()
{ {
// Note: the semicolumn is a must here! // Note: the semicolumn is a must here!
return [ return array(
'label' => $this->GetLabel(), 'label' => $this->GetLabel(),
'onclick' => $this->GetJsCode() . '; return false;', 'onclick' => $this->GetJsCode() . '; return false;',
'url' => $this->GetUrl(), 'url' => $this->GetUrl(),
'css_classes' => $this->GetCssClasses(), 'css_classes' => $this->GetCssClasses(),
'icon_class' => $this->sIconClass, 'icon_class' => $this->sIconClass,
'tooltip' => $this->sTooltip, 'tooltip' => $this->sTooltip
]; );
} }
/** @ignore */ /** @ignore */

View File

@@ -10,7 +10,7 @@
*/ */
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
{ {
public static $idx = 0; static $idx = 0;
/** /**
* Constructor * Constructor
@@ -24,6 +24,6 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
/** @ignore */ /** @ignore */
public function GetMenuItem() public function GetMenuItem()
{ {
return ['label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses]; return array('label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses);
} }
} }

View File

@@ -9,4 +9,5 @@
*/ */
class URLButtonItem extends URLPopupMenuItem class URLButtonItem extends URLPopupMenuItem
{ {
} }

View File

@@ -35,13 +35,13 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/** @ignore */ /** @ignore */
public function GetMenuItem() public function GetMenuItem()
{ {
return ['label' => $this->GetLabel(), return array('label' => $this->GetLabel(),
'url' => $this->GetUrl(), 'url' => $this->GetUrl(),
'target' => $this->GetTarget(), 'target' => $this->GetTarget(),
'css_classes' => $this->aCssClasses, 'css_classes' => $this->aCssClasses,
'icon_class' => $this->sIconClass, 'icon_class' => $this->sIconClass,
'tooltip' => $this->sTooltip, 'tooltip' => $this->sTooltip
]; );
} }
/** @ignore */ /** @ignore */

View File

@@ -19,21 +19,21 @@ interface iPopupMenuExtension
* $param is a DBObjectSet containing the list of objects * $param is a DBObjectSet containing the list of objects
* @api * @api
*/ */
public const MENU_OBJLIST_ACTIONS = 1; const MENU_OBJLIST_ACTIONS = 1;
/** /**
* Insert an item into the Toolkit menu of a list * Insert an item into the Toolkit menu of a list
* *
* $param is a DBObjectSet containing the list of objects * $param is a DBObjectSet containing the list of objects
* @api * @api
*/ */
public const MENU_OBJLIST_TOOLKIT = 2; const MENU_OBJLIST_TOOLKIT = 2;
/** /**
* Insert an item into the Actions menu on an object details page * Insert an item into the Actions menu on an object details page
* *
* $param is a DBObject instance: the object currently displayed * $param is a DBObject instance: the object currently displayed
* @api * @api
*/ */
public const MENU_OBJDETAILS_ACTIONS = 3; const MENU_OBJDETAILS_ACTIONS = 3;
/** /**
* Insert an item into the Dashboard menu * Insert an item into the Dashboard menu
* *
@@ -43,14 +43,14 @@ interface iPopupMenuExtension
* $param is a Dashboard instance: the dashboard currently displayed * $param is a Dashboard instance: the dashboard currently displayed
* @api * @api
*/ */
public const MENU_DASHBOARD_ACTIONS = 4; const MENU_DASHBOARD_ACTIONS = 4;
/** /**
* Insert an item into the User menu (upper right corner) * Insert an item into the User menu (upper right corner)
* *
* $param is null * $param is null
* @api * @api
*/ */
public const MENU_USER_ACTIONS = 5; const MENU_USER_ACTIONS = 5;
/** /**
* Insert an item into the Action menu on an object item in an objects list in the portal * Insert an item into the Action menu on an object item in an objects list in the portal
* *
@@ -58,7 +58,7 @@ interface iPopupMenuExtension
* the current line) * the current line)
* @api * @api
*/ */
public const PORTAL_OBJLISTITEM_ACTIONS = 7; const PORTAL_OBJLISTITEM_ACTIONS = 7;
/** /**
* Insert an item into the Action menu on an object details page in the portal * Insert an item into the Action menu on an object details page in the portal
* *
@@ -66,7 +66,7 @@ interface iPopupMenuExtension
* currently displayed) * currently displayed)
* @api * @api
*/ */
public const PORTAL_OBJDETAILS_ACTIONS = 8; const PORTAL_OBJDETAILS_ACTIONS = 8;
/** /**
* Insert an item into the Actions menu of a list in the portal * Insert an item into the Actions menu of a list in the portal
@@ -76,7 +76,7 @@ interface iPopupMenuExtension
* *
* @todo * @todo
*/ */
public const PORTAL_OBJLIST_ACTIONS = 6; const PORTAL_OBJLIST_ACTIONS = 6;
/** /**
* Insert an item into the user menu of the portal * Insert an item into the user menu of the portal
* Note: This is not implemented yet ! * Note: This is not implemented yet !
@@ -85,7 +85,7 @@ interface iPopupMenuExtension
* *
* @todo * @todo
*/ */
public const PORTAL_USER_ACTIONS = 9; const PORTAL_USER_ACTIONS = 9;
/** /**
* Insert an item into the navigation menu of the portal * Insert an item into the navigation menu of the portal
* Note: This is not implemented yet ! * Note: This is not implemented yet !
@@ -94,7 +94,7 @@ interface iPopupMenuExtension
* *
* @todo * @todo
*/ */
public const PORTAL_MENU_ACTIONS = 10; const PORTAL_MENU_ACTIONS = 10;
/** /**
* Get the list of items to be added to a menu. * Get the list of items to be added to a menu.

View File

@@ -14,7 +14,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
*/ */
public function GetCSSFiles(\Symfony\Component\DependencyInjection\Container $oContainer) public function GetCSSFiles(\Symfony\Component\DependencyInjection\Container $oContainer)
{ {
return []; return array();
} }
/** /**
@@ -30,7 +30,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
*/ */
public function GetJSFiles(\Symfony\Component\DependencyInjection\Container $oContainer) public function GetJSFiles(\Symfony\Component\DependencyInjection\Container $oContainer)
{ {
return []; return array();
} }
/** /**

View File

@@ -11,9 +11,9 @@
*/ */
interface iPortalUIExtension interface iPortalUIExtension
{ {
public const ENUM_PORTAL_EXT_UI_BODY = 'Body'; const ENUM_PORTAL_EXT_UI_BODY = 'Body';
public const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu'; const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu';
public const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent'; const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent';
/** /**
* Returns an array of CSS file urls * Returns an array of CSS file urls

View File

@@ -13,62 +13,62 @@ class RestResult
* Result: no issue has been encountered * Result: no issue has been encountered
* @api * @api
*/ */
public const OK = 0; const OK = 0;
/** /**
* Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation * Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation
* @api * @api
*/ */
public const UNAUTHORIZED = 1; const UNAUTHORIZED = 1;
/** /**
* Result: the parameter 'version' is missing * Result: the parameter 'version' is missing
* @api * @api
*/ */
public const MISSING_VERSION = 2; const MISSING_VERSION = 2;
/** /**
* Result: the parameter 'json_data' is missing * Result: the parameter 'json_data' is missing
* @api * @api
*/ */
public const MISSING_JSON = 3; const MISSING_JSON = 3;
/** /**
* Result: the input structure is not a valid JSON string * Result: the input structure is not a valid JSON string
* @api * @api
*/ */
public const INVALID_JSON = 4; const INVALID_JSON = 4;
/** /**
* Result: the parameter 'auth_user' is missing, authentication aborted * Result: the parameter 'auth_user' is missing, authentication aborted
* @api * @api
*/ */
public const MISSING_AUTH_USER = 5; const MISSING_AUTH_USER = 5;
/** /**
* Result: the parameter 'auth_pwd' is missing, authentication aborted * Result: the parameter 'auth_pwd' is missing, authentication aborted
* @api * @api
*/ */
public const MISSING_AUTH_PWD = 6; const MISSING_AUTH_PWD = 6;
/** /**
* Result: no operation is available for the specified version * Result: no operation is available for the specified version
* @api * @api
*/ */
public const UNSUPPORTED_VERSION = 10; const UNSUPPORTED_VERSION = 10;
/** /**
* Result: the requested operation is not valid for the specified version * Result: the requested operation is not valid for the specified version
* @api * @api
*/ */
public const UNKNOWN_OPERATION = 11; const UNKNOWN_OPERATION = 11;
/** /**
* Result: the requested operation cannot be performed because it can cause data (integrity) loss * Result: the requested operation cannot be performed because it can cause data (integrity) loss
* @api * @api
*/ */
public const UNSAFE = 12; const UNSAFE = 12;
/** /**
* Result: the request page number is not valid. It must be an integer greater than 0 * Result: the request page number is not valid. It must be an integer greater than 0
* @api * @api
*/ */
public const INVALID_PAGE = 13; const INVALID_PAGE = 13;
/** /**
* Result: the operation could not be performed, see the message for troubleshooting * Result: the operation could not be performed, see the message for troubleshooting
* @api * @api
*/ */
public const INTERNAL_ERROR = 100; const INTERNAL_ERROR = 100;
/** /**
* Default constructor - ok! * Default constructor - ok!

View File

@@ -44,6 +44,7 @@ class RestUtils
} }
} }
/** /**
* Read an optional parameter from a Rest/Json structure. * Read an optional parameter from a Rest/Json structure.
* *
@@ -65,6 +66,7 @@ class RestUtils
} }
} }
/** /**
* Read a class from a Rest/Json structure. * Read a class from a Rest/Json structure.
* *
@@ -85,6 +87,7 @@ class RestUtils
return $sClass; return $sClass;
} }
/** /**
* Read a list of attribute codes from a Rest/Json structure. * Read a list of attribute codes from a Rest/Json structure.
* *
@@ -100,7 +103,7 @@ class RestUtils
public static function GetFieldList($sClass, $oData, $sParamName) public static function GetFieldList($sClass, $oData, $sParamName)
{ {
$sFields = self::GetOptionalParam($oData, $sParamName, '*'); $sFields = self::GetOptionalParam($oData, $sParamName, '*');
$aShowFields = []; $aShowFields = array();
if ($sFields == '*') { if ($sFields == '*') {
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
$aShowFields[$sClass][] = $sAttCode; $aShowFields[$sClass][] = $sAttCode;
@@ -137,7 +140,7 @@ class RestUtils
*/ */
protected static function FindObjectFromCriteria($sClass, $oCriteria) protected static function FindObjectFromCriteria($sClass, $oCriteria)
{ {
$aCriteriaReport = []; $aCriteriaReport = array();
if (isset($oCriteria->finalclass)) { if (isset($oCriteria->finalclass)) {
if (!MetaModel::IsValidClass($oCriteria->finalclass)) { if (!MetaModel::IsValidClass($oCriteria->finalclass)) {
throw new Exception("finalclass: Unknown class '" . $oCriteria->finalclass . "'"); throw new Exception("finalclass: Unknown class '" . $oCriteria->finalclass . "'");
@@ -168,6 +171,7 @@ class RestUtils
return $res; return $res;
} }
/** /**
* Find an object from a polymorph search specification (Rest/Json) * Find an object from a polymorph search specification (Rest/Json)
* *
@@ -256,7 +260,7 @@ class RestUtils
} else { } else {
throw new Exception("Wrong format for key"); throw new Exception("Wrong format for key");
} }
$oObjectSet = new DBObjectSet($oSearch, [], [], null, $iLimit, $iOffset); $oObjectSet = new DBObjectSet($oSearch, array(), array(), null, $iLimit, $iOffset);
return $oObjectSet; return $oObjectSet;
} }
@@ -287,7 +291,7 @@ class RestUtils
throw new Exception("A link set must be defined by an array of objects"); throw new Exception("A link set must be defined by an array of objects");
} }
$sLnkClass = $oAttDef->GetLinkedClass(); $sLnkClass = $oAttDef->GetLinkedClass();
$aLinks = []; $aLinks = array();
foreach ($value as $oValues) { foreach ($value as $oValues) {
$oLnk = static::MakeObjectFromFields($sLnkClass, $oValues); $oLnk = static::MakeObjectFromFields($sLnkClass, $oValues);
// Fix for N°1939 // Fix for N°1939

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
/** /**
* This class manages the audit "categories". Each category defines a set of objects * This class manages the audit "categories". Each category defines a set of objects
* to check and is linked to a set of rules that determine the valid or invalid objects * to check and is linked to a set of rules that determine the valid or invalid objects
@@ -32,36 +32,34 @@ class AuditCategory extends cmdbAbstractObject
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "application,grant_by_profile", "category" => "application,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => ['name'], "reconc_keys" => array('name'),
"db_table" => "priv_auditcategory", "db_table" => "priv_auditcategory",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'), 'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", ["description" => "Short name for this category", "allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", ["allowed_values" => null, "sql" => "definition_set", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", array("allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", ["linked_class" => "AuditRule", "ext_key_to_me" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => [], "edit_mode" => LINKSET_EDITMODE_INPLACE, "edit_when" => LINKSET_EDITWHEN_ALWAYS, "tracking_level" => LINKSET_TRACKING_ALL])); MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL)));
MetaModel::Init_AddAttribute(new AttributeInteger("ok_error_tolerance", ["allowed_values" => null, "sql" => "ok_error_tolerance", "default_value" => 5, "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeInteger("ok_error_tolerance", array("allowed_values"=>null, "sql"=>"ok_error_tolerance", "default_value"=>5, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("warning_error_tolerance", ["allowed_values" => null, "sql" => "warning_error_tolerance", "default_value" => 25, "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeInteger("warning_error_tolerance", array("allowed_values" => null, "sql" => "warning_error_tolerance", "default_value" => 25, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect( MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("domains_list",
"domains_list", array("linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "category_id", "ext_key_to_remote" => "domain_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
["linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "category_id", "ext_key_to_remote" => "domain_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => [], "display_style" => 'property']
));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['name', 'description', 'definition_set', 'ok_error_tolerance', 'warning_error_tolerance', 'rules_list', 'domains_list']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'ok_error_tolerance', 'warning_error_tolerance', 'rules_list', 'domains_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['description', ]); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['description', 'definition_set']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('description', 'definition_set')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', ['name', 'description']); // Criteria of the default search form MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
} }
/** /**
@@ -95,3 +93,4 @@ class AuditCategory extends cmdbAbstractObject
return $aShortcutActions; return $aShortcutActions;
} }
} }
?>

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
/** /**
* This class manages the audit "categories". Each category defines a set of objects * This class manages the audit "categories". Each category defines a set of objects
* to check and is linked to a set of rules that determine the valid or invalid objects * to check and is linked to a set of rules that determine the valid or invalid objects
@@ -33,34 +33,32 @@ class AuditDomain extends cmdbAbstractObject
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "application,grant_by_profile", "category" => "application,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"complementary_name_attcode" => ['description'], "complementary_name_attcode" => array('description'),
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => ['name'], "reconc_keys" => array('name'),
"db_table" => "priv_auditdomain", "db_table" => "priv_auditdomain",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-album.svg'), 'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-album.svg'),
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", ["description" => "Short name for this category", "allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("name", array("description" => "Short name for this category", "allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeImage("icon", ["is_null_allowed" => true, "depends_on" => [], "display_max_width" => 96, "display_max_height" => 96, "storage_max_width" => 256, "storage_max_height" => 256, "default_image" => null, "always_load_in_tables" => false])); MetaModel::Init_AddAttribute(new AttributeImage("icon", array("is_null_allowed" => true, "depends_on" => array(), "display_max_width" => 96, "display_max_height" => 96, "storage_max_width" => 256, "storage_max_height" => 256, "default_image" => null, "always_load_in_tables" => false)));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect( MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("categories_list",
"categories_list", array("linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "domain_id", "ext_key_to_remote" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array())));
["linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "domain_id", "ext_key_to_remote" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => []]
));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['name', 'description', 'icon', 'categories_list']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'description', 'icon', 'categories_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['description',]); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('description',)); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['description']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', ['name', 'description']); // Criteria of the default search form MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
} }
public static function GetShortcutActions($sFinalClass) public static function GetShortcutActions($sFinalClass)
@@ -86,39 +84,40 @@ class lnkAuditCategoryToAuditDomain extends cmdbAbstractObject
*/ */
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "application,grant_by_profile", "category" => "application,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "", "name_attcode" => "",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => ['category_id', 'domain_id'], "reconc_keys" => array('category_id', 'domain_id'),
"db_table" => "priv_link_audit_category_domain", "db_table" => "priv_link_audit_category_domain",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
"is_link" => true, "is_link" => true,
'uniqueness_rules' => [ 'uniqueness_rules' => array(
'no_duplicate' => [ 'no_duplicate' => array(
'attributes' => [ 'attributes' => array(
0 => 'category_id', 0 => 'category_id',
1 => 'domain_id', 1 => 'domain_id',
], ),
'filter' => '', 'filter' => '',
'disabled' => false, 'disabled' => false,
'is_blocking' => true, 'is_blocking' => true,
], ),
], ),
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", ["targetclass" => "AuditCategory", "jointype" => '', "allowed_values" => null, "sql" => "category_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("targetclass" => "AuditCategory", "jointype" => '', "allowed_values" => null, "sql" => "category_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", ["allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name"])); MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name")));
MetaModel::Init_AddAttribute(new AttributeExternalKey("domain_id", ["targetclass" => "AuditDomain", "jointype" => '', "allowed_values" => null, "sql" => "domain_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("domain_id", array("targetclass" => "AuditDomain", "jointype" => '', "allowed_values" => null, "sql" => "domain_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("domain_name", ["allowed_values" => null, "extkey_attcode" => 'domain_id', "target_attcode" => "name"])); MetaModel::Init_AddAttribute(new AttributeExternalField("domain_name", array("allowed_values" => null, "extkey_attcode" => 'domain_id', "target_attcode" => "name")));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['category_id', 'domain_id']); MetaModel::Init_SetZListItems('details', array('category_id', 'domain_id'));
MetaModel::Init_SetZListItems('list', ['category_id', 'domain_id']); MetaModel::Init_SetZListItems('list', array('category_id', 'domain_id'));
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['category_id', 'domain_id']); MetaModel::Init_SetZListItems('standard_search', array('category_id', 'domain_id'));
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
/** /**
* This class manages the audit "rule" linked to a given audit category. * This class manages the audit "rule" linked to a given audit category.
* Each rule is based on an OQL expression that returns either the "good" objects * Each rule is based on an OQL expression that returns either the "good" objects
@@ -33,35 +33,35 @@ class AuditRule extends cmdbAbstractObject
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "application,grant_by_profile", "category" => "application,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => ['name'], "reconc_keys" => array('name'),
"db_table" => "priv_auditrule", "db_table" => "priv_auditrule",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit.svg'), 'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit.svg'),
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", ["allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeOQL("query", ["allowed_values" => null, "sql" => "query", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeOQL("query", array("allowed_values" => null, "sql" => "query", "default_value" => "", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", ["allowed_values" => new ValueSetEnum('true,false'), "sql" => "valid_flag", "default_value" => "true", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", array("allowed_values" => new ValueSetEnum('true,false'), "sql" => "valid_flag", "default_value" => "true", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", ["allowed_values" => null, "sql" => "category_id", "targetclass" => "AuditCategory", "is_null_allowed" => false, "on_target_delete" => DEL_MANUAL, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("allowed_values" => null, "sql" => "category_id", "targetclass" => "AuditCategory", "is_null_allowed" => false, "on_target_delete" => DEL_MANUAL, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", ["allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name"])); MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name")));
MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", ["allowed_values" => null, "sql" => "contact_id", "targetclass" => "Contact", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeHTML("process", ["allowed_values" => null, "sql" => "process", "default_value" => "", "is_null_allowed" => true, "depends_on" => []]));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['category_id', 'name', 'description', 'query', 'valid_flag', 'process', 'contact_id']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('category_id', 'name', 'description', 'query', 'valid_flag')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['category_id', 'description', 'query']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('category_id', 'description', 'valid_flag')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['category_id', 'name', 'description', 'valid_flag', 'query']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('category_id', 'name', 'description', 'valid_flag', 'query')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', ['name', 'description', 'category_id', 'contact_id', 'query']); // Criteria of the advanced search form MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'category_id')); // Criteria of the advanced search form
} }
public static function GetShortcutActions($sFinalClass) public static function GetShortcutActions($sFinalClass)
{ {
$aShortcutActions = parent::GetShortcutActions($sFinalClass); $aShortcutActions = parent::GetShortcutActions($sFinalClass);
@@ -72,3 +72,4 @@ class AuditRule extends cmdbAbstractObject
return $aShortcutActions; return $aShortcutActions;
} }
} }
?>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* Copyright (C) 2013-2024 Combodo SAS * Copyright (C) 2013-2024 Combodo SAS
* *
@@ -33,8 +32,7 @@ class CompileCSSService
{ {
} }
public function CompileCSSFromSASS($sSassContent, $aImportPaths = [], $aVariables = []) public function CompileCSSFromSASS($sSassContent, $aImportPaths = [], $aVariables = []){
{
return utils::CompileCSSFromSASS($sSassContent, $aImportPaths, $aVariables); return utils::CompileCSSFromSASS($sSassContent, $aImportPaths, $aVariables);
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -54,7 +53,7 @@ abstract class Dashboard
$this->sLayoutClass = 'DashboardLayoutOneCol'; $this->sLayoutClass = 'DashboardLayoutOneCol';
$this->bAutoReload = false; $this->bAutoReload = false;
$this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval();
$this->aCells = []; $this->aCells = array();
$this->oDOMNode = null; $this->oDOMNode = null;
$this->sId = $sId; $this->sId = $sId;
} }
@@ -66,8 +65,8 @@ abstract class Dashboard
*/ */
public function FromXml($sXml) public function FromXml($sXml)
{ {
$this->aCells = []; // reset the content of the dashboard $this->aCells = array(); // reset the content of the dashboard
set_error_handler(['Dashboard', 'ErrorHandler']); set_error_handler(array('Dashboard', 'ErrorHandler'));
$oDoc = new DOMDocument(); $oDoc = new DOMDocument();
$oDoc->loadXML($sXml); $oDoc->loadXML($sXml);
restore_error_handler(); restore_error_handler();
@@ -81,68 +80,86 @@ abstract class Dashboard
{ {
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
if ($oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0)) { if ($oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0))
{
$this->sLayoutClass = $oLayoutNode->textContent; $this->sLayoutClass = $oLayoutNode->textContent;
} else { }
else
{
$this->sLayoutClass = 'DashboardLayoutOneCol'; $this->sLayoutClass = 'DashboardLayoutOneCol';
} }
if ($oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0)) { if ($oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0))
{
$this->sTitle = $oTitleNode->textContent; $this->sTitle = $oTitleNode->textContent;
} else { }
else
{
$this->sTitle = ''; $this->sTitle = '';
} }
$this->bAutoReload = false; $this->bAutoReload = false;
$this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval();
if ($oAutoReloadNode = $this->oDOMNode->getElementsByTagName('auto_reload')->item(0)) { if ($oAutoReloadNode = $this->oDOMNode->getElementsByTagName('auto_reload')->item(0))
if ($oAutoReloadEnabled = $oAutoReloadNode->getElementsByTagName('enabled')->item(0)) { {
if ($oAutoReloadEnabled = $oAutoReloadNode->getElementsByTagName('enabled')->item(0))
{
$this->bAutoReload = ($oAutoReloadEnabled->textContent == 'true'); $this->bAutoReload = ($oAutoReloadEnabled->textContent == 'true');
} }
if ($oAutoReloadInterval = $oAutoReloadNode->getElementsByTagName('interval')->item(0)) { if ($oAutoReloadInterval = $oAutoReloadNode->getElementsByTagName('interval')->item(0))
{
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$oAutoReloadInterval->textContent); $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$oAutoReloadInterval->textContent);
} }
} }
if ($oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0)) { if ($oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0))
{
$oCellsList = $oCellsNode->getElementsByTagName('cell'); $oCellsList = $oCellsNode->getElementsByTagName('cell');
$aCellOrder = []; $aCellOrder = array();
$iCellRank = 0; $iCellRank = 0;
/** @var \DOMElement $oCellNode */ /** @var \DOMElement $oCellNode */
foreach ($oCellsList as $oCellNode) { foreach($oCellsList as $oCellNode)
{
$oCellRank = $oCellNode->getElementsByTagName('rank')->item(0); $oCellRank = $oCellNode->getElementsByTagName('rank')->item(0);
if ($oCellRank) { if ($oCellRank)
{
$iCellRank = (float)$oCellRank->textContent; $iCellRank = (float)$oCellRank->textContent;
} }
$oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0); $oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0);
{ {
$oDashletList = $oDashletsNode->getElementsByTagName('dashlet'); $oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
$iRank = 0; $iRank = 0;
$aDashletOrder = []; $aDashletOrder = array();
/** @var \DOMElement $oDomNode */ /** @var \DOMElement $oDomNode */
foreach ($oDashletList as $oDomNode) { foreach($oDashletList as $oDomNode)
{
$oRank = $oDomNode->getElementsByTagName('rank')->item(0); $oRank = $oDomNode->getElementsByTagName('rank')->item(0);
if ($oRank) { if ($oRank)
{
$iRank = (float)$oRank->textContent; $iRank = (float)$oRank->textContent;
} }
$oNewDashlet = $this->InitDashletFromDOMNode($oDomNode); $oNewDashlet = $this->InitDashletFromDOMNode($oDomNode);
$aDashletOrder[] = ['rank' => $iRank, 'dashlet' => $oNewDashlet]; $aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
} }
usort($aDashletOrder, [get_class($this), 'SortOnRank']); usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
$aDashletList = []; $aDashletList = array();
foreach ($aDashletOrder as $aItem) { foreach($aDashletOrder as $aItem)
{
$aDashletList[] = $aItem['dashlet']; $aDashletList[] = $aItem['dashlet'];
} }
$aCellOrder[] = ['rank' => $iCellRank, 'dashlets' => $aDashletList]; $aCellOrder[] = array('rank' => $iCellRank, 'dashlets' => $aDashletList);
} }
} }
usort($aCellOrder, [get_class($this), 'SortOnRank']); usort($aCellOrder, array(get_class($this), 'SortOnRank'));
foreach ($aCellOrder as $aItem) { foreach($aCellOrder as $aItem)
{
$this->aCells[] = $aItem['dashlets']; $this->aCells[] = $aItem['dashlets'];
} }
} else { }
$this->aCells = []; else
{
$this->aCells = array();
} }
} }
@@ -191,9 +208,12 @@ abstract class Dashboard
*/ */
public static function ErrorHandler($errno, $errstr, $errfile, $errline) public static function ErrorHandler($errno, $errstr, $errfile, $errline)
{ {
if ($errno == E_WARNING && (substr_count($errstr, "DOMDocument::loadXML()") > 0)) { if ($errno == E_WARNING && (substr_count($errstr,"DOMDocument::loadXML()")>0))
{
throw new DOMException($errstr); throw new DOMException($errstr);
} else { }
else
{
return false; return false;
} }
} }
@@ -243,7 +263,8 @@ abstract class Dashboard
$oDefinition->appendChild($oCellsNode); $oDefinition->appendChild($oCellsNode);
$iCellRank = 0; $iCellRank = 0;
foreach ($this->aCells as $aCell) { foreach ($this->aCells as $aCell)
{
$oCellNode = $oDoc->createElement('cell'); $oCellNode = $oDoc->createElement('cell');
$oCellNode->setAttribute('id', $iCellRank); $oCellNode->setAttribute('id', $iCellRank);
$oCellsNode->appendChild($oCellNode); $oCellsNode->appendChild($oCellNode);
@@ -255,7 +276,8 @@ abstract class Dashboard
$oDashletsNode = $oDoc->createElement('dashlets'); $oDashletsNode = $oDoc->createElement('dashlets');
$oCellNode->appendChild($oDashletsNode); $oCellNode->appendChild($oDashletsNode);
/** @var \Dashlet $oDashlet */ /** @var \Dashlet $oDashlet */
foreach ($aCell as $oDashlet) { foreach ($aCell as $oDashlet)
{
$oNode = $oDoc->createElement('dashlet'); $oNode = $oDoc->createElement('dashlet');
$oDashletsNode->appendChild($oNode); $oDashletsNode->appendChild($oNode);
$oNode->setAttribute('id', $oDashlet->GetID()); $oNode->setAttribute('id', $oDashlet->GetID());
@@ -282,7 +304,7 @@ abstract class Dashboard
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int) $aParams['auto_reload_sec']); $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int) $aParams['auto_reload_sec']);
foreach($aParams['cells'] as $aCell) { foreach($aParams['cells'] as $aCell) {
$aCellDashlets = []; $aCellDashlets = array();
foreach($aCell as $aDashletParams) { foreach($aCell as $aDashletParams) {
$sDashletClass = $aDashletParams['dashlet_class']; $sDashletClass = $aDashletParams['dashlet_class'];
$sId = $aDashletParams['dashlet_id']; $sId = $aDashletParams['dashlet_id'];
@@ -398,7 +420,7 @@ abstract class Dashboard
{ {
$sId = $this->GetNewDashletId(); $sId = $this->GetNewDashletId();
$oDashlet->SetId($sId); $oDashlet->SetId($sId);
$this->aCells[] = [$oDashlet]; $this->aCells[] = array($oDashlet);
} }
/** /**
@@ -408,7 +430,7 @@ abstract class Dashboard
* @throws \ReflectionException * @throws \ReflectionException
* @throws \Exception * @throws \Exception
*/ */
public function RenderProperties($oPage, $aExtraParams = []) public function RenderProperties($oPage, $aExtraParams = array())
{ {
// menu to pick a layout and edit other properties of the dashboard // menu to pick a layout and edit other properties of the dashboard
$oPage->add('<div class="ui-widget-content ui-corner-all ibo-dashboard-editor--properties"><div class="ui-widget-header ui-corner-all ibo-dashboard-editor--properties-title">'.Dict::S('UI:DashboardEdit:Properties').'</div>'); $oPage->add('<div class="ui-widget-content ui-corner-all ibo-dashboard-editor--properties"><div class="ui-widget-header ui-corner-all ibo-dashboard-editor--properties-title">'.Dict::S('UI:DashboardEdit:Properties').'</div>');
@@ -420,7 +442,7 @@ abstract class Dashboard
if (is_subclass_of($sLayoutClass, 'DashboardLayout')) { if (is_subclass_of($sLayoutClass, 'DashboardLayout')) {
$oReflection = new ReflectionClass($sLayoutClass); $oReflection = new ReflectionClass($sLayoutClass);
if (!$oReflection->isAbstract()) { if (!$oReflection->isAbstract()) {
$aCallSpec = [$sLayoutClass, 'GetInfo']; $aCallSpec = array($sLayoutClass, 'GetInfo');
$aInfo = call_user_func($aCallSpec); $aInfo = call_user_func($aCallSpec);
$sChecked = ($this->sLayoutClass == $sLayoutClass) ? 'checked' : ''; $sChecked = ($this->sLayoutClass == $sLayoutClass) ? 'checked' : '';
$oPage->add('<input type="radio" name="layout_class" '.$sChecked.' value="'.$sLayoutClass.'" id="layout_'.$sLayoutClass.'"><label for="layout_'.$sLayoutClass.'"><img src="'.$sUrl.$aInfo['icon'].'" class="ibo-dashboard--properties--icon" data-role="ibo-dashboard--properties--icon"/></label>'); // title="" on either the img or the label does nothing ! $oPage->add('<input type="radio" name="layout_class" '.$sChecked.' value="'.$sLayoutClass.'" id="layout_'.$sLayoutClass.'"><label for="layout_'.$sLayoutClass.'"><img src="'.$sUrl.$aInfo['icon'].'" class="ibo-dashboard--properties--icon" data-role="ibo-dashboard--properties--icon"/></label>'); // title="" on either the img or the label does nothing !
@@ -444,6 +466,7 @@ abstract class Dashboard
$oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit $oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit
$oForm->AddField($oField); $oForm->AddField($oField);
$this->SetFormParams($oForm, $aExtraParams); $this->SetFormParams($oForm, $aExtraParams);
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard'); $oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
@@ -499,7 +522,7 @@ EOF
* *
* @return \Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardLayout * @return \Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardLayout
*/ */
public function Render($oPage, $bEditMode = false, $aExtraParams = [], $bCanEdit = true) public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{ {
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); $aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
@@ -528,8 +551,7 @@ EOF
$oToolbar->AddHtml($sHtml); $oToolbar->AddHtml($sHtml);
} else { } else {
$oPage->add_script( $oPage->add_script(<<<JS
<<<JS
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML").attr("title", $('<div>').html("$sTitleForHTML").text()); $(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML").attr("title", $('<div>').html("$sTitleForHTML").text());
JS JS
); );
@@ -573,7 +595,7 @@ JS
* @param WebPage $oPage * @param WebPage $oPage
* @param array $aExtraParams * @param array $aExtraParams
*/ */
public function RenderDashletsProperties(WebPage $oPage, $aExtraParams = []) public function RenderDashletsProperties(WebPage $oPage, $aExtraParams = array())
{ {
// Toolbox/palette to edit the properties of each dashlet // Toolbox/palette to edit the properties of each dashlet
$oPage->add('<div class="ui-widget-content ui-corner-all ibo-dashlet--properties"><div class="ui-widget-header ui-corner-all ibo-dashlet--properties--title">'.Dict::S('UI:DashboardEdit:DashletProperties').'</div>'); $oPage->add('<div class="ui-widget-content ui-corner-all ibo-dashlet--properties"><div class="ui-widget-header ui-corner-all ibo-dashlet--properties--title">'.Dict::S('UI:DashboardEdit:DashletProperties').'</div>');
@@ -582,10 +604,13 @@ JS
$oLayout = new $this->sLayoutClass(); $oLayout = new $this->sLayoutClass();
$oPage->add('<div id="dashlet_properties">'); $oPage->add('<div id="dashlet_properties">');
foreach ($this->aCells as $iCellIdx => $aCell) { foreach($this->aCells as $iCellIdx => $aCell)
{
/** @var \Dashlet $oDashlet */ /** @var \Dashlet $oDashlet */
foreach ($aCell as $oDashlet) { foreach($aCell as $oDashlet)
if ($oDashlet->IsVisible()) { {
if ($oDashlet->IsVisible())
{
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" style="display:none">'); $oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" style="display:none">');
$oForm = $oDashlet->GetForm(); $oForm = $oDashlet->GetForm();
$this->SetFormParams($oForm, $aExtraParams); $this->SetFormParams($oForm, $aExtraParams);
@@ -607,17 +632,18 @@ JS
*/ */
protected function GetAvailableDashlets() protected function GetAvailableDashlets()
{ {
$aDashlets = []; $aDashlets = array();
foreach (get_declared_classes() as $sDashletClass) { foreach( get_declared_classes() as $sDashletClass)
{
// DashletUnknown is not among the selection as it is just a fallback for dashlets that can't instantiated. // DashletUnknown is not among the selection as it is just a fallback for dashlets that can't instantiated.
if (is_subclass_of($sDashletClass, 'Dashlet') && !in_array($sDashletClass, ['DashletUnknown', 'DashletProxy'])) { if (is_subclass_of($sDashletClass, 'Dashlet') && !in_array($sDashletClass, array('DashletUnknown', 'DashletProxy'))) {
$oReflection = new ReflectionClass($sDashletClass); $oReflection = new ReflectionClass($sDashletClass);
if (!$oReflection->isAbstract()) { if (!$oReflection->isAbstract()) {
$aCallSpec = [$sDashletClass, 'IsVisible']; $aCallSpec = array($sDashletClass, 'IsVisible');
$bVisible = call_user_func($aCallSpec); $bVisible = call_user_func($aCallSpec);
if ($bVisible) { if ($bVisible) {
$aCallSpec = [$sDashletClass, 'GetInfo']; $aCallSpec = array($sDashletClass, 'GetInfo');
$aInfo = call_user_func($aCallSpec); $aInfo = call_user_func($aCallSpec);
$aDashlets[$sDashletClass] = $aInfo; $aDashlets[$sDashletClass] = $aInfo;
} }
@@ -634,9 +660,11 @@ JS
protected function GetNewDashletId() protected function GetNewDashletId()
{ {
$iNewId = 0; $iNewId = 0;
foreach ($this->aCells as $aDashlets) { foreach($this->aCells as $aDashlets)
{
/** @var \Dashlet $oDashlet */ /** @var \Dashlet $oDashlet */
foreach ($aDashlets as $oDashlet) { foreach($aDashlets as $oDashlet)
{
$iNewId = max($iNewId, (int)$oDashlet->GetID()); $iNewId = max($iNewId, (int)$oDashlet->GetID());
} }
} }
@@ -653,7 +681,7 @@ JS
* *
* @return void * @return void
*/ */
abstract protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = []); abstract protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = array());
/** /**
* @param \DesignerForm $oForm * @param \DesignerForm $oForm
@@ -661,7 +689,7 @@ JS
* *
* @return mixed * @return mixed
*/ */
abstract protected function SetFormParams($oForm, $aExtraParams = []); abstract protected function SetFormParams($oForm, $aExtraParams = array());
/** /**
* @param string $sType * @param string $sType
@@ -671,7 +699,8 @@ JS
*/ */
public static function GetDashletClassFromType($sType, $oFactory = null) public static function GetDashletClassFromType($sType, $oFactory = null)
{ {
if (is_subclass_of($sType, 'Dashlet')) { if (is_subclass_of($sType, 'Dashlet'))
{
return $sType; return $sType;
} }
return 'DashletUnknown'; return 'DashletUnknown';
@@ -694,12 +723,14 @@ JS
*/ */
public static function GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletOrigId) public static function GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletOrigId)
{ {
if (strpos($sDashletOrigId, '_ID_row') !== false) { if(strpos($sDashletOrigId, '_ID_row') !== false)
{
return $sDashletOrigId; return $sDashletOrigId;
} }
$sDashletId = $sDashboardDivId."_ID_row".$iRow."_col".$iCol."_".$sDashletOrigId; $sDashletId = $sDashboardDivId."_ID_row".$iRow."_col".$iCol."_".$sDashletOrigId;
if ($bIsCustomized) { if ($bIsCustomized)
{
$sDashletId = 'CUSTOM_'.$sDashletId; $sDashletId = 'CUSTOM_'.$sDashletId;
} }
@@ -751,9 +782,9 @@ class RuntimeDashboard extends Dashboard
* @inheritDoc * @inheritDoc
* @throws \Exception * @throws \Exception
*/ */
protected function SetFormParams($oForm, $aExtraParams = []) protected function SetFormParams($oForm, $aExtraParams = array())
{ {
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', ['operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams]); $oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
} }
/** /**
@@ -769,11 +800,14 @@ class RuntimeDashboard extends Dashboard
$oUDSearch->AddCondition('menu_code', $this->sId, '='); $oUDSearch->AddCondition('menu_code', $this->sId, '=');
$oUDSet = new DBObjectSet($oUDSearch); $oUDSet = new DBObjectSet($oUDSearch);
$bIsNew = false; $bIsNew = false;
if ($oUDSet->Count() > 0) { if ($oUDSet->Count() > 0)
{
// Assuming there is at most one couple {user, menu}! // Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch(); $oUserDashboard = $oUDSet->Fetch();
$oUserDashboard->Set('contents', $sXml); $oUserDashboard->Set('contents', $sXml);
} else { }
else
{
// No such customized dashboard for the current user, let's create a new record // No such customized dashboard for the current user, let's create a new record
$oUserDashboard = new UserDashboard(); $oUserDashboard = new UserDashboard();
$oUserDashboard->Set('user_id', UserRights::GetUserId()); $oUserDashboard->Set('user_id', UserRights::GetUserId());
@@ -804,7 +838,8 @@ class RuntimeDashboard extends Dashboard
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oUDSearch->AddCondition('menu_code', $this->sId, '='); $oUDSearch->AddCondition('menu_code', $this->sId, '=');
$oUDSet = new DBObjectSet($oUDSearch); $oUDSet = new DBObjectSet($oUDSearch);
if ($oUDSet->Count() > 0) { if ($oUDSet->Count() > 0)
{
// Assuming there is at most one couple {user, menu}! // Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch(); $oUserDashboard = $oUDSet->Fetch();
utils::PushArchiveMode(false); utils::PushArchiveMode(false);
@@ -848,11 +883,14 @@ class RuntimeDashboard extends Dashboard
} else { } else {
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized); $sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
} }
} else { }
else
{
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized); $sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
} }
if ($sDashboardDefinition !== false) { if ($sDashboardDefinition !== false)
{
$oDashboard = new RuntimeDashboard($sDashBoardId); $oDashboard = new RuntimeDashboard($sDashBoardId);
$oDashboard->FromXml($sDashboardDefinition); $oDashboard->FromXml($sDashboardDefinition);
$oDashboard->SetCustomFlag($bCustomized); $oDashboard->SetCustomFlag($bCustomized);
@@ -899,6 +937,7 @@ class RuntimeDashboard extends Dashboard
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized); $sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
} }
if ($sDashboardDefinition !== false) { if ($sDashboardDefinition !== false) {
$oDashboard = new RuntimeDashboard($sDashBoardId); $oDashboard = new RuntimeDashboard($sDashBoardId);
$oDashboard->FromXml($sDashboardDefinition); $oDashboard->FromXml($sDashboardDefinition);
@@ -915,11 +954,11 @@ class RuntimeDashboard extends Dashboard
* @inheritDoc * @inheritDoc
* @throws \Exception * @throws \Exception
*/ */
public function Render($oPage, $bEditMode = false, $aExtraParams = [], $bCanEdit = true) public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{ {
if (!isset($aExtraParams['query_params']) && isset($aExtraParams['this->class'])) { if (!isset($aExtraParams['query_params']) && isset($aExtraParams['this->class'])) {
$oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']); $oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']);
$aRenderParams = ['query_params' => $oObj->ToArgsForQuery()]; $aRenderParams = array('query_params' => $oObj->ToArgsForQuery());
} else { } else {
$aRenderParams = $aExtraParams; $aRenderParams = $aExtraParams;
} }
@@ -929,7 +968,7 @@ class RuntimeDashboard extends Dashboard
if (isset($aExtraParams['query_params']['this->object()'])) { if (isset($aExtraParams['query_params']['this->object()'])) {
/** @var \DBObject $oObj */ /** @var \DBObject $oObj */
$oObj = $aExtraParams['query_params']['this->object()']; $oObj = $aExtraParams['query_params']['this->object()'];
$aAjaxParams = ['this->class' => get_class($oObj), 'this->id' => $oObj->GetKey()]; $aAjaxParams = array('this->class' => get_class($oObj), 'this->id' => $oObj->GetKey());
if (isset($aExtraParams['from_dashboard_page'])) { if (isset($aExtraParams['from_dashboard_page'])) {
$aAjaxParams['from_dashboard_page'] = $aExtraParams['from_dashboard_page']; $aAjaxParams['from_dashboard_page'] = $aExtraParams['from_dashboard_page'];
} }
@@ -962,7 +1001,9 @@ class RuntimeDashboard extends Dashboard
} }
JS JS
); );
} else { }
else
{
$oPage->add_script( $oPage->add_script(
<<<EOF <<<EOF
if (typeof(AutoReloadDashboardId$sDivId) !== 'undefined') if (typeof(AutoReloadDashboardId$sDivId) !== 'undefined')
@@ -991,7 +1032,7 @@ EOF
* @throws \CoreUnexpectedValue * @throws \CoreUnexpectedValue
* @throws \MySQLException * @throws \MySQLException
*/ */
protected function RenderSelector(WebPage $oPage, DashboardLayoutUIBlock $oDashboard, $aAjaxParams = []) protected function RenderSelector(WebPage $oPage, DashboardLayoutUIBlock $oDashboard, $aAjaxParams = array())
{ {
if (!$this->HasCustomDashboard()) { if (!$this->HasCustomDashboard()) {
return; return;
@@ -1029,7 +1070,7 @@ EOF
var dashboard = $('.ibo-dashboard#$sDivId') var dashboard = $('.ibo-dashboard#$sDivId')
dashboard.block(); dashboard.block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
{ operation: 'toggle_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, reload_url: $sReloadURL }, { operation: 'toggle_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, reload_url: '$sReloadURL' },
function(data) { function(data) {
dashboard.html(data); dashboard.html(data);
dashboard.unblock(); dashboard.unblock();
@@ -1051,7 +1092,8 @@ JS
*/ */
protected function HasCustomDashboard() protected function HasCustomDashboard()
{ {
try { try
{
// Search for an eventual user defined dashboard // Search for an eventual user defined dashboard
$oUDSearch = new DBObjectSearch('UserDashboard'); $oUDSearch = new DBObjectSearch('UserDashboard');
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
@@ -1059,7 +1101,9 @@ JS
$oUDSet = new DBObjectSet($oUDSearch); $oUDSet = new DBObjectSet($oUDSearch);
return ($oUDSet->Count() > 0); return ($oUDSet->Count() > 0);
} catch (Exception $e) { }
catch (Exception $e)
{
return false; return false;
} }
} }
@@ -1095,23 +1139,21 @@ JS
->AddCSSClass('ibo-action-button'); ->AddCSSClass('ibo-action-button');
$oToolbar->AddSubBlock($oActionButton); $oToolbar->AddSubBlock($oActionButton);
$aActions = []; $aActions = array();
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile)); $sFile = addslashes(utils::LocalPath($this->sDefinitionFile));
$sJSExtraParams = json_encode($aExtraParams); $sJSExtraParams = json_encode($aExtraParams);
if ($this->HasCustomDashboard()) { if ($this->HasCustomDashboard()) {
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)"); $oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem(); $aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
$oRevert = new JSPopupMenuItem( $oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:DeleteCustom'),
'UI:Dashboard:RevertConfirm', "if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false");
Dict::S('UI:Dashboard:DeleteCustom'),
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false"
);
$aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem(); $aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem();
} else { } else {
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:CreateCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)"); $oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:CreateCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem(); $aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
} }
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions); utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
$oActionsMenu = $oPage->GetPopoverMenu($sPopoverMenuId, $aActions) $oActionsMenu = $oPage->GetPopoverMenu($sPopoverMenuId, $aActions)
@@ -1151,7 +1193,7 @@ EOF
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function RenderProperties($oPage, $aExtraParams = []) public function RenderProperties($oPage, $aExtraParams = array())
{ {
parent::RenderProperties($oPage, $aExtraParams); parent::RenderProperties($oPage, $aExtraParams);
@@ -1183,6 +1225,7 @@ EOF
); );
} }
/** /**
* @param WebPage $oPage * @param WebPage $oPage
* *
@@ -1193,11 +1236,11 @@ EOF
* @throws \ReflectionException * @throws \ReflectionException
* @throws \Exception * @throws \Exception
*/ */
public function RenderEditor($oPage, $aExtraParams = []) public function RenderEditor($oPage, $aExtraParams = array())
{ {
if (isset($aExtraParams['this->class'])) { if (isset($aExtraParams['this->class'])) {
$oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']); $oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']);
$aRenderParams = ['query_params' => $oObj->ToArgsForQuery()]; $aRenderParams = array('query_params' => $oObj->ToArgsForQuery());
} else { } else {
$aRenderParams = $aExtraParams; $aRenderParams = $aExtraParams;
} }
@@ -1346,41 +1389,51 @@ JS
// Get the list of all 'dashboard' menus in which we can insert a dashlet // Get the list of all 'dashboard' menus in which we can insert a dashlet
$aAllMenus = ApplicationMenu::ReflectionMenuNodes(); $aAllMenus = ApplicationMenu::ReflectionMenuNodes();
$sRootMenuId = ApplicationMenu::GetRootMenuId($sContextMenuId); $sRootMenuId = ApplicationMenu::GetRootMenuId($sContextMenuId);
$aAllowedDashboards = []; $aAllowedDashboards = array();
$sDefaultDashboard = null; $sDefaultDashboard = null;
// Store the parent menus for acces check // Store the parent menus for acces check
$aParentMenus = []; $aParentMenus = array();
foreach ($aAllMenus as $idx => $aMenu) { foreach($aAllMenus as $idx => $aMenu)
{
/** @var MenuNode $oMenu */ /** @var MenuNode $oMenu */
$oMenu = $aMenu['node']; $oMenu = $aMenu['node'];
if (count(ApplicationMenu::GetChildren($oMenu->GetIndex())) > 0) { if (count(ApplicationMenu::GetChildren($oMenu->GetIndex())) > 0)
{
$aParentMenus[$oMenu->GetMenuId()] = $aMenu; $aParentMenus[$oMenu->GetMenuId()] = $aMenu;
} }
} }
foreach ($aAllMenus as $idx => $aMenu) { foreach($aAllMenus as $idx => $aMenu)
{
$oMenu = $aMenu['node']; $oMenu = $aMenu['node'];
if ($oMenu instanceof DashboardMenuNode) { if ($oMenu instanceof DashboardMenuNode)
{
// Get the root parent for access check // Get the root parent for access check
$sParentId = $aMenu['parent']; $sParentId = $aMenu['parent'];
$aParentMenu = $aParentMenus[$sParentId]; $aParentMenu = $aParentMenus[$sParentId];
while (isset($aParentMenus[$aParentMenu['parent']])) { while (isset($aParentMenus[$aParentMenu['parent']]))
{
// grand parent exists // grand parent exists
$sParentId = $aParentMenu['parent']; $sParentId = $aParentMenu['parent'];
$aParentMenu = $aParentMenus[$sParentId]; $aParentMenu = $aParentMenus[$sParentId];
} }
/** @var \MenuNode $oParentMenu */ /** @var \MenuNode $oParentMenu */
$oParentMenu = $aParentMenu['node']; $oParentMenu = $aParentMenu['node'];
if ($oMenu->IsEnabled() && $oParentMenu->IsEnabled()) { if ($oMenu->IsEnabled() && $oParentMenu->IsEnabled())
{
$sMenuLabel = $oMenu->GetTitle(); $sMenuLabel = $oMenu->GetTitle();
$sParentLabel = Dict::S('Menu:'.$sParentId); $sParentLabel = Dict::S('Menu:'.$sParentId);
if ($sParentLabel != $sMenuLabel) { if ($sParentLabel != $sMenuLabel)
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel; $aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
} else { }
else
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel; $aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
} }
if (empty($sDefaultDashboard) && ($sRootMenuId == ApplicationMenu::GetRootMenuId($oMenu->GetMenuId()))) { if (empty($sDefaultDashboard) && ($sRootMenuId == ApplicationMenu::GetRootMenuId($oMenu->GetMenuId())))
{
$sDefaultDashboard = $oMenu->GetMenuId(); $sDefaultDashboard = $oMenu->GetMenuId();
} }
} }
@@ -1395,17 +1448,21 @@ JS
// Get the list of possible dashlets that support a creation from // Get the list of possible dashlets that support a creation from
// an OQL // an OQL
$aDashlets = []; $aDashlets = array();
foreach (get_declared_classes() as $sDashletClass) { foreach(get_declared_classes() as $sDashletClass)
if (is_subclass_of($sDashletClass, 'Dashlet')) { {
if (is_subclass_of($sDashletClass, 'Dashlet'))
{
$oReflection = new ReflectionClass($sDashletClass); $oReflection = new ReflectionClass($sDashletClass);
if (!$oReflection->isAbstract()) { if (!$oReflection->isAbstract())
$aCallSpec = [$sDashletClass, 'CanCreateFromOQL']; {
$aCallSpec = array($sDashletClass, 'CanCreateFromOQL');
$bShorcutMode = call_user_func($aCallSpec); $bShorcutMode = call_user_func($aCallSpec);
if ($bShorcutMode) { if ($bShorcutMode)
$aCallSpec = [$sDashletClass, 'GetInfo']; {
$aCallSpec = array($sDashletClass, 'GetInfo');
$aInfo = call_user_func($aCallSpec); $aInfo = call_user_func($aCallSpec);
$aDashlets[$sDashletClass] = ['label' => $aInfo['label'], 'class' => $sDashletClass, 'icon' => $aInfo['icon']]; $aDashlets[$sDashletClass] = array('label' => $aInfo['label'], 'class' => $sDashletClass, 'icon' => $aInfo['icon']);
} }
} }
} }
@@ -1413,7 +1470,8 @@ JS
$oSelectorField = new DesignerFormSelectorField('dashlet_class', Dict::S('UI:DashletCreation:DashletType'), ''); $oSelectorField = new DesignerFormSelectorField('dashlet_class', Dict::S('UI:DashletCreation:DashletType'), '');
$oForm->AddField($oSelectorField); $oForm->AddField($oSelectorField);
foreach ($aDashlets as $sDashletClass => $aDashletInfo) { foreach($aDashlets as $sDashletClass => $aDashletInfo)
{
$oSubForm = new DesignerForm(); $oSubForm = new DesignerForm();
$oMetaModel = new ModelReflectionRuntime(); $oMetaModel = new ModelReflectionRuntime();
/** @var \Dashlet $oDashlet */ /** @var \Dashlet $oDashlet */
@@ -1545,7 +1603,7 @@ JS
/** /**
* @inheritDoc * @inheritDoc
*/ */
protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = []) protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = array())
{ {
$sDashletIdOrig = $oDashlet->GetID(); $sDashletIdOrig = $oDashlet->GetID();
$sDashboardSanitizedId = $this->GetSanitizedId(); $sDashboardSanitizedId = $this->GetSanitizedId();
@@ -1572,27 +1630,31 @@ JS
private function UpdateDashletUserPrefs(Dashlet $oDashlet, $sDashletIdOrig, array $aExtraParams) private function UpdateDashletUserPrefs(Dashlet $oDashlet, $sDashletIdOrig, array $aExtraParams)
{ {
$bIsDashletWithListPref = ($oDashlet instanceof DashletObjectList); $bIsDashletWithListPref = ($oDashlet instanceof DashletObjectList);
if (!$bIsDashletWithListPref) { if (!$bIsDashletWithListPref)
{
return; return;
} }
/** @var \DashletObjectList $oDashlet */ /** @var \DashletObjectList $oDashlet */
$bDashletIdInNewFormat = ($sDashletIdOrig === $oDashlet->GetID()); $bDashletIdInNewFormat = ($sDashletIdOrig === $oDashlet->GetID());
if ($bDashletIdInNewFormat) { if ($bDashletIdInNewFormat)
{
return; return;
} }
$sNewPrefKey = $this->GetDashletObjectListAppUserPreferencesPrefix($oDashlet, $aExtraParams, $oDashlet->GetID()); $sNewPrefKey = $this->GetDashletObjectListAppUserPreferencesPrefix($oDashlet, $aExtraParams, $oDashlet->GetID());
$sPrefValueForNewKey = appUserPreferences::GetPref($sNewPrefKey, null); $sPrefValueForNewKey = appUserPreferences::GetPref($sNewPrefKey, null);
$bHasPrefInNewFormat = ($sPrefValueForNewKey !== null); $bHasPrefInNewFormat = ($sPrefValueForNewKey !== null);
if ($bHasPrefInNewFormat) { if ($bHasPrefInNewFormat)
{
return; return;
} }
$sOldPrefKey = $this->GetDashletObjectListAppUserPreferencesPrefix($oDashlet, $aExtraParams, $sDashletIdOrig); $sOldPrefKey = $this->GetDashletObjectListAppUserPreferencesPrefix($oDashlet, $aExtraParams, $sDashletIdOrig);
$sPrefValueForOldKey = appUserPreferences::GetPref($sOldPrefKey, null); $sPrefValueForOldKey = appUserPreferences::GetPref($sOldPrefKey, null);
$bHasPrefInOldFormat = ($sPrefValueForOldKey !== null); $bHasPrefInOldFormat = ($sPrefValueForOldKey !== null);
if (!$bHasPrefInOldFormat) { if (!$bHasPrefInOldFormat)
{
return; return;
} }
@@ -1611,7 +1673,7 @@ JS
private function GetDashletObjectListAppUserPreferencesPrefix(DashletObjectList $oDashlet, $aExtraParams, $sDashletId) private function GetDashletObjectListAppUserPreferencesPrefix(DashletObjectList $oDashlet, $aExtraParams, $sDashletId)
{ {
$sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId; $sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId;
$aClassAliases = []; $aClassAliases = array();
try { try {
$oFilter = $oDashlet->GetDBSearch($aExtraParams); $oFilter = $oDashlet->GetDBSearch($aExtraParams);
$aClassAliases = $oFilter->GetSelectedClasses(); $aClassAliases = $oFilter->GetSelectedClasses();

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -42,11 +41,11 @@ abstract class DashboardLayout
public static function GetInfo() public static function GetInfo()
{ {
return [ return array(
'label' => '', 'label' => '',
'icon' => '', 'icon' => '',
'description' => '', 'description' => '',
]; );
} }
} }
@@ -64,12 +63,16 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
$aKeys = array_reverse(array_keys($aDashlets)); $aKeys = array_reverse(array_keys($aDashlets));
$idx = 0; $idx = 0;
$bNoVisibleFound = true; $bNoVisibleFound = true;
while ($idx < count($aKeys) && $bNoVisibleFound) { while($idx < count($aKeys) && $bNoVisibleFound)
{
/** @var \Dashlet $oDashlet */ /** @var \Dashlet $oDashlet */
$oDashlet = $aDashlets[$aKeys[$idx]]; $oDashlet = $aDashlets[$aKeys[$idx]];
if ($oDashlet::IsVisible()) { if ($oDashlet::IsVisible())
{
$bNoVisibleFound = false; $bNoVisibleFound = false;
} else { }
else
{
unset($aDashlets[$aKeys[$idx]]); unset($aDashlets[$aKeys[$idx]]);
} }
$idx++; $idx++;
@@ -79,17 +82,22 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
protected function TrimCellsArray($aCells) protected function TrimCellsArray($aCells)
{ {
foreach ($aCells as $key => $aDashlets) { foreach($aCells as $key => $aDashlets)
{
$aCells[$key] = $this->TrimCell($aDashlets); $aCells[$key] = $this->TrimCell($aDashlets);
} }
$aKeys = array_reverse(array_keys($aCells)); $aKeys = array_reverse(array_keys($aCells));
$idx = 0; $idx = 0;
$bNoVisibleFound = true; $bNoVisibleFound = true;
while ($idx < count($aKeys) && $bNoVisibleFound) { while($idx < count($aKeys) && $bNoVisibleFound)
{
$aDashlets = $aCells[$aKeys[$idx]]; $aDashlets = $aCells[$aKeys[$idx]];
if (count($aDashlets) > 0) { if (count($aDashlets) > 0)
{
$bNoVisibleFound = false; $bNoVisibleFound = false;
} else { }
else
{
unset($aCells[$aKeys[$idx]]); unset($aCells[$aKeys[$idx]]);
} }
$idx++; $idx++;
@@ -104,7 +112,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
* @param bool $bEditMode * @param bool $bEditMode
* @param array $aExtraParams * @param array $aExtraParams
*/ */
public function Render($oPage, $aCells, $bEditMode = false, $aExtraParams = []) public function Render($oPage, $aCells, $bEditMode = false, $aExtraParams = array())
{ {
// Trim the list of cells to remove the invisible/empty ones at the end of the array // Trim the list of cells to remove the invisible/empty ones at the end of the array
$aCells = $this->TrimCellsArray($aCells); $aCells = $this->TrimCellsArray($aCells);
@@ -149,7 +157,8 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
$oPage->add_script("function updateDashboard".$aExtraParams['dashboard_div_id']."(){".$sJSReload."}"); $oPage->add_script("function updateDashboard".$aExtraParams['dashboard_div_id']."(){".$sJSReload."}");
if ($bEditMode) { // Add one row for extensibility if ($bEditMode) // Add one row for extensibility
{
$oDashboardRow = new DashboardRow(); $oDashboardRow = new DashboardRow();
$oDashboardLayout->AddDashboardRow($oDashboardRow); $oDashboardLayout->AddDashboardRow($oDashboardRow);
@@ -171,7 +180,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
$iColNumber = (int) $iCellIdx % $this->iNbCols; $iColNumber = (int) $iCellIdx % $this->iNbCols;
$iRowNumber = (int) floor($iCellIdx / $this->iNbCols); $iRowNumber = (int) floor($iCellIdx / $this->iNbCols);
return [$iColNumber, $iRowNumber]; return array($iColNumber, $iRowNumber);
} }
} }
@@ -182,13 +191,13 @@ class DashboardLayoutOneCol extends DashboardLayoutMultiCol
parent::__construct(); parent::__construct();
$this->iNbCols = 1; $this->iNbCols = 1;
} }
public static function GetInfo() static public function GetInfo()
{ {
return [ return array(
'label' => 'One Column', 'label' => 'One Column',
'icon' => 'images/layout_1col.png', 'icon' => 'images/layout_1col.png',
'description' => '', 'description' => '',
]; );
} }
} }
@@ -199,13 +208,13 @@ class DashboardLayoutTwoCols extends DashboardLayoutMultiCol
parent::__construct(); parent::__construct();
$this->iNbCols = 2; $this->iNbCols = 2;
} }
public static function GetInfo() static public function GetInfo()
{ {
return [ return array(
'label' => 'Two Columns', 'label' => 'Two Columns',
'icon' => 'images/layout_2col.png', 'icon' => 'images/layout_2col.png',
'description' => '', 'description' => '',
]; );
} }
} }
@@ -216,12 +225,12 @@ class DashboardLayoutThreeCols extends DashboardLayoutMultiCol
parent::__construct(); parent::__construct();
$this->iNbCols = 3; $this->iNbCols = 3;
} }
public static function GetInfo() static public function GetInfo()
{ {
return [ return array(
'label' => 'Two Columns', 'label' => 'Two Columns',
'icon' => 'images/layout_3col.png', 'icon' => 'images/layout_3col.png',
'description' => '', 'description' => '',
]; );
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
xsi:noNamespaceSchemaLocation="https://www.combodo.com/itop-schema/3.3"
version="3.3">
<classes> <classes>
<class id="AbstractResource" _delta="define"> <class id="AbstractResource" _delta="define">
<parent>cmdbAbstractObject</parent> <parent>cmdbAbstractObject</parent>
<properties> <properties>
<comment>/* Resource access control abstraction. Can be herited by abstract resource access control classes. Generally controlled using UR_ACTION_MODIFY access right. */</comment> <comment>/* Resource access control abstraction. Can be herited by abstract resource access control classes. Generaly controlled using UR_ACTION_MODIFY access right. */</comment>
<abstract>true</abstract> <abstract>true</abstract>
</properties> </properties>
<presentation/> <presentation/>
@@ -851,168 +849,5 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
</methods> </methods>
</class> </class>
</classes> </classes>
<property_types _delta="define">
<property_type id="Dashlet" xsi:type="Combodo-AbstractPropertyType"/>
<property_type id="DashletGroupBy" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<label>UI:DashletGroupBy:Title</label>
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletGroupBy:Prop-Title</label>
</node>
<node id="query" xsi:type="Combodo-ValueType-OQL">
<label>UI:DashletGroupBy:Prop-Query</label>
</node>
<node id="group_by" xsi:type="Combodo-ValueType-ClassAttributeGroupBy">
<label>UI:DashletGroupBy:Prop-GroupBy</label>
<class>{{query.selected_class}}</class>
</node>
<node id="style" xsi:type="Combodo-ValueType-Choice"> <!-- Possible de le cacher, etc celui-ci nous met dedans -->
<label>UI:DashletGroupBy:Prop-Style</label>
<values>
<value id="bars">
<label>UI:DashletGroupByBars:Label</label>
</value>
<value id="pie">
<label>UI:DashletGroupByPie:Label</label>
</value>
<value id="table">
<label>UI:DashletGroupByTable:Label</label>
</value>
</values>
</node>
<node id="aggregation_function" xsi:type="Combodo-ValueType-AggregateFunction">
<label>UI:DashletGroupBy:Prop-Function</label>
<class>{{query.selected_class}}</class> <!-- pour savoir si il y a des attributs additionnables -->
</node>
<node id="aggregation_attribute" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:DashletGroupBy:Prop-FunctionAttribute</label>
<relevance-condition>{{aggregation_function.value != 'count'}}</relevance-condition>
<class>{{query.selected_class}}</class>
<category>numeric</category>
</node>
<node id="order_by" xsi:type="Combodo-ValueType-ChoiceFromInput">
<label>UI:DashletGroupBy:Prop-OrderField</label>
<values>
<value id="attribute">
<label>{{aggregation_attribute.label}}</label>
</value>
<value id="function">
<label>{{aggregation_function.label}}</label>
</value>
</values>
</node>
<node id="limit" xsi:type="Combodo-ValueType-Integer">
<label>UI:DashletGroupBy:Prop-Limit</label>
<relevance-condition>{{order_by.value = 'function'}}</relevance-condition>
</node>
<node id="order_direction" xsi:type="Combodo-ValueType-Choice">
<label>UI:DashletGroupBy:Prop-OrderDirection</label>
<values>
<value id="asc">
<label>UI:DashletGroupBy:Order:asc</label>
</value>
<value id="desc">
<label>UI:DashletGroupBy:Order:desc</label>
</value>
</values>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletBadge" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class" xsi:type="Combodo-ValueType-Class">
<label>UI:DashletBadge:Prop-Class</label>
<categories-csv>bizmodel</categories-csv>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletHeaderDynamic" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<label>UI:DashletHeaderDynamic:Title</label>
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletHeaderDynamic:Prop-Title</label>
</node>
<node id="icon" xsi:type="Combodo-ValueType-Icon">
<label>UI:DashletHeaderDynamic:Prop-Icon</label>
</node>
<node id="subtitle" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletHeaderDynamic:Prop-Subtitle</label>
</node>
<node id="query" xsi:type="Combodo-ValueType-OQL">
<label>UI:DashletHeaderDynamic:Prop-Query</label>
</node>
<node id="group_by" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:DashletHeaderDynamic:Prop-GroupBy</label>
<class>{{query.selected_class}}</class>
<category>enum</category>
</node>
<node id="values" xsi:type="Combodo-ValueType-CollectionOfValues">
<label>UI:DashletHeaderDynamic:Prop-Values</label>
<xml-format xsi:type="Combodo-XMLFormat-CSV"/>
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
<class>{{query.selected_class}}</class>
<attribute>{{group_by.attribute}}</attribute>
</value-type>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletHeaderStatic" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletHeaderStatic:Prop-Title</label>
</node>
<node id="icon" xsi:type="Combodo-ValueType-Icon">
<label>UI:DashletHeaderStatic:Prop-Icon</label>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletObjectList" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletObjectList:Prop-Title</label>
</node>
<node id="query" xsi:type="Combodo-ValueType-OQL">
<label>UI:DashletObjectList:Prop-Query</label>
</node>
<node id="menu" xsi:type="Combodo-ValueType-Boolean">
<label>UI:DashletObjectList:Prop-Menu</label>
<on>
<!-- not so cute, but matches exactly 3.2 implementation of boolean fields -->
<label>UI:UserManagement:ActionAllowed:Yes</label>
<value>true</value>
</on>
<off>
<label>UI:UserManagement:ActionAllowed:No</label>
<value>false</value>
</off>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletPlainText" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="text" xsi:type="Combodo-ValueType-Text">
<label>UI:DashletPlainText:Prop-Text</label>
</node>
</nodes>
</definition>
</property_type>
</property_types>
</meta> </meta>
</itop_design> </itop_design>

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -105,7 +104,7 @@ class DisplayBlock
*/ */
public const ENUM_STYLE_CHART_AJAX = 'chart_ajax'; public const ENUM_STYLE_CHART_AJAX = 'chart_ajax';
public const TAG_BLOCK = 'itopblock'; const TAG_BLOCK = 'itopblock';
/** @var \DBSearch */ /** @var \DBSearch */
protected $m_oFilter; protected $m_oFilter;
protected $m_aConditions; // Conditions added to the filter -> avoid duplicate conditions protected $m_aConditions; // Conditions added to the filter -> avoid duplicate conditions
@@ -138,18 +137,20 @@ class DisplayBlock
* *
* @throws \ApplicationException * @throws \ApplicationException
*/ */
public function __construct(DBSearch $oFilter, $sStyle = self::ENUM_STYLE_LIST, $bAsynchronous = false, $aParams = [], $oSet = null) public function __construct(DBSearch $oFilter, $sStyle = self::ENUM_STYLE_LIST, $bAsynchronous = false, $aParams = array(), $oSet = null)
{ {
$this->m_oFilter = $oFilter->DeepClone(); $this->m_oFilter = $oFilter->DeepClone();
$this->m_aConditions = []; $this->m_aConditions = array();
$this->m_sStyle = $sStyle; $this->m_sStyle = $sStyle;
$this->m_bAsynchronous = $bAsynchronous; $this->m_bAsynchronous = $bAsynchronous;
$this->m_aParams = $aParams; $this->m_aParams = $aParams;
$this->m_oSet = $oSet; $this->m_oSet = $oSet;
if (array_key_exists('show_obsolete_data', $aParams)) { if (array_key_exists('show_obsolete_data', $aParams))
{
$this->m_bShowObsoleteData = $aParams['show_obsolete_data']; $this->m_bShowObsoleteData = $aParams['show_obsolete_data'];
} }
if ($this->m_bShowObsoleteData === null) { if ($this->m_bShowObsoleteData === null)
{
// User defined // User defined
$this->m_bShowObsoleteData = utils::ShowObsoleteData(); $this->m_bShowObsoleteData = utils::ShowObsoleteData();
} }
@@ -411,18 +412,22 @@ class DisplayBlock
* @throws \CoreException * @throws \CoreException
* @throws \Exception * @throws \Exception
*/ */
public static function FromObjectSet(DBObjectSet $oSet, $sStyle, $aParams = []) public static function FromObjectSet(DBObjectSet $oSet, $sStyle, $aParams = array())
{ {
$oDummyFilter = new DBObjectSearch($oSet->GetClass()); $oDummyFilter = new DBObjectSearch($oSet->GetClass());
$aKeys = []; $aKeys = array();
$oSet->OptimizeColumnLoad([$oSet->GetClassAlias() => []]); // No need to load all the columns just to get the id $oSet->OptimizeColumnLoad(array($oSet->GetClassAlias() => array())); // No need to load all the columns just to get the id
while ($oObject = $oSet->Fetch()) { while($oObject = $oSet->Fetch())
{
$aKeys[] = $oObject->GetKey(); $aKeys[] = $oObject->GetKey();
} }
$oSet->Rewind(); $oSet->Rewind();
if (count($aKeys) > 0) { if (count($aKeys) > 0)
{
$oDummyFilter->AddCondition('id', $aKeys, 'IN'); $oDummyFilter->AddCondition('id', $aKeys, 'IN');
} else { }
else
{
$oDummyFilter->AddCondition('id', 0, '='); $oDummyFilter->AddCondition('id', 0, '=');
} }
$oBlock = new DisplayBlock($oDummyFilter, $sStyle, false, $aParams); // DisplayBlocks built this way are synchronous $oBlock = new DisplayBlock($oDummyFilter, $sStyle, false, $aParams); // DisplayBlocks built this way are synchronous
@@ -443,7 +448,7 @@ class DisplayBlock
$iStartPos = stripos($sTemplate, '<'.self::TAG_BLOCK.' ', 0); $iStartPos = stripos($sTemplate, '<'.self::TAG_BLOCK.' ', 0);
$iEndPos = stripos($sTemplate, '</'.self::TAG_BLOCK.'>', $iStartPos); $iEndPos = stripos($sTemplate, '</'.self::TAG_BLOCK.'>', $iStartPos);
$iEndTag = stripos($sTemplate, '>', $iStartPos); $iEndTag = stripos($sTemplate, '>', $iStartPos);
$aParams = []; $aParams = array();
if (($iStartPos === false) || ($iEndPos === false)) { if (($iStartPos === false) || ($iEndPos === false)) {
return null; return null;
@@ -451,7 +456,7 @@ class DisplayBlock
$sITopData = substr($sTemplate, 1 + $iEndTag, $iEndPos - $iEndTag - 1); $sITopData = substr($sTemplate, 1 + $iEndTag, $iEndPos - $iEndTag - 1);
$sITopTag = substr($sTemplate, $iStartPos + strlen('<'.self::TAG_BLOCK), $iEndTag - $iStartPos - strlen('<'.self::TAG_BLOCK)); $sITopTag = substr($sTemplate, $iStartPos + strlen('<'.self::TAG_BLOCK), $iEndTag - $iStartPos - strlen('<'.self::TAG_BLOCK));
$aMatches = []; $aMatches = array();
$sBlockClass = "DisplayBlock"; $sBlockClass = "DisplayBlock";
$bAsynchronous = false; $bAsynchronous = false;
$sBlockType = 'list'; $sBlockType = 'list';
@@ -517,28 +522,25 @@ class DisplayBlock
return new $sBlockClass($oFilter, $sBlockType, $bAsynchronous, $aParams); return new $sBlockClass($oFilter, $sBlockType, $bAsynchronous, $aParams);
} }
public function DisplayIntoContentBlock(UIContentBlock $oContentBlock, WebPage $oPage, $sId, $aExtraParams = []) public function DisplayIntoContentBlock(UIContentBlock $oContentBlock, WebPage $oPage, $sId, $aExtraParams = array())
{ {
$oContentBlock->AddSubBlock($this->GetDisplay($oPage, $sId, $aExtraParams)); $oContentBlock->AddSubBlock($this->GetDisplay($oPage, $sId, $aExtraParams));
} }
public function Display(WebPage $oPage, $sId, $aExtraParams = []) public function Display(WebPage $oPage, $sId, $aExtraParams = array())
{ {
$oPage->AddUiBlock($this->GetDisplay($oPage, $sId, $aExtraParams)); $oPage->AddUiBlock($this->GetDisplay($oPage, $sId, $aExtraParams));
} }
public function GetDisplay(WebPage $oPage, $sId, $aExtraParams = []): UIContentBlock public function GetDisplay(WebPage $oPage, $sId, $aExtraParams = array()): UIContentBlock
{ {
$oHtml = new UIContentBlock($sId); $oHtml = new UIContentBlock($sId);
$oHtml->AddCSSClass("display_block"); $oHtml->AddCSSClass("display_block");
$aExtraParams = array_merge($aExtraParams, $this->m_aParams); $aExtraParams = array_merge($aExtraParams, $this->m_aParams);
$aExtraParams['currentId'] = $sId; $aExtraParams['currentId'] = $sId;
$sExtraParams = addslashes(str_replace( $sExtraParams = addslashes(str_replace('"', "'",
'"', json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
"'",
json_encode($aExtraParams)
)); // JSON encode, change the style of the quotes and escape them
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
@@ -547,9 +549,9 @@ class DisplayBlock
$sClass = $aExtraParams['this->class']; $sClass = $aExtraParams['this->class'];
$iKey = $aExtraParams['this->id']; $iKey = $aExtraParams['this->id'];
$oObj = MetaModel::GetObject($sClass, $iKey); $oObj = MetaModel::GetObject($sClass, $iKey);
$aQueryParams = ['this->object()' => $oObj]; $aQueryParams = array('this->object()' => $oObj);
} else { } else {
$aQueryParams = []; $aQueryParams = array();
} }
} }
@@ -585,7 +587,8 @@ class DisplayBlock
'); ');
} }
if ($this->m_sStyle == static::ENUM_STYLE_LIST) { // Search form need to extract result list extra data, the simplest way is to expose this configuration if ($this->m_sStyle == static::ENUM_STYLE_LIST) // Search form need to extract result list extra data, the simplest way is to expose this configuration
{
$listJsonExtraParams = json_encode(json_encode($aExtraParams)); $listJsonExtraParams = json_encode(json_encode($aExtraParams));
$oPage->add_ready_script(" $oPage->add_ready_script("
$('#$sId').data('sExtraParams', ".$listJsonExtraParams."); $('#$sId').data('sExtraParams', ".$listJsonExtraParams.");
@@ -605,7 +608,7 @@ class DisplayBlock
* @throws \DictExceptionMissingString * @throws \DictExceptionMissingString
* @throws \MySQLException * @throws \MySQLException
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
if (!isset($aExtraParams['currentId'])) { if (!isset($aExtraParams['currentId'])) {
$sId = utils::GetUniqueId(); // Works only if the page is not an Ajax one ! $sId = utils::GetUniqueId(); // Works only if the page is not an Ajax one !
@@ -637,7 +640,7 @@ class DisplayBlock
$this->CheckParams($this->m_sStyle, $aExtraParams); $this->CheckParams($this->m_sStyle, $aExtraParams);
// Add the extra params into the filter if they make sense for such a filter // Add the extra params into the filter if they make sense for such a filter
$bDoSearch = utils::ReadParam('dosearch', false); $bDoSearch = utils::ReadParam('dosearch', false);
$aQueryParams = []; $aQueryParams = array();
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
} else { } else {
@@ -645,7 +648,7 @@ class DisplayBlock
$sClass = $aExtraParams['this->class']; $sClass = $aExtraParams['this->class'];
$iKey = $aExtraParams['this->id']; $iKey = $aExtraParams['this->id'];
$oObj = MetaModel::GetObject($sClass, $iKey); $oObj = MetaModel::GetObject($sClass, $iKey);
$aQueryParams = ['this->object()' => $oObj]; $aQueryParams = array('this->object()' => $oObj);
} }
} }
if ($this->m_oSet == null) { if ($this->m_oSet == null) {
@@ -655,7 +658,7 @@ class DisplayBlock
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sClass = $this->m_oFilter->GetClass(); $sClass = $this->m_oFilter->GetClass();
$aFilterCodes = MetaModel::GetFiltersList($sClass); $aFilterCodes = MetaModel::GetFiltersList($sClass);
$aCallSpec = [$sClass, 'MapContextParam']; $aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec)) { if (is_callable($aCallSpec)) {
foreach ($oAppContext->GetNames() as $sContextParam) { foreach ($oAppContext->GetNames() as $sContextParam) {
$sParamCode = call_user_func($aCallSpec, $sContextParam); //Map context parameter to the value/filter code depending on the class $sParamCode = call_user_func($aCallSpec, $sContextParam); //Map context parameter to the value/filter code depending on the class
@@ -690,9 +693,11 @@ class DisplayBlock
} }
} }
if (!is_null($condition)) { if (!is_null($condition))
{
$sOpCode = null; // default operator $sOpCode = null; // default operator
if (is_array($condition)) { if (is_array($condition))
{
// Multiple values, add them as AND X IN (v1, v2, v3...) // Multiple values, add them as AND X IN (v1, v2, v3...)
$sOpCode = 'IN'; $sOpCode = 'IN';
} }
@@ -700,22 +705,26 @@ class DisplayBlock
$this->AddCondition($sFilterCode, $condition, $sOpCode, $bParseSearchString); $this->AddCondition($sFilterCode, $condition, $sOpCode, $bParseSearchString);
} }
} }
if ($bDoSearch) { if ($bDoSearch)
{
// Keep the table_id identifying this table if we're performing a search // Keep the table_id identifying this table if we're performing a search
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); $sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
if ($sTableId != null) { if ($sTableId != null)
{
$aExtraParams['table_id'] = $sTableId; $aExtraParams['table_id'] = $sTableId;
} }
} }
} }
$aOrderBy = []; $aOrderBy = array();
if (isset($aExtraParams['order_by'])) { if (isset($aExtraParams['order_by']))
{
// Convert the string describing the order_by parameter into an array // Convert the string describing the order_by parameter into an array
// The syntax is +attCode1,-attCode2 // The syntax is +attCode1,-attCode2
// attCode1 => ascending, attCode2 => descending // attCode1 => ascending, attCode2 => descending
$aTemp = explode(',', $aExtraParams['order_by']); $aTemp = explode(',', $aExtraParams['order_by']);
foreach ($aTemp as $sTemp) { foreach($aTemp as $sTemp)
$aMatches = []; {
$aMatches = array();
if (preg_match('/^([+-])?(.+)$/', $sTemp, $aMatches)) { if (preg_match('/^([+-])?(.+)$/', $sTemp, $aMatches)) {
$bAscending = true; $bAscending = true;
if ($aMatches[1] == '-') { if ($aMatches[1] == '-') {
@@ -726,10 +735,6 @@ class DisplayBlock
} }
} }
if (!$this->m_oFilter->IsAllDataAllowed() && ($aExtraParams['display_unauthorized_objects'] ?? false) === true) {
$this->m_oFilter->AllowAllData();
}
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams(); $aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams); $this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
} }
@@ -796,13 +801,17 @@ class DisplayBlock
$sHtml .= Dict::format('UI:Error:UnsupportedStyleOfBlock', $this->m_sStyle); $sHtml .= Dict::format('UI:Error:UnsupportedStyleOfBlock', $this->m_sStyle);
} }
$bAutoReload = false; $bAutoReload = false;
if (isset($aExtraParams['auto_reload'])) { if (isset($aExtraParams['auto_reload']))
if ($aExtraParams['auto_reload'] === true) { {
if ($aExtraParams['auto_reload'] === true)
{
// Note: does not work in the switch (case true) because a positive number evaluates to true!!! // Note: does not work in the switch (case true) because a positive number evaluates to true!!!
$aExtraParams['auto_reload'] = 'standard'; $aExtraParams['auto_reload'] = 'standard';
} }
switch ($aExtraParams['auto_reload']) { switch($aExtraParams['auto_reload'])
{
case 'fast': case 'fast':
$bAutoReload = true; $bAutoReload = true;
$iReloadInterval = MetaModel::GetConfig()->GetFastReloadInterval()*1000; $iReloadInterval = MetaModel::GetConfig()->GetFastReloadInterval()*1000;
@@ -815,16 +824,20 @@ class DisplayBlock
break; break;
default: default:
if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0)) { if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0))
{
$bAutoReload = true; $bAutoReload = true;
$iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000; $iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000;
} else { }
else
{
// incorrect config, ignore it // incorrect config, ignore it
$bAutoReload = false; $bAutoReload = false;
} }
} }
} }
if (($bAutoReload) && ($this->m_sStyle != static::ENUM_STYLE_SEARCH)) { // Search form do NOT auto-reload if (($bAutoReload) && ($this->m_sStyle != static::ENUM_STYLE_SEARCH)) // Search form do NOT auto-reload
{
// Used either for asynchronous or auto_reload // Used either for asynchronous or auto_reload
// does a json_encode twice to get a string usable as function parameter // does a json_encode twice to get a string usable as function parameter
$sFilterBefore = $this->m_oFilter->serialize(); $sFilterBefore = $this->m_oFilter->serialize();
@@ -871,7 +884,8 @@ JS
{ {
// Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations) // Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations)
// Moreover, it keeps the query as simple as possible // Moreover, it keeps the query as simple as possible
if (isset($this->m_aConditions[$sFilterCode]) && $condition == $this->m_aConditions[$sFilterCode]) { if (isset($this->m_aConditions[$sFilterCode]) && $condition == $this->m_aConditions[$sFilterCode])
{
// Skip // Skip
return; return;
} }
@@ -881,40 +895,51 @@ JS
$bConditionAdded = false; $bConditionAdded = false;
// If the condition is an external key with a class having a hierarchy, use a "below" criteria // If the condition is an external key with a class having a hierarchy, use a "below" criteria
if (MetaModel::IsValidAttCode($sClass, $sFilterCode)) { if (MetaModel::IsValidAttCode($sClass, $sFilterCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode);
if ($oAttDef->IsExternalKey()) { if ($oAttDef->IsExternalKey())
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass()); $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass());
if ($sHierarchicalKeyCode !== false) { if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($oAttDef->GetTargetClass()); $oFilter = new DBObjectSearch($oAttDef->GetTargetClass());
if (($sOpCode == 'IN') && is_array($condition)) { if (($sOpCode == 'IN') && is_array($condition))
{
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition)); $oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
} else { }
else
{
$oFilter->AddCondition('id', $condition); $oFilter->AddCondition('id', $condition);
} }
$oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass()); $oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass());
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default $oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default
$this->m_oFilter->AddCondition_PointingTo($oHKFilter, $sFilterCode); $this->m_oFilter->AddCondition_PointingTo($oHKFilter, $sFilterCode);
$bConditionAdded = true; $bConditionAdded = true;
} elseif (($sOpCode == 'IN') && is_array($condition)) { }
else if (($sOpCode == 'IN') && is_array($condition))
{
$this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition)); $this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition));
$bConditionAdded = true; $bConditionAdded = true;
} }
} elseif (($sOpCode == 'IN') && is_array($condition)) { }
else if (($sOpCode == 'IN') && is_array($condition))
{
$this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition)); $this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition));
$bConditionAdded = true; $bConditionAdded = true;
} }
} }
// In all other cases, just add the condition directly // In all other cases, just add the condition directly
if (!$bConditionAdded) { if (!$bConditionAdded)
{
$this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator $this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator
} }
} }
protected static function GetConditionIN($oFilter, $sFilterCode, $condition) static protected function GetConditionIN($oFilter, $sFilterCode, $condition)
{ {
$oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias()); $oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias());
$sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')'; $sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')';
@@ -947,10 +972,13 @@ JS
protected function MakeGroupByQuery(&$aExtraParams, &$oGroupByExp, &$sGroupByLabel, &$aGroupBy, &$sAggregationFunction, &$sFctVar, &$sAggregationAttr, &$sSql) protected function MakeGroupByQuery(&$aExtraParams, &$oGroupByExp, &$sGroupByLabel, &$aGroupBy, &$sAggregationFunction, &$sFctVar, &$sAggregationAttr, &$sSql)
{ {
$sAlias = $this->m_oFilter->GetClassAlias(); $sAlias = $this->m_oFilter->GetClassAlias();
if (isset($aExtraParams['group_by_label'])) { if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']); $oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label']; $sGroupByLabel = $aExtraParams['group_by_label'];
} else { }
else
{
// Backward compatibility: group_by is simply a field id // Backward compatibility: group_by is simply a field id
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias); $oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']); $sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
@@ -958,52 +986,61 @@ JS
// Security filtering // Security filtering
$aFields = $oGroupByExp->ListRequiredFields(); $aFields = $oGroupByExp->ListRequiredFields();
foreach ($aFields as $sFieldAlias) { foreach($aFields as $sFieldAlias)
$aMatches = []; {
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches)) { $aMatches = array();
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]); $sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]); $oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ($oAttDef instanceof AttributeOneWayPassword) { if ($oAttDef instanceof AttributeOneWayPassword)
{
throw new Exception('Grouping on password fields is not supported.'); throw new Exception('Grouping on password fields is not supported.');
} }
} }
} }
$aGroupBy = []; $aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp; $aGroupBy['grouped_by_1'] = $oGroupByExp;
$aQueryParams = []; $aQueryParams = array();
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
} }
$aFunctions = []; $aFunctions = array();
$sAggregationFunction = 'count'; $sAggregationFunction = 'count';
$sFctVar = '_itop_count_'; $sFctVar = '_itop_count_';
$sAggregationAttr = ''; $sAggregationAttr = '';
if (isset($aExtraParams['aggregation_function']) && !empty($aExtraParams['aggregation_attribute'])) { if (isset($aExtraParams['aggregation_function']) && !empty($aExtraParams['aggregation_attribute']))
{
$sAggregationFunction = $aExtraParams['aggregation_function']; $sAggregationFunction = $aExtraParams['aggregation_function'];
$sAggregationAttr = $aExtraParams['aggregation_attribute']; $sAggregationAttr = $aExtraParams['aggregation_attribute'];
$oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAggregationAttr.'`'); $oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAggregationAttr.'`');
$oFctExpr = new FunctionExpression(strtoupper($sAggregationFunction), [$oAttrExpr]); $oFctExpr = new FunctionExpression(strtoupper($sAggregationFunction), array($oAttrExpr));
$sFctVar = '_itop_'.$sAggregationFunction.'_'; $sFctVar = '_itop_'.$sAggregationFunction.'_';
$aFunctions = [$sFctVar => $oFctExpr]; $aFunctions = array($sFctVar => $oFctExpr);
} }
if (!empty($sAggregationAttr)) { if (!empty($sAggregationAttr))
{
$sClass = $this->m_oFilter->GetClass(); $sClass = $this->m_oFilter->GetClass();
$sAggregationAttr = MetaModel::GetLabel($sClass, $sAggregationAttr); $sAggregationAttr = MetaModel::GetLabel($sClass, $sAggregationAttr);
} }
$iLimit = 0; $iLimit = 0;
if (isset($aExtraParams['limit'])) { if (isset($aExtraParams['limit']))
{
$iLimit = intval($aExtraParams['limit']); $iLimit = intval($aExtraParams['limit']);
} }
$aOrderBy = []; $aOrderBy = array();
if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by'])) { if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by']))
switch ($aExtraParams['order_by']) { {
switch ($aExtraParams['order_by'])
{
case 'attribute': case 'attribute':
$aOrderBy = ['grouped_by_1' => ($aExtraParams['order_direction'] === 'asc')]; $aOrderBy = array('grouped_by_1' => ($aExtraParams['order_direction'] === 'asc'));
break; break;
case 'function': case 'function':
$aOrderBy = [$sFctVar => ($aExtraParams['order_direction'] === 'asc')]; $aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc'));
break; break;
} }
} }
@@ -1039,31 +1076,31 @@ JS
$this->AddCondition($sFilterCode, $sContextParamValue); $this->AddCondition($sFilterCode, $sContextParamValue);
} }
} }
$aQueryParams = []; $aQueryParams = array();
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
} }
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, [], $aQueryParams); $this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData); $this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
} }
// Summary details // Summary details
$aCounts = []; $aCounts = array();
$aStateLabels = []; $aStateLabels = array();
if (!empty($sStateAttrCode) && !empty($sStatesList)) { if (!empty($sStateAttrCode) && !empty($sStatesList)) {
$aStates = explode(',', $sStatesList); $aStates = explode(',', $sStatesList);
// Generate one count + group by query [#1330] // Generate one count + group by query [#1330]
$sClassAlias = $this->m_oFilter->GetClassAlias(); $sClassAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExpr = Expression::FromOQL($sClassAlias.'.'.$sStateAttrCode); $oGroupByExpr = Expression::FromOQL($sClassAlias.'.'.$sStateAttrCode);
$aGroupBy = ['group1' => $oGroupByExpr]; $aGroupBy = array('group1' => $oGroupByExpr);
$oGroupBySearch = $this->m_oFilter->DeepClone(); $oGroupBySearch = $this->m_oFilter->DeepClone();
if (isset($this->m_bShowObsoleteData)) { if (isset($this->m_bShowObsoleteData)) {
$oGroupBySearch->SetShowObsoleteData($this->m_bShowObsoleteData); $oGroupBySearch->SetShowObsoleteData($this->m_bShowObsoleteData);
} }
$sCountGroupByQuery = $oGroupBySearch->MakeGroupByQuery($aQueryParams, $aGroupBy, false); $sCountGroupByQuery = $oGroupBySearch->MakeGroupByQuery($aQueryParams, $aGroupBy, false);
$aCountGroupByResults = CMDBSource::QueryToArray($sCountGroupByQuery); $aCountGroupByResults = CMDBSource::QueryToArray($sCountGroupByQuery);
$aCountsQueryResults = []; $aCountsQueryResults = array();
foreach ($aCountGroupByResults as $aCountGroupBySingleResult) { foreach ($aCountGroupByResults as $aCountGroupBySingleResult) {
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1]; $aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
} }
@@ -1077,8 +1114,7 @@ JS
: 0; : 0;
if ($aCounts[$sStateValue] == 0) { if ($aCounts[$sStateValue] == 0) {
$aCounts[$sStateValue] = ['link' => '-', 'label' => $aCounts[$sStateValue]]; $aCounts[$sStateValue] = ['link' => '-', 'label' => $aCounts[$sStateValue]];;
;
} else { } else {
$oSingleGroupByValueFilter = $this->m_oFilter->DeepClone(); $oSingleGroupByValueFilter = $this->m_oFilter->DeepClone();
$oSingleGroupByValueFilter->AddCondition($sStateAttrCode, $sStateValue, '='); $oSingleGroupByValueFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
@@ -1142,8 +1178,7 @@ JS
$('#".$oBlock->GetId()."').html(data); $('#".$oBlock->GetId()."').html(data);
$('#".$oBlock->GetId()."').unblock(); $('#".$oBlock->GetId()."').unblock();
}); });
$('#".$oBlock->GetId()."').unblock();" $('#".$oBlock->GetId()."').unblock();");
);
return $oBlock; return $oBlock;
} }
@@ -1185,11 +1220,11 @@ JS
$this->AddCondition($sFilterCode, $sContextParamValue); $this->AddCondition($sFilterCode, $sContextParamValue);
} }
} }
$aQueryParams = []; $aQueryParams = array();
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
} }
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, [], $aQueryParams); $this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData); $this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
} }
$iCount = $this->m_oSet->Count(); $iCount = $this->m_oSet->Count();
@@ -1206,15 +1241,8 @@ JS
if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY)) { if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY)) {
$sCreateActionUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$sClass.$oAppContext->GetForLink(true); $sCreateActionUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$sClass.$oAppContext->GetForLink(true);
$sCreateActionLabel = Dict::Format('UI:Button:Create'); $sCreateActionLabel = Dict::Format('UI:Button:Create');
$oBlock = DashletFactory::MakeForDashletBadge( $oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, $sCreateActionUrl,
$sClassIconUrl, $sCreateActionLabel, $aRefreshParams);
$sHyperlink,
$iCount,
$sClassLabel,
$sCreateActionUrl,
$sCreateActionLabel,
$aRefreshParams
);
} else { } else {
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams); $oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
} }
@@ -1244,9 +1272,9 @@ JS
$aRes = CMDBSource::QueryToArray($sSql); $aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = []; $aGroupBy = array();
$aLabels = []; $aLabels = array();
$aValues = []; $aValues = array();
$iTotalCount = 0; $iTotalCount = 0;
foreach ($aRes as $iRow => $aRow) { foreach ($aRes as $iRow => $aRow) {
$sValue = $aRow['grouped_by_1']; $sValue = $aRow['grouped_by_1'];
@@ -1257,7 +1285,7 @@ JS
$iTotalCount += $aRow['_itop_count_']; $iTotalCount += $aRow['_itop_count_'];
} }
$aData = []; $aData = array();
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink(true); $sParams = $oAppContext->GetForLink(true);
foreach ($aGroupBy as $iRow => $iCount) { foreach ($aGroupBy as $iRow => $iCount) {
@@ -1268,22 +1296,22 @@ JS
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
} else { } else {
$aQueryParams = []; $aQueryParams = array();
} }
$sFilter = rawurlencode($oSubsetSearch->serialize(false, $aQueryParams)); $sFilter = rawurlencode($oSubsetSearch->serialize(false, $aQueryParams));
$aData[] = [ $aData[] = array(
'group' => $aLabels[$iRow], 'group' => $aLabels[$iRow],
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1$sParams&filter=$sFilter\">$iCount</a>", 'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1$sParams&filter=$sFilter\">$iCount</a>"
]; // TO DO: add the context information ); // TO DO: add the context information
} }
$aAttribs = [ $aAttribs = array(
'group' => ['label' => $sGroupByLabel, 'description' => ''], 'group' => array('label' => $sGroupByLabel, 'description' => ''),
'value' => [ 'value' => array(
'label' => Dict::S('UI:GroupBy:'.$sAggregationFunction), 'label' => Dict::S('UI:GroupBy:'.$sAggregationFunction),
'description' => Dict::Format('UI:GroupBy:'.$sAggregationFunction.'+', $sAggregationAttr), 'description' => Dict::Format('UI:GroupBy:'.$sAggregationFunction.'+', $sAggregationAttr),
], ),
]; );
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection'; $sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams(); $aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
@@ -1381,12 +1409,10 @@ JS
$oBlock->aExtraParams = $aExtraParams; $oBlock->aExtraParams = $aExtraParams;
$oBlock->sFilter = $this->m_oFilter->ToOQL(); $oBlock->sFilter = $this->m_oFilter->ToOQL();
// Check the classes that can be read (i.e authorized) by this user... // Check the classes that can be read (i.e authorized) by this user...
foreach ($aClasses as $sAlias => $sClassName) { foreach ($aClasses as $sAlias => $sClassName) {
if ( if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO) {
(UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) !== UR_ALLOWED_NO)
|| ($aExtraParams['display_unauthorized_objects'] ?? false) === true
) {
$aAuthorizedClasses[$sAlias] = $sClassName; $aAuthorizedClasses[$sAlias] = $sClassName;
} }
} }
@@ -1405,7 +1431,7 @@ JS
$sSearchFilter = $this->m_oSet->GetFilter()->serialize(); $sSearchFilter = $this->m_oSet->GetFilter()->serialize();
// Limit the size of the URL (N°1585 - request uri too long) // Limit the size of the URL (N°1585 - request uri too long)
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) { if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) {
$oBlock->sEventAttachedData = json_encode([ $oBlock->sEventAttachedData = json_encode(array(
'filter' => $sSearchFilter, 'filter' => $sSearchFilter,
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(), 'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()), 'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
@@ -1413,7 +1439,7 @@ JS
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(), 'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
'breadcrumb_icon' => 'fas fa-search', 'breadcrumb_icon' => 'fas fa-search',
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES, 'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
]); ));
} }
} }
@@ -1446,25 +1472,25 @@ JS
$oContentBlock = new UIContentBlock(); $oContentBlock = new UIContentBlock();
$oHtml = new Html(); $oHtml = new Html();
$oContentBlock->AddSubBlock($oHtml); $oContentBlock->AddSubBlock($oHtml);
$aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']) : []; $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']) : array();
if (!isset($aExtraParams['group_by'])) { if (!isset($aExtraParams['group_by'])) {
$oHtml->AddHtml('<p>'.Dict::S('UI:Error:MandatoryTemplateParameter_group_by').'</p>'); $oHtml->AddHtml('<p>'.Dict::S('UI:Error:MandatoryTemplateParameter_group_by').'</p>');
} else { } else {
$aGroupByFields = []; $aGroupByFields = array();
$aGroupBy = explode(',', $aExtraParams['group_by']); $aGroupBy = explode(',', $aExtraParams['group_by']);
foreach ($aGroupBy as $sGroupBy) { foreach ($aGroupBy as $sGroupBy) {
$aMatches = []; $aMatches = array();
if (preg_match('/^(.+)\.(.+)$/', $sGroupBy, $aMatches) > 0) { if (preg_match('/^(.+)\.(.+)$/', $sGroupBy, $aMatches) > 0) {
$aGroupByFields[] = ['alias' => $aMatches[1], 'att_code' => $aMatches[2]]; $aGroupByFields[] = array('alias' => $aMatches[1], 'att_code' => $aMatches[2]);
} }
} }
if (count($aGroupByFields) == 0) { if (count($aGroupByFields) == 0) {
$oHtml->AddHtml('<p>'.Dict::Format('UI:Error:InvalidGroupByFields', $aExtraParams['group_by']).'</p>'); $oHtml->AddHtml('<p>'.Dict::Format('UI:Error:InvalidGroupByFields', $aExtraParams['group_by']).'</p>');
} else { } else {
$aResults = []; $aResults = array();
$aCriteria = []; $aCriteria = array();
while ($aObjects = $this->m_oSet->FetchAssoc()) { while ($aObjects = $this->m_oSet->FetchAssoc()) {
$aKeys = []; $aKeys = array();
foreach ($aGroupByFields as $aField) { foreach ($aGroupByFields as $aField) {
$sAlias = $aField['alias']; $sAlias = $aField['alias'];
if (is_null($aObjects[$sAlias])) { if (is_null($aObjects[$sAlias])) {
@@ -1481,7 +1507,7 @@ JS
$oHtml->AddHtml("<table>\n"); $oHtml->AddHtml("<table>\n");
// Construct a new (parametric) query that will return the content of this block // Construct a new (parametric) query that will return the content of this block
$oBlockFilter = $this->m_oFilter->DeepClone(); $oBlockFilter = $this->m_oFilter->DeepClone();
$aExpressions = []; $aExpressions = array();
$index = 0; $index = 0;
foreach ($aGroupByFields as $aField) { foreach ($aGroupByFields as $aField) {
$aExpressions[] = '`'.$aField['alias'].'`.`'.$aField['att_code'].'` = :param'.$index++; $aExpressions[] = '`'.$aField['alias'].'`.`'.$aField['att_code'].'` = :param'.$index++;
@@ -1493,7 +1519,7 @@ JS
foreach ($aResults as $sCategory => $aObjects) { foreach ($aResults as $sCategory => $aObjects) {
$oHtml->AddHtml("<tr><td><h1>$sCategory</h1></td></tr>\n"); $oHtml->AddHtml("<tr><td><h1>$sCategory</h1></td></tr>\n");
if (count($aDisplayAliases) == 1) { if (count($aDisplayAliases) == 1) {
$aSimpleArray = []; $aSimpleArray = array();
foreach ($aObjects as $aRow) { foreach ($aObjects as $aRow) {
$oObj = $aRow[$aDisplayAliases[0]]; $oObj = $aRow[$aDisplayAliases[0]];
if (!is_null($oObj)) { if (!is_null($oObj)) {
@@ -1509,12 +1535,12 @@ JS
$oHtml->AddHtml("</td></tr>\n"); $oHtml->AddHtml("</td></tr>\n");
} else { } else {
$index = 0; $index = 0;
$aArgs = []; $aArgs = array();
foreach ($aGroupByFields as $aField) { foreach ($aGroupByFields as $aField) {
$aArgs['param'.$index] = $aCriteria[$sCategory][$aField['alias'].'.'.$aField['att_code']]; $aArgs['param'.$index] = $aCriteria[$sCategory][$aField['alias'].'.'.$aField['att_code']];
$index++; $index++;
} }
$oSet = new CMDBObjectSet($oBlockFilter, [], $aArgs); $oSet = new CMDBObjectSet($oBlockFilter, array(), $aArgs);
if (empty($aExtraParams['currentId'])) { if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !! $iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
} else { } else {
@@ -1601,7 +1627,7 @@ JS
{ {
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie'; $sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
$sId = utils::ReadParam('id', ''); $sId = utils::ReadParam('id', '');
$aValues = []; $aValues = array();
$oBlock = null; $oBlock = null;
$sJSURLs = ''; $sJSURLs = '';
@@ -1612,18 +1638,21 @@ JS
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql); $this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
$aRes = CMDBSource::QueryToArray($sSql); $aRes = CMDBSource::QueryToArray($sSql);
$iTotalCount = 0; $iTotalCount = 0;
$aURLs = []; $aURLs = array();
foreach ($aRes as $iRow => $aRow) { foreach ($aRes as $iRow => $aRow) {
$sValue = $aRow['grouped_by_1']; $sValue = $aRow['grouped_by_1'];
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue); $sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$iTotalCount += $aRow['_itop_count_']; $iTotalCount += $aRow['_itop_count_'];
$aValues[] = [ $aValues[] = array(
'label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'),
'label_html' => $sHtmlValue, 'label_html' => $sHtmlValue,
'value' => (float)$aRow[$sFctVar], 'value' => (float)$aRow[$sFctVar],
]; );
// Build the search for this subset // Build the search for this subset
$oSubsetSearch = $this->m_oFilter->DeepClone(); $oSubsetSearch = $this->m_oFilter->DeepClone();
@@ -1662,7 +1691,7 @@ JS
$aColumns = []; $aColumns = [];
$aNames = []; $aNames = [];
foreach ($aValues as $idx => $aValue) { foreach ($aValues as $idx => $aValue) {
$aColumns[] = ['series_'.$idx, (float)$aValue['value']]; $aColumns[] = array('series_'.$idx, (float)$aValue['value']);
$aNames['series_'.$idx] = $aValue['label']; $aNames['series_'.$idx] = $aValue['label'];
} }
@@ -1711,12 +1740,12 @@ JS
$oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile); $oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile);
$oBlock->sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search'.$oAppContext->GetForLink(true).'&filter='.rawurlencode($this->m_oFilter->serialize()).'&format=csv'; $oBlock->sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search'.$oAppContext->GetForLink(true).'&filter='.rawurlencode($this->m_oFilter->serialize()).'&format=csv';
// Pass the parameters via POST, since expression may be very long // Pass the parameters via POST, since expression may be very long
$aParamsToPost = [ $aParamsToPost = array(
'expression' => $this->m_oFilter->ToOQL(true), 'expression' => $this->m_oFilter->ToOQL(true),
'format' => 'csv', 'format' => 'csv',
'filename' => $oBlock->sCsvFile, 'filename' => $oBlock->sCsvFile,
'charset' => 'UTF-8', 'charset' => 'UTF-8',
]; );
if ($oBlock->bAdvancedMode) { if ($oBlock->bAdvancedMode) {
$oBlock->sDownloadLink .= '&fields_advanced=1'; $oBlock->sDownloadLink .= '&fields_advanced=1';
$aParamsToPost['fields_advanced'] = 1; $aParamsToPost['fields_advanced'] = 1;
@@ -1775,7 +1804,8 @@ class MenuBlock extends DisplayBlock
$oRouter = Router::GetInstance(); $oRouter = Router::GetInstance();
$oRenderBlock = new UIContentBlock(); $oRenderBlock = new UIContentBlock();
if ($this->m_sStyle == 'popup') { // popup is a synonym of 'list' for backward compatibility if ($this->m_sStyle == 'popup') // popup is a synonym of 'list' for backward compatibility
{
$this->m_sStyle = static::ENUM_STYLE_LIST; $this->m_sStyle = static::ENUM_STYLE_LIST;
} }
@@ -1814,6 +1844,7 @@ class MenuBlock extends DisplayBlock
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(true); $sContext = $oAppContext->GetForLink(true);
$sFilter = $this->GetFilter()->serialize(); $sFilter = $this->GetFilter()->serialize();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass); $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot(); $sRootUrl = utils::GetAbsoluteUrlAppRoot();
@@ -1891,8 +1922,8 @@ class MenuBlock extends DisplayBlock
// Life cycle actions may be available... if all objects are in the same state // Life cycle actions may be available... if all objects are in the same state
// Group by <state> // Group by <state>
$oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias()); $oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias());
$aGroupBy = ['__state__' => $oGroupByExp]; $aGroupBy = array('__state__' => $oGroupByExp);
$aQueryParams = []; $aQueryParams = array();
if (isset($aExtraParams['query_params'])) { if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params']; $aQueryParams = $aExtraParams['query_params'];
} }
@@ -1924,10 +1955,10 @@ class MenuBlock extends DisplayBlock
switch ($iActionAllowed) { switch ($iActionAllowed) {
case UR_ALLOWED_YES: case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS: case UR_ALLOWED_DEPENDS:
$aTransitionActions[$sStimulusCode] = [ $aTransitionActions[$sStimulusCode] = array(
'label' => $aStimuli[$sStimulusCode]->GetLabel(), 'label' => $aStimuli[$sStimulusCode]->GetLabel(),
'url' => "{$sRootUrl}pages/UI.php?stimulus=$sStimulusCode&class=$sLifecycleClass&{$sUrlQueryString}", 'url' => "{$sRootUrl}pages/UI.php?stimulus=$sStimulusCode&class=$sLifecycleClass&{$sUrlQueryString}",
] + $aActionParams; ) + $aActionParams;
break; break;
default: default:
@@ -1989,16 +2020,16 @@ class MenuBlock extends DisplayBlock
// Just one object in the set, possible actions are "new / clone / modify and delete" // Just one object in the set, possible actions are "new / clone / modify and delete"
if (!isset($aExtraParams['link_attr'])) { if (!isset($aExtraParams['link_attr'])) {
if ($bIsModifyAllowed) { if ($bIsModifyAllowed) {
$aRegularActions['UI:Menu:Modify'] = [ $aRegularActions['UI:Menu:Modify'] = array(
'label' => Dict::S('UI:Menu:Modify'), 'label' => Dict::S('UI:Menu:Modify'),
'url' => $oRouter->GenerateUrl('object.modify', ['class' => $sClass, 'id' => $id]) . "{$sContext}#", 'url' => $oRouter->GenerateUrl('object.modify', ['class' => $sClass, 'id' => $id]) . "{$sContext}#",
] + $aActionParams; ) + $aActionParams;
} }
if ($bIsDeleteAllowed) { if ($bIsDeleteAllowed) {
$aRegularActions['UI:Menu:Delete'] = [ $aRegularActions['UI:Menu:Delete'] = array(
'label' => Dict::S('UI:Menu:Delete'), 'label' => Dict::S('UI:Menu:Delete'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}", 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}",
] + $aActionParams; ) + $aActionParams;
} }
// Relations... // Relations...
@@ -2007,16 +2038,16 @@ class MenuBlock extends DisplayBlock
$this->AddMenuSeparator($aRegularActions); $this->AddMenuSeparator($aRegularActions);
foreach ($aRelations as $sRelationCode => $aRelationInfo) { foreach ($aRelations as $sRelationCode => $aRelationInfo) {
if (array_key_exists('down', $aRelationInfo)) { if (array_key_exists('down', $aRelationInfo)) {
$aRegularActions['UI:Menu:'.$sRelationCode.'_down'] = [ $aRegularActions[$sRelationCode.'_down'] = array(
'label' => $aRelationInfo['down'], 'label' => $aRelationInfo['down'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}", 'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}",
] + $aActionParams; ) + $aActionParams;
} }
if (array_key_exists('up', $aRelationInfo)) { if (array_key_exists('up', $aRelationInfo)) {
$aRegularActions['UI:Menu:'.$sRelationCode.'_up'] = [ $aRegularActions[$sRelationCode.'_up'] = array(
'label' => $aRelationInfo['up'], 'label' => $aRelationInfo['up'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}", 'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}",
] + $aActionParams; ) + $aActionParams;
} }
} }
} }
@@ -2028,7 +2059,7 @@ class MenuBlock extends DisplayBlock
$bCanKill = false; $bCanKill = false;
$oUser = UserRights::GetUserObject(); $oUser = UserRights::GetUserObject();
$aUserProfiles = []; $aUserProfiles = array();
if (!is_null($oUser)) { if (!is_null($oUser)) {
$oProfileSet = $oUser->Get('profile_list'); $oProfileSet = $oUser->Get('profile_list');
while ($oProfile = $oProfileSet->Fetch()) { while ($oProfile = $oProfileSet->Fetch()) {
@@ -2050,10 +2081,10 @@ class MenuBlock extends DisplayBlock
if ($bCanKill) { if ($bCanKill) {
$this->AddMenuSeparator($aRegularActions); $this->AddMenuSeparator($aRegularActions);
$aRegularActions['concurrent_lock_unlock'] = [ $aRegularActions['concurrent_lock_unlock'] = array(
'label' => Dict::S('UI:Menu:KillConcurrentLock'), 'label' => Dict::S('UI:Menu:KillConcurrentLock'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}", 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}",
]; );
} }
} }
} }
@@ -2061,7 +2092,7 @@ class MenuBlock extends DisplayBlock
$this->AddMenuSeparator($aRegularActions); $this->AddMenuSeparator($aRegularActions);
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) { $this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
$aRegularActions[$sLabel] = ['label' => $sLabel, 'url' => $data] + $aActionParams; $aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
}); });
} }
break; break;
@@ -2258,16 +2289,6 @@ class MenuBlock extends DisplayBlock
$sIconClass = 'fas fa-file-pdf fa-lg'; $sIconClass = 'fas fa-file-pdf fa-lg';
$sLabel = ''; $sLabel = '';
break; break;
case 'UI:Menu:impacts_up':
$sIconClass = 'fas fa-sitemap fa-rotate-180';
$sLabel = '';
$aAction['tooltip'] = Dict::S('Relation:impacts/UpStream');
break;
case 'UI:Menu:impacts_down':
$sIconClass = 'fas fa-sitemap';
$sLabel = '';
$aAction['tooltip'] = Dict::S('Relation:impacts/DownStream');
break;
default: default:
if (isset($aAction['icon_class']) && (strlen($aAction['icon_class']) > 0)) { if (isset($aAction['icon_class']) && (strlen($aAction['icon_class']) > 0)) {
@@ -2437,11 +2458,13 @@ class MenuBlock extends DisplayBlock
protected function AddMenuSeparator(&$aActions) protected function AddMenuSeparator(&$aActions)
{ {
$sSeparator = '<hr class="menu-separator"/>'; $sSeparator = '<hr class="menu-separator"/>';
if (count($aActions) > 0) { // Make sure that the separator is not the first item in the menu if (count($aActions) > 0) // Make sure that the separator is not the first item in the menu
{
$aKeys = array_keys($aActions); $aKeys = array_keys($aActions);
$sLastKey = array_pop($aKeys); $sLastKey = array_pop($aKeys);
if ($aActions[$sLastKey]['label'] != $sSeparator) { // Make sure there are no 2 consecutive separators if ($aActions[$sLastKey]['label'] != $sSeparator) // Make sure there are no 2 consecutive separators
$aActions['sep_'.(count($aActions) - 1)] = ['label' => $sSeparator, 'url' => '']; {
$aActions['sep_'.(count($aActions)-1)] = array('label' => $sSeparator, 'url' => '');
} }
} }
} }
@@ -2501,10 +2524,10 @@ class MenuBlock extends DisplayBlock
*/ */
protected function AddBulkDeleteObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:BulkDelete', $sActionLabel = 'UI:Menu:BulkDelete') protected function AddBulkDeleteObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:BulkDelete', $sActionLabel = 'UI:Menu:BulkDelete')
{ {
$aActions[$sActionIdentifier] = [ $aActions[$sActionIdentifier] = array(
'label' => Dict::S($sActionLabel), 'label' => Dict::S($sActionLabel),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_deletion&filter=".urlencode($sFilter)), 'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_deletion&filter=".urlencode($sFilter)),
] + $this->GetDefaultParamsForMenuAction(); ) + $this->GetDefaultParamsForMenuAction();
} }
/** /**

View File

@@ -22,28 +22,31 @@ class ExcelExporter
public function __construct($sToken = null) public function __construct($sToken = null)
{ {
$this->aStatistics = [ $this->aStatistics = array(
'objects_count' => 0, 'objects_count' => 0,
'total_duration' => 0, 'total_duration' => 0,
'data_retrieval_duration' => 0, 'data_retrieval_duration' => 0,
'excel_build_duration' => 0, 'excel_build_duration' => 0,
'excel_write_duration' => 0, 'excel_write_duration' => 0,
'peak_memory_usage' => 0, 'peak_memory_usage' => 0,
]; );
$this->fStartTime = microtime(true); $this->fStartTime = microtime(true);
$this->oSearch = null; $this->oSearch = null;
$this->sState = 'new'; $this->sState = 'new';
$this->aObjectsIDs = []; $this->aObjectsIDs = array();
$this->iPosition = 0; $this->iPosition = 0;
$this->aAuthorizedClasses = null; $this->aAuthorizedClasses = null;
$this->aTableHeaders = null; $this->aTableHeaders = null;
$this->sOutputFilePath = null; $this->sOutputFilePath = null;
$this->bAdvancedMode = false; $this->bAdvancedMode = false;
$this->CheckDataDir(); $this->CheckDataDir();
if ($sToken == null) { if ($sToken == null)
{
$this->sToken = $this->GetNewToken(); $this->sToken = $this->GetNewToken();
} else { }
else
{
$this->sToken = $sToken; $this->sToken = $sToken;
$this->ReloadState(); $this->ReloadState();
} }
@@ -51,10 +54,13 @@ class ExcelExporter
public function __destruct() public function __destruct()
{ {
if (($this->sState != 'done') && ($this->sState != 'error') && ($this->sToken != null)) { if (($this->sState != 'done') && ($this->sState != 'error') && ($this->sToken != null))
{
// Operation in progress, save the state // Operation in progress, save the state
$this->SaveState(); $this->SaveState();
} else { }
else
{
// Operation completed, cleanup the temp files // Operation completed, cleanup the temp files
@unlink($this->GetStateFile()); @unlink($this->GetStateFile());
@unlink($this->GetDataFile()); @unlink($this->GetDataFile());
@@ -79,7 +85,7 @@ class ExcelExporter
public function SaveState() public function SaveState()
{ {
$aState = [ $aState = array(
'state' => $this->sState, 'state' => $this->sState,
'statistics' => $this->aStatistics, 'statistics' => $this->aStatistics,
'filter' => $this->oSearch->serialize(), 'filter' => $this->oSearch->serialize(),
@@ -88,7 +94,7 @@ class ExcelExporter
'object_ids' => $this->aObjectsIDs, 'object_ids' => $this->aObjectsIDs,
'output_file_path' => $this->sOutputFilePath, 'output_file_path' => $this->sOutputFilePath,
'advanced_mode' => $this->bAdvancedMode, 'advanced_mode' => $this->bAdvancedMode,
]; );
file_put_contents($this->GetStateFile(), json_encode($aState)); file_put_contents($this->GetStateFile(), json_encode($aState));
@@ -97,16 +103,19 @@ class ExcelExporter
public function ReloadState() public function ReloadState()
{ {
if ($this->sToken == null) { if ($this->sToken == null)
{
throw new Exception('ExcelExporter not initialized with a token, cannot reload state'); throw new Exception('ExcelExporter not initialized with a token, cannot reload state');
} }
if (!file_exists($this->GetStateFile())) { if (!file_exists($this->GetStateFile()))
{
throw new Exception("ExcelExporter: missing status file '".$this->GetStateFile()."', cannot reload state."); throw new Exception("ExcelExporter: missing status file '".$this->GetStateFile()."', cannot reload state.");
} }
$sJson = file_get_contents($this->GetStateFile()); $sJson = file_get_contents($this->GetStateFile());
$aState = json_decode($sJson, true); $aState = json_decode($sJson, true);
if ($aState === null) { if ($aState === null)
{
throw new Exception("ExcelExporter:corrupted status file '".$this->GetStateFile()."', not a JSON, cannot reload state."); throw new Exception("ExcelExporter:corrupted status file '".$this->GetStateFile()."', not a JSON, cannot reload state.");
} }
@@ -132,13 +141,16 @@ class ExcelExporter
$sMessage = Dict::Format('ExcelExporter:ErrorUnexpected_State', $this->sState); $sMessage = Dict::Format('ExcelExporter:ErrorUnexpected_State', $this->sState);
$fTime = microtime(true); $fTime = microtime(true);
try { try
switch ($this->sState) { {
switch($this->sState)
{
case 'new': case 'new':
$oIDSet = new DBObjectSet($this->oSearch); $oIDSet = new DBObjectSet($this->oSearch);
$oIDSet->OptimizeColumnLoad(['id']); $oIDSet->OptimizeColumnLoad(array('id'));
$this->aObjectsIDs = []; $this->aObjectsIDs = array();
while ($oObj = $oIDSet->Fetch()) { while($oObj = $oIDSet->Fetch())
{
$this->aObjectsIDs[] = $oObj->GetKey(); $this->aObjectsIDs[] = $oObj->GetKey();
} }
$sCode = 'retrieving-data'; $sCode = 'retrieving-data';
@@ -152,7 +164,8 @@ class ExcelExporter
$this->GetFieldsList($oIDSet, $this->bAdvancedMode); $this->GetFieldsList($oIDSet, $this->bAdvancedMode);
$sRow = json_encode($this->aTableHeaders); $sRow = json_encode($this->aTableHeaders);
$hFile = @fopen($this->GetDataFile(), 'ab'); $hFile = @fopen($this->GetDataFile(), 'ab');
if ($hFile === false) { if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.'); throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
} }
fwrite($hFile, $sRow."\n"); fwrite($hFile, $sRow."\n");
@@ -168,24 +181,32 @@ class ExcelExporter
$oCurrentSearch->AddCondition('id', $aIDs, 'IN'); $oCurrentSearch->AddCondition('id', $aIDs, 'IN');
$hFile = @fopen($this->GetDataFile(), 'ab'); $hFile = @fopen($this->GetDataFile(), 'ab');
if ($hFile === false) { if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.'); throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
} }
$oSet = new DBObjectSet($oCurrentSearch); $oSet = new DBObjectSet($oCurrentSearch);
$this->GetFieldsList($oSet, $this->bAdvancedMode); $this->GetFieldsList($oSet, $this->bAdvancedMode);
while ($aObjects = $oSet->FetchAssoc()) { while($aObjects = $oSet->FetchAssoc())
$aRow = []; {
foreach ($this->aAuthorizedClasses as $sAlias => $sClassName) { $aRow = array();
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
{
$oObj = $aObjects[$sAlias]; $oObj = $aObjects[$sAlias];
if ($this->bAdvancedMode) { if ($this->bAdvancedMode)
{
$aRow[] = $oObj->GetKey(); $aRow[] = $oObj->GetKey();
} }
foreach ($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef) { foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$value = $oObj->Get($sAttCodeEx); $value = $oObj->Get($sAttCodeEx);
if ($value instanceof ormCaseLog) { if ($value instanceOf ormCaseLog)
{
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it! // Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
$sExcelVal = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText())); $sExcelVal = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
} else { }
else
{
$sExcelVal = $oAttDef->GetEditValue($value, $oObj); $sExcelVal = $oAttDef->GetEditValue($value, $oObj);
} }
$aRow[] = $sExcelVal; $aRow[] = $sExcelVal;
@@ -196,13 +217,16 @@ class ExcelExporter
} }
fclose($hFile); fclose($hFile);
if (($this->iPosition + $this->iChunkSize) > count($this->aObjectsIDs)) { if (($this->iPosition + $this->iChunkSize) > count($this->aObjectsIDs))
{
// Next state // Next state
$this->sState = 'building-excel'; $this->sState = 'building-excel';
$sCode = 'building-excel'; $sCode = 'building-excel';
$iPercentage = 80; $iPercentage = 80;
$sMessage = Dict::S('ExcelExporter:BuildingExcelFile'); $sMessage = Dict::S('ExcelExporter:BuildingExcelFile');
} else { }
else
{
$sCode = 'retrieving-data'; $sCode = 'retrieving-data';
$this->iPosition += $this->iChunkSize; $this->iPosition += $this->iChunkSize;
$iPercentage = 5 + round(75 * ($this->iPosition / count($this->aObjectsIDs))); $iPercentage = 5 + round(75 * ($this->iPosition / count($this->aObjectsIDs)));
@@ -212,14 +236,16 @@ class ExcelExporter
case 'building-excel': case 'building-excel':
$hFile = @fopen($this->GetDataFile(), 'rb'); $hFile = @fopen($this->GetDataFile(), 'rb');
if ($hFile === false) { if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for reading.'); throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for reading.');
} }
$sHeaders = fgets($hFile); $sHeaders = fgets($hFile);
$aHeaders = json_decode($sHeaders, true); $aHeaders = json_decode($sHeaders, true);
$aData = []; $aData = array();
while ($sLine = fgets($hFile)) { while($sLine = fgets($hFile))
{
$aRow = json_decode($sLine); $aRow = json_decode($sLine);
$aData[] = $aRow; $aData[] = $aRow;
} }
@@ -252,29 +278,35 @@ class ExcelExporter
$sMessage = Dict::S('ExcelExporter:Done'); $sMessage = Dict::S('ExcelExporter:Done');
break; break;
} }
} catch (Exception $e) { }
catch(Exception $e)
{
$sCode = 'error'; $sCode = 'error';
$sMessage = $e->getMessage(); $sMessage = $e->getMessage();
} }
$this->aStatistics['total_duration'] += microtime(true) - $fTime; $this->aStatistics['total_duration'] += microtime(true) - $fTime;
$peak_memory = memory_get_peak_usage(true); $peak_memory = memory_get_peak_usage(true);
if ($peak_memory > $this->aStatistics['peak_memory_usage']) { if ($peak_memory > $this->aStatistics['peak_memory_usage'])
{
$this->aStatistics['peak_memory_usage'] = $peak_memory; $this->aStatistics['peak_memory_usage'] = $peak_memory;
} }
return [ return array(
'code' => $sCode, 'code' => $sCode,
'message' => $sMessage, 'message' => $sMessage,
'percentage' => $iPercentage, 'percentage' => $iPercentage,
]; );
} }
public function GetExcelFilePath() public function GetExcelFilePath()
{ {
if ($this->sOutputFilePath == null) { if ($this->sOutputFilePath == null)
{
return utils::GetDataPath().'bulk_export/'.$this->sToken.'.xlsx'; return utils::GetDataPath().'bulk_export/'.$this->sToken.'.xlsx';
} else { }
else
{
return $this->sOutputFilePath; return $this->sOutputFilePath;
} }
} }
@@ -305,11 +337,14 @@ class ExcelExporter
$aFiles = glob(utils::GetDataPath().'bulk_export/*.*'); $aFiles = glob(utils::GetDataPath().'bulk_export/*.*');
$iDelay = MetaModel::GetConfig()->Get('xlsx_exporter_cleanup_old_files_delay'); $iDelay = MetaModel::GetConfig()->Get('xlsx_exporter_cleanup_old_files_delay');
if ($iDelay > 0) { if($iDelay > 0)
foreach ($aFiles as $sFile) { {
foreach($aFiles as $sFile)
{
$iModificationTime = filemtime($sFile); $iModificationTime = filemtime($sFile);
if ($iModificationTime < (time() - $iDelay)) { if($iModificationTime < (time() - $iDelay))
{
// Temporary files older than one day are deleted // Temporary files older than one day are deleted
//echo "Supposed to delete: '".$sFile." (Unix Modification Time: $iModificationTime)'\n"; //echo "Supposed to delete: '".$sFile." (Unix Modification Time: $iModificationTime)'\n";
@unlink($sFile); @unlink($sFile);
@@ -320,18 +355,21 @@ class ExcelExporter
public function DisplayStatistics(Page $oPage) public function DisplayStatistics(Page $oPage)
{ {
$aStats = [ $aStats = array(
'Number of objects exported' => $this->aStatistics['objects_count'], 'Number of objects exported' => $this->aStatistics['objects_count'],
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']), 'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']), 'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']), 'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']), 'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']), 'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
]; );
if ($oPage instanceof CLIPage) { if ($oPage instanceof CLIPage)
{
$oPage->add($this->GetStatistics('text')); $oPage->add($this->GetStatistics('text'));
} else { }
else
{
$oPage->add($this->GetStatistics('html')); $oPage->add($this->GetStatistics('html'));
} }
} }
@@ -339,24 +377,29 @@ class ExcelExporter
public function GetStatistics($sFormat = 'html') public function GetStatistics($sFormat = 'html')
{ {
$sStats = ''; $sStats = '';
$aStats = [ $aStats = array(
'Number of objects exported' => $this->aStatistics['objects_count'], 'Number of objects exported' => $this->aStatistics['objects_count'],
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']), 'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']), 'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']), 'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']), 'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']), 'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
]; );
if ($sFormat == 'text') { if ($sFormat == 'text')
foreach ($aStats as $sLabel => $sValue) { {
foreach($aStats as $sLabel => $sValue)
{
$sStats .= "+------------------------------+----------+\n"; $sStats .= "+------------------------------+----------+\n";
$sStats .= sprintf("|%-30s|%10s|\n", $sLabel, $sValue); $sStats .= sprintf("|%-30s|%10s|\n", $sLabel, $sValue);
} }
$sStats .= "+------------------------------+----------+"; $sStats .= "+------------------------------+----------+";
} else { }
else
{
$sStats .= '<table><tbody>'; $sStats .= '<table><tbody>';
foreach ($aStats as $sLabel => $sValue) { foreach($aStats as $sLabel => $sValue)
{
$sStats .= "<tr><td>$sLabel</td><td>$sValue</td></tr>"; $sStats .= "<tr><td>$sLabel</td><td>$sValue</td></tr>";
} }
$sStats .= '</tbody></table>'; $sStats .= '</tbody></table>';
@@ -367,24 +410,27 @@ class ExcelExporter
public static function HumanDisplay($iSize) public static function HumanDisplay($iSize)
{ {
$aUnits = ['B','KB','MB','GB','TB','PB']; $aUnits = array('B','KB','MB','GB','TB','PB');
return @round($iSize/pow(1024,($i=floor(log($iSize,1024)))),2).' '.$aUnits[$i]; return @round($iSize/pow(1024,($i=floor(log($iSize,1024)))),2).' '.$aUnits[$i];
} }
protected function CheckDataDir() protected function CheckDataDir()
{ {
if (!is_dir(utils::GetDataPath()."bulk_export")) { if(!is_dir(utils::GetDataPath()."bulk_export"))
{
@mkdir(utils::GetDataPath()."bulk_export", 0777, true /* recursive */); @mkdir(utils::GetDataPath()."bulk_export", 0777, true /* recursive */);
clearstatcache(); clearstatcache();
} }
if (!is_writable(utils::GetDataPath()."bulk_export")) { if (!is_writable(utils::GetDataPath()."bulk_export"))
{
throw new Exception('Data directory "'.utils::GetDataPath().'bulk_export" could not be written.'); throw new Exception('Data directory "'.utils::GetDataPath().'bulk_export" could not be written.');
} }
} }
protected function GetStateFile($sToken = null) protected function GetStateFile($sToken = null)
{ {
if ($sToken == null) { if ($sToken == null)
{
$sToken = $this->sToken; $sToken = $this->sToken;
} }
return utils::GetDataPath()."bulk_export/$sToken.status"; return utils::GetDataPath()."bulk_export/$sToken.status";
@@ -398,12 +444,14 @@ class ExcelExporter
protected function GetNewToken() protected function GetNewToken()
{ {
$iNum = rand(); $iNum = rand();
do { do
{
$iNum++; $iNum++;
$sToken = sprintf("%08x", $iNum); $sToken = sprintf("%08x", $iNum);
$sFileName = $this->GetStateFile($sToken); $sFileName = $this->GetStateFile($sToken);
$hFile = @fopen($sFileName, 'x'); $hFile = @fopen($sFileName, 'x');
} while ($hFile === false); }
while($hFile === false);
fclose($hFile); fclose($hFile);
return $sToken; return $sToken;
@@ -411,61 +459,82 @@ class ExcelExporter
protected function GetFieldsList($oSet, $bFieldsAdvanced = false, $bLocalize = true, $aFields = null) protected function GetFieldsList($oSet, $bFieldsAdvanced = false, $bLocalize = true, $aFields = null)
{ {
$this->aFieldsList = []; $this->aFieldsList = array();
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$aClasses = $oSet->GetFilter()->GetSelectedClasses(); $aClasses = $oSet->GetFilter()->GetSelectedClasses();
$this->aAuthorizedClasses = []; $this->aAuthorizedClasses = array();
foreach ($aClasses as $sAlias => $sClassName) { foreach($aClasses as $sAlias => $sClassName)
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) { {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
{
$this->aAuthorizedClasses[$sAlias] = $sClassName; $this->aAuthorizedClasses[$sAlias] = $sClassName;
} }
} }
$aAttribs = []; $aAttribs = array();
$this->aTableHeaders = []; $this->aTableHeaders = array();
foreach ($this->aAuthorizedClasses as $sAlias => $sClassName) { foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
$aList[$sAlias] = []; {
$aList[$sAlias] = array();
foreach (MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) { foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
if (is_null($aFields) || (count($aFields) == 0)) { {
if (is_null($aFields) || (count($aFields) == 0))
{
// Standard list of attributes (no link sets) // Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) { if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
{
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) { if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
if ($bFieldsAdvanced) { {
if ($bFieldsAdvanced)
{
$aList[$sAlias][$sAttCodeEx] = $oAttDef; $aList[$sAlias][$sAttCodeEx] = $oAttDef;
if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE))
{
$sRemoteClass = $oAttDef->GetTargetClass(); $sRemoteClass = $oAttDef->GetTargetClass();
foreach (MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) { foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
{
$this->aFieldsList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode); $this->aFieldsList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode);
} }
} }
} }
} else { }
else
{
// Any other attribute // Any other attribute
$this->aFieldsList[$sAlias][$sAttCodeEx] = $oAttDef; $this->aFieldsList[$sAlias][$sAttCodeEx] = $oAttDef;
} }
} }
} else { }
else
{
// User defined list of attributes // User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) { if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$this->aFieldsList[$sAlias][$sAttCode] = $oAttDef; $this->aFieldsList[$sAlias][$sAttCode] = $oAttDef;
} }
} }
} }
if ($bFieldsAdvanced) { if ($bFieldsAdvanced)
{
$this->aTableHeaders['id'] = '0'; $this->aTableHeaders['id'] = '0';
} }
foreach ($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef) { foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$sLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx; $sLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx;
if ($oAttDef instanceof AttributeDateTime) { if($oAttDef instanceof AttributeDateTime)
{
$this->aTableHeaders[$sLabel] = 'datetime'; $this->aTableHeaders[$sLabel] = 'datetime';
} else { }
else
{
$this->aTableHeaders[$sLabel] = 'string'; $this->aTableHeaders[$sLabel] = 'string';
} }
} }
} }
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -78,6 +77,7 @@ class CoreCannotSaveObjectException extends CoreException
return $sContent; return $sContent;
} }
public function getIssues() public function getIssues()
{ {
return $this->aIssues; return $this->aIssues;

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -36,10 +35,10 @@ class CoreException extends Exception
} }
if (count($this->m_aContextData) > 0) { if (count($this->m_aContextData) > 0) {
$sMessage .= ": "; $sMessage .= ": ";
$aContextItems = []; $aContextItems = array();
foreach ($this->m_aContextData as $sKey => $value) { foreach ($this->m_aContextData as $sKey => $value) {
if (is_array($value)) { if (is_array($value)) {
$aPairs = []; $aPairs = array();
foreach ($value as $key => $val) { foreach ($value as $key => $val) {
if (is_array($val)) { if (is_array($val)) {
$aPairs[] = $key.'=>('.implode(', ', $val).')'; $aPairs[] = $key.'=>('.implode(', ', $val).')';

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -10,4 +9,5 @@
*/ */
class CorePortalInvalidActionRuleException extends CoreException class CorePortalInvalidActionRuleException extends CoreException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,10 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ForgotPasswordApplicationException extends Exception
{
}

View File

@@ -1,10 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ForgotPasswordUserInputException extends Exception
{
}

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -9,7 +8,7 @@ class DictExceptionMissingString extends DictException
{ {
public function __construct($sLanguageCode, $sStringCode) public function __construct($sLanguageCode, $sStringCode)
{ {
$aContext = []; $aContext = array();
$aContext['language_code'] = $sLanguageCode; $aContext['language_code'] = $sLanguageCode;
$aContext['string_code'] = $sStringCode; $aContext['string_code'] = $sStringCode;
parent::__construct('Missing localized string', $aContext); parent::__construct('Missing localized string', $aContext);

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -9,7 +8,7 @@ class DictExceptionUnknownLanguage extends DictException
{ {
public function __construct($sLanguageCode) public function __construct($sLanguageCode)
{ {
$aContext = []; $aContext = array();
$aContext['language_code'] = $sLanguageCode; $aContext['language_code'] = $sLanguageCode;
parent::__construct('Unknown localization language', $aContext); parent::__construct('Unknown localization language', $aContext);
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -7,4 +6,5 @@
class iTopXmlException extends CoreException class iTopXmlException extends CoreException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -20,10 +19,10 @@ class MySQLHasGoneAwayException extends MySQLException
*/ */
public static function getErrorCodes() public static function getErrorCodes()
{ {
return [ return array(
2006, 2006,
2013, 2013,
]; );
} }
public function __construct($sIssue, $aContext) public function __construct($sIssue, $aContext)

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -10,4 +9,5 @@
*/ */
class MySQLNoTransactionException extends MySQLException class MySQLNoTransactionException extends MySQLException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -12,4 +11,5 @@
*/ */
class MySQLQueryHasNoResultException extends MySQLException class MySQLQueryHasNoResultException extends MySQLException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -10,4 +9,5 @@
*/ */
class MySQLTransactionNotClosedException extends MySQLException class MySQLTransactionNotClosedException extends MySQLException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -10,4 +9,5 @@
*/ */
class CoreOqlException extends CoreException class CoreOqlException extends CoreException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -10,4 +9,5 @@
*/ */
class CoreOqlMultipleResultsForbiddenException extends CoreOqlException class CoreOqlMultipleResultsForbiddenException extends CoreOqlException
{ {
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* Copyright (C) 2013-2024 Combodo SAS * Copyright (C) 2013-2024 Combodo SAS
* *
@@ -23,8 +22,8 @@
* @author Olivier DAIN <olivier.dain@combodo.com> * @author Olivier DAIN <olivier.dain@combodo.com>
* @since 3.0.0 N°3588 * @since 3.0.0 N°3588
*/ */
class FindStylesheetObject class FindStylesheetObject{
{
//file URIs //file URIs
private $aStylesheetFileURIs; private $aStylesheetFileURIs;
@@ -93,8 +92,7 @@ class FindStylesheetObject
$this->sLastStyleSheetPath = $sStylesheetFilePath; $this->sLastStyleSheetPath = $sStylesheetFilePath;
} }
public function AlreadyFetched(string $sStylesheetFilePath): bool public function AlreadyFetched(string $sStylesheetFilePath) : bool {
{
return in_array($sStylesheetFilePath, $this->aAllStylesheetFilePaths); return in_array($sStylesheetFilePath, $this->aAllStylesheetFilePaths);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
/** /**
* Persistent class InputOutputTask * Persistent class InputOutputTask
* *
@@ -34,37 +34,36 @@ class InputOutputTask extends cmdbAbstractObject
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "application", "category" => "application",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_iotask", "db_table" => "priv_iotask",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", ["allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("category", ["allowed_values" => new ValueSetEnum('Input, Ouput'), "sql" => "category", "default_value" => "Input", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeEnum("category", array("allowed_values" => new ValueSetEnum('Input, Ouput'), "sql" => "category", "default_value" => "Input", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("source_type", ["allowed_values" => new ValueSetEnum('File, Database, Web Service'), "sql" => "source_type", "default_value" => "File", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("allowed_values" => new ValueSetEnum('File, Database, Web Service'), "sql" => "source_type", "default_value" => "File", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum( MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype",
"source_subtype", array("allowed_values" => new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql" => "source_subtype", "default_value" => "CSV", "is_null_allowed" => false, "depends_on" => array())));
["allowed_values" => new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql" => "source_subtype", "default_value" => "CSV", "is_null_allowed" => false, "depends_on" => []] MetaModel::Init_AddAttribute(new AttributeString("source_path", array("allowed_values" => null, "sql" => "source_path", "default_value" => "", "is_null_allowed" => false, "depends_on" => array())));
)); MetaModel::Init_AddAttribute(new AttributeClass("objects_class", array("class_category" => "", "more_values" => "", "sql" => "objects_class", "default_value" => null, "is_null_allowed" => true, "depends_on" => array(), "class_exclusion_list" => null)));
MetaModel::Init_AddAttribute(new AttributeString("source_path", ["allowed_values" => null, "sql" => "source_path", "default_value" => "", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("allowed_values" => new ValueSetEnum('Yes,No'), "sql" => "test_mode", "default_value" => 'No', "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeClass("objects_class", ["class_category" => "", "more_values" => "", "sql" => "objects_class", "default_value" => null, "is_null_allowed" => true, "depends_on" => [], "class_exclusion_list" => null])); MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("allowed_values" => new ValueSetEnum('Yes,No'), "sql" => "verbose_mode", "default_value" => 'No', "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", ["allowed_values" => new ValueSetEnum('Yes,No'), "sql" => "test_mode", "default_value" => 'No', "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeEnum("options", array("allowed_values" => new ValueSetEnum('Full, Update Only, Creation Only'), "sql" => "options", "default_value" => 'Full', "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", ["allowed_values" => new ValueSetEnum('Yes,No'), "sql" => "verbose_mode", "default_value" => 'No', "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeEnum("options", ["allowed_values" => new ValueSetEnum('Full, Update Only, Creation Only'), "sql" => "options", "default_value" => 'Full', "is_null_allowed" => true, "depends_on" => []]));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype', 'source_path', 'options', 'test_mode', 'verbose_mode']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype', 'source_path', 'options', 'test_mode', 'verbose_mode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['description', 'category', 'objects_class', 'source_type', 'source_subtype', 'options']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('description', 'category', 'objects_class', 'source_type', 'source_subtype', 'options')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['name', 'category', 'objects_class', 'source_type', 'source_subtype']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('name', 'category', 'objects_class', 'source_type', 'source_subtype')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', ['name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype']); // Criteria of the advanced search form MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype')); // Criteria of the advanced search form
} }
} }
?>

View File

@@ -18,17 +18,23 @@ class LoginBasic extends AbstractLoginFSMExtension
*/ */
public function ListSupportedLoginModes() public function ListSupportedLoginModes()
{ {
return ['basic']; return array('basic');
} }
protected function OnModeDetection(&$iErrorCode) protected function OnModeDetection(&$iErrorCode)
{ {
if (!Session::IsSet('login_mode')) { if (!Session::IsSet('login_mode'))
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { {
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
{
Session::Set('login_mode', 'basic'); Session::Set('login_mode', 'basic');
} elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { }
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
{
Session::Set('login_mode', 'basic'); Session::Set('login_mode', 'basic');
} elseif (isset($_SERVER['PHP_AUTH_USER'])) { }
elseif (isset($_SERVER['PHP_AUTH_USER']))
{
Session::Set('login_mode', 'basic'); Session::Set('login_mode', 'basic');
} }
} }
@@ -37,18 +43,22 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode) protected function OnReadCredentials(&$iErrorCode)
{ {
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'basic') { if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'basic')
{
list($sAuthUser) = $this->GetAuthUserAndPassword(); list($sAuthUser) = $this->GetAuthUserAndPassword();
Session::Set('login_temp_auth_user', $sAuthUser); Session::Set('login_temp_auth_user', $sAuthUser);
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
} }
protected function OnCheckCredentials(&$iErrorCode) protected function OnCheckCredentials(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'basic') { if (Session::Get('login_mode') == 'basic')
{
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword(); list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal')) { if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR; return LoginWebPage::LOGIN_FSM_ERROR;
} }
@@ -59,7 +69,8 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode) protected function OnCredentialsOK(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'basic') { if (Session::Get('login_mode') == 'basic')
{
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode')); LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -67,9 +78,11 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode) protected function OnError(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'basic') { if (Session::Get('login_mode') == 'basic')
{
$iOnExit = LoginWebPage::getIOnExit(); $iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN) { if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
} }
LoginWebPage::HTTP401Error(); LoginWebPage::HTTP401Error();
@@ -79,7 +92,8 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode) protected function OnConnected(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'basic') { if (Session::Get('login_mode') == 'basic')
{
Session::Set('can_logoff', true); Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode); return LoginWebPage::CheckLoggedUser($iErrorCode);
} }
@@ -91,33 +105,42 @@ class LoginBasic extends AbstractLoginFSMExtension
$sAuthUser = ''; $sAuthUser = '';
$sAuthPwd = null; $sAuthPwd = null;
$sAuthorization = ''; $sAuthorization = '';
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
{
$sAuthorization = $_SERVER['HTTP_AUTHORIZATION']; $sAuthorization = $_SERVER['HTTP_AUTHORIZATION'];
} elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { }
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
{
$sAuthorization = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; $sAuthorization = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
} }
if (!empty($sAuthorization)) { if (!empty($sAuthorization))
{
list($sAuthUser, $sAuthPwd) = explode(':', base64_decode(substr($sAuthorization, 6))); list($sAuthUser, $sAuthPwd) = explode(':', base64_decode(substr($sAuthorization, 6)));
} else { }
if (isset($_SERVER['PHP_AUTH_USER'])) { else
{
if (isset($_SERVER['PHP_AUTH_USER']))
{
$sAuthUser = $_SERVER['PHP_AUTH_USER']; $sAuthUser = $_SERVER['PHP_AUTH_USER'];
// Unfortunately, the RFC is not clear about the encoding... // Unfortunately, the RFC is not clear about the encoding...
// IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8 // IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8
// So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base // So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base
if (!LoginWebPage::LooksLikeUTF8($sAuthUser)) { if (!LoginWebPage::LooksLikeUTF8($sAuthUser))
{
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
// Supposed to be harmless in case of a plain ASCII string... // Supposed to be harmless in case of a plain ASCII string...
$sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser); $sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser);
} }
$sAuthPwd = $_SERVER['PHP_AUTH_PW']; $sAuthPwd = $_SERVER['PHP_AUTH_PW'];
if (!LoginWebPage::LooksLikeUTF8($sAuthPwd)) { if (!LoginWebPage::LooksLikeUTF8($sAuthPwd))
{
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
// Supposed to be harmless in case of a plain ASCII string... // Supposed to be harmless in case of a plain ASCII string...
$sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd); $sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd);
} }
} }
} }
return [$sAuthUser, $sAuthPwd]; return array($sAuthUser, $sAuthPwd);
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -19,7 +18,7 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
*/ */
public function ListSupportedLoginModes() public function ListSupportedLoginModes()
{ {
return ['before']; return array('before');
} }
protected function OnStart(&$iErrorCode) protected function OnStart(&$iErrorCode)
@@ -32,10 +31,13 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
$sProposedLoginMode = utils::ReadParam('login_mode', ''); $sProposedLoginMode = utils::ReadParam('login_mode', '');
$index = array_search($sProposedLoginMode, $aAllowedLoginTypes); $index = array_search($sProposedLoginMode, $aAllowedLoginTypes);
if ($index !== false) { if ($index !== false)
{
// Force login mode // Force login mode
Session::Set('login_mode', $sProposedLoginMode); Session::Set('login_mode', $sProposedLoginMode);
} else { }
else
{
Session::Unset('login_mode'); Session::Unset('login_mode');
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -47,7 +49,8 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
$sProposedLoginMode = utils::ReadParam('login_mode', ''); $sProposedLoginMode = utils::ReadParam('login_mode', '');
$index = array_search($sProposedLoginMode, $aAllowedLoginTypes); $index = array_search($sProposedLoginMode, $aAllowedLoginTypes);
if ($index !== false) { if ($index !== false)
{
// Force login mode // Force login mode
LoginWebPage::SetLoginModeAndReload($sProposedLoginMode); LoginWebPage::SetLoginModeAndReload($sProposedLoginMode);
} else { } else {
@@ -66,6 +69,8 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
*/ */
class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExtension class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExtension
{ {
/** /**
* Must be executed after the other login plugins * Must be executed after the other login plugins
* *
@@ -73,16 +78,19 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
*/ */
public function ListSupportedLoginModes() public function ListSupportedLoginModes()
{ {
return ['after']; return array('after');
} }
protected function OnError(&$iErrorCode) protected function OnError(&$iErrorCode)
{ {
self::ResetLoginSession(); self::ResetLoginSession();
$iOnExit = LoginWebPage::getIOnExit(); $iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN) { if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
} elseif ($iOnExit == LoginWebPage::EXIT_HTTP_401) { }
elseif ($iOnExit == LoginWebPage::EXIT_HTTP_401)
{
LoginWebPage::HTTP401Error(); // Error, exit LoginWebPage::HTTP401Error(); // Error, exit
} }
// LoginWebPage::EXIT_PROMPT // LoginWebPage::EXIT_PROMPT
@@ -91,7 +99,8 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnCredentialsOk(&$iErrorCode) protected function OnCredentialsOk(&$iErrorCode)
{ {
if (!Session::IsSet('login_mode')) { if (!Session::IsSet('login_mode'))
{
// N°6358 - if EXIT_RETURN was asked, send an error // N°6358 - if EXIT_RETURN was asked, send an error
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) { if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
@@ -128,8 +137,10 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
private static function ResetLoginSession() private static function ResetLoginSession()
{ {
LoginWebPage::ResetSession(); LoginWebPage::ResetSession();
foreach (Session::ListVariables() as $sKey) { foreach (Session::ListVariables() as $sKey)
if (utils::StartsWith($sKey, 'login_')) { {
if (utils::StartsWith($sKey, 'login_'))
{
Session::Unset($sKey); Session::Unset($sKey);
} }
} }

View File

@@ -11,6 +11,7 @@ use Combodo\iTop\Application\Helper\Session;
class LoginExternal extends AbstractLoginFSMExtension class LoginExternal extends AbstractLoginFSMExtension
{ {
/** /**
* Return the list of supported login modes for this plugin * Return the list of supported login modes for this plugin
* *
@@ -18,14 +19,16 @@ class LoginExternal extends AbstractLoginFSMExtension
*/ */
public function ListSupportedLoginModes() public function ListSupportedLoginModes()
{ {
return ['external']; return array('external');
} }
protected function OnModeDetection(&$iErrorCode) protected function OnModeDetection(&$iErrorCode)
{ {
if (!Session::IsSet('login_mode')) { if (!Session::IsSet('login_mode'))
{
$sAuthUser = $this->GetAuthUser(); $sAuthUser = $this->GetAuthUser();
if ($sAuthUser && (strlen($sAuthUser) > 0)) { if ($sAuthUser && (strlen($sAuthUser) > 0))
{
Session::Set('login_mode', 'external'); Session::Set('login_mode', 'external');
} }
} }
@@ -34,9 +37,11 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode) protected function OnCheckCredentials(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'external') { if (Session::Get('login_mode') == 'external')
{
$sAuthUser = $this->GetAuthUser(); $sAuthUser = $this->GetAuthUser();
if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external')) { if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR; return LoginWebPage::LOGIN_FSM_ERROR;
} }
@@ -47,7 +52,8 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode) protected function OnCredentialsOK(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'external') { if (Session::Get('login_mode') == 'external')
{
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'external', Session::Get('login_mode')); LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'external', Session::Get('login_mode'));
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -55,7 +61,8 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode) protected function OnConnected(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'external') { if (Session::Get('login_mode') == 'external')
{
Session::Set('can_logoff', false); Session::Set('can_logoff', false);
return LoginWebPage::CheckLoggedUser($iErrorCode); return LoginWebPage::CheckLoggedUser($iErrorCode);
} }
@@ -64,9 +71,11 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode) protected function OnError(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'external') { if (Session::Get('login_mode') == 'external')
{
$iOnExit = LoginWebPage::getIOnExit(); $iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN) { if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
} }
LoginWebPage::HTTP401Error(); LoginWebPage::HTTP401Error();
@@ -75,10 +84,13 @@ class LoginExternal extends AbstractLoginFSMExtension
} }
/** /**
* @return bool|mixed * @return bool
*/ */
private function GetAuthUser() private function GetAuthUser()
{ {
return MetaModel::GetConfig()->GetExternalAuthenticationVariable(); $sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
/** @var string $sAuthUser */
return $sAuthUser; // Retrieve the value
} }
} }

View File

@@ -23,7 +23,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/ */
public function ListSupportedLoginModes() public function ListSupportedLoginModes()
{ {
return ['form']; return array('form');
} }
/** /**
@@ -34,8 +34,10 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') { if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') {
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data'); $sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data'); $sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd)) { if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd))
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER)) { {
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
{
// X-Combodo-Ajax is a special header automatically added to all ajax requests // X-Combodo-Ajax is a special header automatically added to all ajax requests
// Let's reply that we're currently logged-out // Let's reply that we're currently logged-out
header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.0 401 Unauthorized');
@@ -64,10 +66,12 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/ */
protected function OnCheckCredentials(&$iErrorCode) protected function OnCheckCredentials(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'form') { if (Session::Get('login_mode') == 'form')
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data'); $sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data'); $sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal')) { if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR; return LoginWebPage::LOGIN_FSM_ERROR;
} }
@@ -81,7 +85,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/ */
protected function OnCredentialsOK(&$iErrorCode) protected function OnCredentialsOK(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'form') { if (Session::Get('login_mode') == 'form')
{
// Store 'auth_user' in session for further use // Store 'auth_user' in session for further use
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode')); LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
} }
@@ -93,7 +98,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/ */
protected function OnError(&$iErrorCode) protected function OnError(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'form') { if (Session::Get('login_mode') == 'form')
{
$this->bForceFormOnError = true; $this->bForceFormOnError = true;
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -104,7 +110,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/ */
protected function OnConnected(&$iErrorCode) protected function OnConnected(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'form') { if (Session::Get('login_mode') == 'form')
{
Session::Set('can_logoff', true); Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode); return LoginWebPage::CheckLoggedUser($iErrorCode);
} }
@@ -124,23 +131,24 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data'); $sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data'); $sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data');
$aData = [ $aData = array(
'sAuthUser' => $sAuthUser, 'sAuthUser' => $sAuthUser,
'sAuthPwd' => $sAuthPwd, 'sAuthPwd' => $sAuthPwd,
]; );
$oLoginContext->AddBlockExtension('login_input', new LoginBlockExtension('extensionblock/loginforminput.html.twig', $aData)); $oLoginContext->AddBlockExtension('login_input', new LoginBlockExtension('extensionblock/loginforminput.html.twig', $aData));
$oLoginContext->AddBlockExtension('login_submit', new LoginBlockExtension('extensionblock/loginformsubmit.html.twig')); $oLoginContext->AddBlockExtension('login_submit', new LoginBlockExtension('extensionblock/loginformsubmit.html.twig'));
$oLoginContext->AddBlockExtension('login_form_footer', new LoginBlockExtension('extensionblock/loginformfooter.html.twig')); $oLoginContext->AddBlockExtension('login_form_footer', new LoginBlockExtension('extensionblock/loginformfooter.html.twig'));
$bEnableResetPassword = MetaModel::GetConfig()->Get('forgot_password'); $bEnableResetPassword = MetaModel::GetConfig()->Get('forgot_password');
$sResetPasswordUrl = MetaModel::GetConfig()->Get('forgot_password.url'); $sResetPasswordUrl = MetaModel::GetConfig()->Get('forgot_password.url');
if ($sResetPasswordUrl == '') { if ($sResetPasswordUrl == '')
{
$sResetPasswordUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd'; $sResetPasswordUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd';
} }
$aData = [ $aData = array(
'bEnableResetPassword' => $bEnableResetPassword, 'bEnableResetPassword' => $bEnableResetPassword,
'sResetPasswordUrl' => $sResetPasswordUrl, 'sResetPasswordUrl' => $sResetPasswordUrl,
]; );
$oLoginContext->AddBlockExtension('login_links', new LoginBlockExtension('extensionblock/loginformlinks.html.twig', $aData)); $oLoginContext->AddBlockExtension('login_links', new LoginBlockExtension('extensionblock/loginformlinks.html.twig', $aData));
return $oLoginContext; return $oLoginContext;

View File

@@ -6,6 +6,7 @@
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
*/ */
use Combodo\iTop\Application\Branding; use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\TwigBase\Twig\Extension; use Combodo\iTop\Application\TwigBase\Twig\Extension;
use Combodo\iTop\Application\WebPage\NiceWebPage; use Combodo\iTop\Application\WebPage\NiceWebPage;
@@ -40,11 +41,11 @@ class LoginTwigContext
*/ */
public function __construct() public function __construct()
{ {
$this->aBlockExtension = []; $this->aBlockExtension = array();
$this->aPostedVars = []; $this->aPostedVars = array();
$this->sTwigLoaderPath = null; $this->sTwigLoaderPath = null;
$this->aCSSFiles = []; $this->aCSSFiles = array();
$this->aJsFiles = []; $this->aJsFiles = array();
$this->sTwigNameSpace = null; $this->sTwigNameSpace = null;
} }
@@ -178,7 +179,7 @@ class LoginBlockExtension
* @param array $aData Data given to the twig template (into the variable {{ aData }}) * @param array $aData Data given to the twig template (into the variable {{ aData }})
* @api * @api
*/ */
public function __construct($sTwig, $aData = []) public function __construct($sTwig, $aData = array())
{ {
$this->sTwig = $sTwig; $this->sTwig = $sTwig;
$this->aData = $aData; $this->aData = $aData;
@@ -209,18 +210,21 @@ class LoginTwigRenderer
public function __construct() public function __construct()
{ {
$this->aLoginPluginList = LoginWebPage::GetLoginPluginList('iLoginUIExtension', false); $this->aLoginPluginList = LoginWebPage::GetLoginPluginList('iLoginUIExtension', false);
$this->aPluginFormData = []; $this->aPluginFormData = array();
$aTwigLoaders = []; $aTwigLoaders = array();
$this->aPostedVars = []; $this->aPostedVars = array();
foreach ($this->aLoginPluginList as $oLoginPlugin) { foreach ($this->aLoginPluginList as $oLoginPlugin)
{
/** @var \iLoginUIExtension $oLoginPlugin */ /** @var \iLoginUIExtension $oLoginPlugin */
$oLoginContext = $oLoginPlugin->GetTwigContext(); $oLoginContext = $oLoginPlugin->GetTwigContext();
if (is_null($oLoginContext)) { if (is_null($oLoginContext))
{
continue; continue;
} }
$this->aPluginFormData[] = $oLoginContext; $this->aPluginFormData[] = $oLoginContext;
$sTwigLoaderPath = $oLoginContext->GetTwigLoaderPath(); $sTwigLoaderPath = $oLoginContext->GetTwigLoaderPath();
if ($sTwigLoaderPath != null) { if ($sTwigLoaderPath != null)
{
$oExtensionLoader = new FilesystemLoader(); $oExtensionLoader = new FilesystemLoader();
$oExtensionLoader->setPaths($sTwigLoaderPath); $oExtensionLoader->setPaths($sTwigLoaderPath);
$aTwigLoaders[] = $oExtensionLoader; $aTwigLoaders[] = $oExtensionLoader;
@@ -228,8 +232,8 @@ class LoginTwigRenderer
$this->aPostedVars = array_merge($this->aPostedVars, $oLoginContext->GetPostedVars()); $this->aPostedVars = array_merge($this->aPostedVars, $oLoginContext->GetPostedVars());
} }
$oCoreLoader = new FilesystemLoader([], APPROOT.'templates'); $oCoreLoader = new FilesystemLoader(array(), APPROOT.'templates');
$aCoreTemplatesPaths = ['pages/login', 'pages/login/password']; $aCoreTemplatesPaths = array('pages/login', 'pages/login/password');
// Having this path declared after the plugins let the plugins replace the core templates // Having this path declared after the plugins let the plugins replace the core templates
$oCoreLoader->setPaths($aCoreTemplatesPaths); $oCoreLoader->setPaths($aCoreTemplatesPaths);
// Having the core templates accessible within a different namespace offer the possibility to extend them while replacing them // Having the core templates accessible within a different namespace offer the possibility to extend them while replacing them
@@ -243,20 +247,23 @@ class LoginTwigRenderer
public function GetDefaultVars() public function GetDefaultVars()
{ {
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url'); $sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl(); $sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$aVars = [ $aVars = array(
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(), 'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),
'aPluginFormData' => $this->GetPluginFormData(), 'aPluginFormData' => $this->GetPluginFormData(),
'sItopVersion' => ITOP_VERSION,
'sVersionShort' => $sVersionShort,
'sIconUrl' => $sIconUrl, 'sIconUrl' => $sIconUrl,
'sDisplayIcon' => $sDisplayIcon, 'sDisplayIcon' => $sDisplayIcon,
]; );
return $aVars; return $aVars;
} }
public function Render(NiceWebPage $oPage, $sTwigFile, $aVars = []) public function Render(NiceWebPage $oPage, $sTwigFile, $aVars = array())
{ {
$oTemplate = $this->GetTwig()->load($sTwigFile); $oTemplate = $this->GetTwig()->load($sTwigFile);
$oPage->add($oTemplate->renderBlock('body', $aVars)); $oPage->add($oTemplate->renderBlock('body', $aVars));
@@ -265,14 +272,17 @@ class LoginTwigRenderer
$oPage->add_style($oTemplate->renderBlock('css', $aVars)); $oPage->add_style($oTemplate->renderBlock('css', $aVars));
// Render CSS links // Render CSS links
foreach ($this->aPluginFormData as $oFormData) { foreach ($this->aPluginFormData as $oFormData)
{
/** @var \LoginTwigContext $oFormData */ /** @var \LoginTwigContext $oFormData */
$aCSSFiles = $oFormData->GetCSSFiles(); $aCSSFiles = $oFormData->GetCSSFiles();
foreach ($aCSSFiles as $sCSSFile) { foreach ($aCSSFiles as $sCSSFile)
{
$oPage->LinkStylesheetFromURI($sCSSFile); $oPage->LinkStylesheetFromURI($sCSSFile);
} }
$aJsFiles = $oFormData->GetJsFiles(); $aJsFiles = $oFormData->GetJsFiles();
foreach ($aJsFiles as $sJsFile) { foreach ($aJsFiles as $sJsFile)
{
$oPage->LinkScriptFromURI($sJsFile); $oPage->LinkScriptFromURI($sJsFile);
} }

View File

@@ -23,15 +23,17 @@ class LoginURL extends AbstractLoginFSMExtension
*/ */
public function ListSupportedLoginModes() public function ListSupportedLoginModes()
{ {
return ['url']; return array('url');
} }
protected function OnModeDetection(&$iErrorCode) protected function OnModeDetection(&$iErrorCode)
{ {
if (!Session::IsSet('login_mode') && !$this->bErrorOccurred) { if (!Session::IsSet('login_mode') && !$this->bErrorOccurred)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data'); $sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!empty($sAuthUser) && !empty($sAuthPwd)) { if (!empty($sAuthUser) && !empty($sAuthPwd))
{
Session::Set('login_mode', 'url'); Session::Set('login_mode', 'url');
} }
} }
@@ -40,7 +42,8 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode) protected function OnReadCredentials(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'url') { if (Session::Get('login_mode') == 'url')
{
Session::Set('login_temp_auth_user', utils::ReadParam('auth_user', '', false, 'raw_data')); Session::Set('login_temp_auth_user', utils::ReadParam('auth_user', '', false, 'raw_data'));
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -48,10 +51,12 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode) protected function OnCheckCredentials(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'url') { if (Session::Get('login_mode') == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data'); $sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal')) { if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR; return LoginWebPage::LOGIN_FSM_ERROR;
} }
@@ -62,7 +67,8 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode) protected function OnCredentialsOK(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'url') { if (Session::Get('login_mode') == 'url')
{
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode')); LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -70,7 +76,8 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode) protected function OnError(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'url') { if (Session::Get('login_mode') == 'url')
{
$this->bErrorOccurred = true; $this->bErrorOccurred = true;
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -78,7 +85,8 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode) protected function OnConnected(&$iErrorCode)
{ {
if (Session::Get('login_mode') == 'url') { if (Session::Get('login_mode') == 'url')
{
Session::Set('can_logoff', true); Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode); return LoginWebPage::CheckLoggedUser($iErrorCode);
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
/** /**
* Class LoginWebPage * Class LoginWebPage
* *
@@ -37,33 +37,33 @@ use Combodo\iTop\Service\Events\EventService;
class LoginWebPage extends NiceWebPage class LoginWebPage extends NiceWebPage
{ {
public const EXIT_PROMPT = 0; const EXIT_PROMPT = 0;
public const EXIT_HTTP_401 = 1; const EXIT_HTTP_401 = 1;
public const EXIT_RETURN = 2; const EXIT_RETURN = 2;
public const EXIT_CODE_OK = 0; const EXIT_CODE_OK = 0;
public const EXIT_CODE_MISSINGLOGIN = 1; const EXIT_CODE_MISSINGLOGIN = 1;
public const EXIT_CODE_MISSINGPASSWORD = 2; const EXIT_CODE_MISSINGPASSWORD = 2;
public const EXIT_CODE_WRONGCREDENTIALS = 3; const EXIT_CODE_WRONGCREDENTIALS = 3;
public const EXIT_CODE_MUSTBEADMIN = 4; const EXIT_CODE_MUSTBEADMIN = 4;
public const EXIT_CODE_PORTALUSERNOTAUTHORIZED = 5; const EXIT_CODE_PORTALUSERNOTAUTHORIZED = 5;
public const EXIT_CODE_NOTAUTHORIZED = 6; const EXIT_CODE_NOTAUTHORIZED = 6;
// Login FSM States // Login FSM States
public const LOGIN_STATE_START = 'start'; // Entry state const LOGIN_STATE_START = 'start'; // Entry state
public const LOGIN_STATE_MODE_DETECTION = 'login mode detection'; // Detect which login plugin to use const LOGIN_STATE_MODE_DETECTION = 'login mode detection'; // Detect which login plugin to use
public const LOGIN_STATE_READ_CREDENTIALS = 'read credentials'; // Read the credentials const LOGIN_STATE_READ_CREDENTIALS = 'read credentials'; // Read the credentials
public const LOGIN_STATE_CHECK_CREDENTIALS = 'check credentials'; // Check if the credentials are valid const LOGIN_STATE_CHECK_CREDENTIALS = 'check credentials'; // Check if the credentials are valid
public const LOGIN_STATE_CREDENTIALS_OK = 'credentials ok'; // User provisioning const LOGIN_STATE_CREDENTIALS_OK = 'credentials ok'; // User provisioning
public const LOGIN_STATE_USER_OK = 'user ok'; // Additional check (2FA) const LOGIN_STATE_USER_OK = 'user ok'; // Additional check (2FA)
public const LOGIN_STATE_CONNECTED = 'connected'; // User connected const LOGIN_STATE_CONNECTED = 'connected'; // User connected
public const LOGIN_STATE_SET_ERROR = 'prepare for error'; // Internal state to trigger ERROR state const LOGIN_STATE_SET_ERROR = 'prepare for error'; // Internal state to trigger ERROR state
public const LOGIN_STATE_ERROR = 'error'; // An error occurred, next state will be NONE const LOGIN_STATE_ERROR = 'error'; // An error occurred, next state will be NONE
// Login FSM Returns // Login FSM Returns
public const LOGIN_FSM_RETURN = 0; // End the FSM OK (connected) const LOGIN_FSM_RETURN = 0; // End the FSM OK (connected)
public const LOGIN_FSM_ERROR = 1; // Error signaled const LOGIN_FSM_ERROR = 1; // Error signaled
public const LOGIN_FSM_CONTINUE = 2; // Continue FSM const LOGIN_FSM_CONTINUE = 2; // Continue FSM
protected static $sHandlerClass = __class__; protected static $sHandlerClass = __class__;
private static $iOnExit; private static $iOnExit;
@@ -78,7 +78,7 @@ class LoginWebPage extends NiceWebPage
*/ */
public static function NewLoginWebPage() public static function NewLoginWebPage()
{ {
return new self::$sHandlerClass(); return new self::$sHandlerClass;
} }
protected static $m_sLoginFailedMessage = ''; protected static $m_sLoginFailedMessage = '';
@@ -128,18 +128,23 @@ class LoginWebPage extends NiceWebPage
$oProfilesSet = $oUser->Get('profile_list'); $oProfilesSet = $oUser->Get('profile_list');
//delete old profiles //delete old profiles
$aExistingProfiles = []; $aExistingProfiles = [];
while ($oProfile = $oProfilesSet->Fetch()) { while ($oProfile = $oProfilesSet->Fetch())
{
array_push($aExistingProfiles, $oProfile->Get('profileid')); array_push($aExistingProfiles, $oProfile->Get('profileid'));
$iArrayKey = array_search($oProfile->Get('profileid'), $aProfiles); $iArrayKey = array_search($oProfile->Get('profileid'), $aProfiles);
if (!$iArrayKey) { if (!$iArrayKey)
{
$oProfilesSet->RemoveItem($oProfile->Get('profileid')); $oProfilesSet->RemoveItem($oProfile->Get('profileid'));
} else { }
else
{
unset($aProfiles[$iArrayKey]); unset($aProfiles[$iArrayKey]);
} }
} }
//add profiles not already linked with user //add profiles not already linked with user
foreach ($aProfiles as $iProfileId) { foreach ($aProfiles as $iProfileId)
$oProfilesSet->AddItem(MetaModel::NewObject('URP_UserProfile', ['profileid' => $iProfileId, 'reason' => $sOrigin])); {
$oProfilesSet->AddItem(MetaModel::NewObject('URP_UserProfile', array('profileid' => $iProfileId, 'reason' => $sOrigin)));
} }
$oUser->Set('profile_list', $oProfilesSet); $oUser->Set('profile_list', $oProfilesSet);
} }
@@ -149,49 +154,56 @@ class LoginWebPage extends NiceWebPage
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION); $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url'); $sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl(); $sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities( $this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES,
$sIconUrl, self::PAGES_CHARSET)."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
ENT_QUOTES,
self::PAGES_CHARSET
)."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
} }
public function DisplayLoginForm($bFailedLogin = false) public function DisplayLoginForm($bFailedLogin = false)
{ {
$oTwigContext = new LoginTwigRenderer(); $oTwigContext = new LoginTwigRenderer();
$aPostedVars = array_merge(['login_mode', 'loginop'], $oTwigContext->GetPostedVars()); $aPostedVars = array_merge(array('login_mode', 'loginop'), $oTwigContext->GetPostedVars());
$sMessage = Dict::S('UI:Login:IdentifyYourself'); $sMessage = Dict::S('UI:Login:IdentifyYourself');
// Error message // Error message
if ($bFailedLogin) { if ($bFailedLogin)
if (self::$m_sLoginFailedMessage != '') { {
if (self::$m_sLoginFailedMessage != '')
{
$sMessage = self::$m_sLoginFailedMessage; $sMessage = self::$m_sLoginFailedMessage;
} else { }
else
{
$sMessage = Dict::S('UI:Login:IncorrectLoginPassword'); $sMessage = Dict::S('UI:Login:IncorrectLoginPassword');
} }
} }
// Keep the OTHER parameters posted // Keep the OTHER parameters posted
$aPreviousPostedVars = []; $aPreviousPostedVars = array();
foreach ($_POST as $sPostedKey => $postedValue) { foreach($_POST as $sPostedKey => $postedValue)
if (!in_array($sPostedKey, $aPostedVars)) { {
if (is_array($postedValue)) { if (!in_array($sPostedKey, $aPostedVars))
foreach ($postedValue as $sKey => $sValue) { {
if (is_array($postedValue))
{
foreach($postedValue as $sKey => $sValue)
{
$sName = "{$sPostedKey}[{$sKey}]"; $sName = "{$sPostedKey}[{$sKey}]";
$aPreviousPostedVars[$sName] = $sValue; $aPreviousPostedVars[$sName] = $sValue;
} }
} else { }
else
{
$aPreviousPostedVars[$sPostedKey] = $postedValue; $aPreviousPostedVars[$sPostedKey] = $postedValue;
} }
} }
} }
$aVars = [ $aVars = array(
'bFailedLogin' => $bFailedLogin, 'bFailedLogin' => $bFailedLogin,
'sMessage' => $sMessage, 'sMessage' => $sMessage,
'aPreviousPostedVars' => $aPreviousPostedVars, 'aPreviousPostedVars' => $aPreviousPostedVars,
]; );
$aVars = array_merge($aVars, $oTwigContext->GetDefaultVars()); $aVars = array_merge($aVars, $oTwigContext->GetDefaultVars());
$oTwigContext->Render($this, 'login.html.twig', $aVars); $oTwigContext->Render($this, 'login.html.twig', $aVars);
@@ -214,22 +226,27 @@ class LoginWebPage extends NiceWebPage
{ {
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data'); $sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
try { try
{
UserRights::Login($sAuthUser); // Set the user's language (if possible!) UserRights::Login($sAuthUser); // Set the user's language (if possible!)
/** @var UserInternal $oUser */ /** @var UserInternal $oUser */
$oUser = UserRights::GetUserObject(); $oUser = UserRights::GetUserObject();
if ($oUser != null) { if ($oUser != null)
if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token')) { {
throw new ForgotPasswordUserInputException('External accounts do not allow password reset'); if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token'))
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible'));
} }
if (!$oUser->CanChangePassword()) { if (!$oUser->CanChangePassword())
throw new ForgotPasswordUserInputException('The account does not allow password reset'); {
throw new Exception(Dict::S('UI:ResetPwd-Error-FixedPwd'));
} }
$sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed $sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed
if ($sTo == '') { if ($sTo == '')
throw new ForgotPasswordUserInputException('Missing email address for this account'); {
throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmail'));
} }
// This token allows the user to change the password without knowing the previous one // This token allows the user to change the password without knowing the previous one
@@ -248,29 +265,29 @@ class LoginWebPage extends NiceWebPage
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken); $sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
$oEmail->SetBody(Dict::Format('UI:ResetPwd-EmailBody', $sResetUrl, $oUser->Get('login'))); $oEmail->SetBody(Dict::Format('UI:ResetPwd-EmailBody', $sResetUrl, $oUser->Get('login')));
$iRes = $oEmail->Send($aIssues, true /* force synchronous exec */); $iRes = $oEmail->Send($aIssues, true /* force synchronous exec */);
switch ($iRes) { switch ($iRes)
{
//case EMAIL_SEND_PENDING: //case EMAIL_SEND_PENDING:
case EMAIL_SEND_OK: case EMAIL_SEND_OK:
break; break;
case EMAIL_SEND_ERROR: case EMAIL_SEND_ERROR:
default: default:
throw new ForgotPasswordApplicationException('Failed to send the password reset email for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues)); IssueLog::Error('Failed to send the email with the NEW password for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues));
throw new Exception(Dict::S('UI:ResetPwd-Error-Send'));
} }
} }
} catch (ForgotPasswordApplicationException $e) {
IssueLog::Error('Failed to process the forgot password request for user "'.$sAuthUser.'" [reason='.get_class($e).']: '.$e->getMessage());
} catch (ForgotPasswordUserInputException $e) {
IssueLog::Info('Failed to process the forgot password request for user "'.$sAuthUser.'" [reason='.get_class($e).']: '.$e->getMessage());
} catch (\Throwable $e) {
IssueLog::Error('Unexpected error while processing the forgot password request for user "'.$sAuthUser.'": '.$e->getMessage());
}
$oTwigContext = new LoginTwigRenderer(); $oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars(); $aVars = $oTwigContext->GetDefaultVars();
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars); $oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
} }
catch(Exception $e)
{
$this->DisplayForgotPwdForm(true, $e->getMessage());
}
}
public function DisplayResetPwdForm($sErrorMessage = null) public function DisplayResetPwdForm($sErrorMessage = null)
{ {
@@ -287,16 +304,22 @@ class LoginWebPage extends NiceWebPage
$aVars['sToken'] = $sToken; $aVars['sToken'] = $sToken;
$aVars['sErrorMessage'] = $sErrorMessage; $aVars['sErrorMessage'] = $sErrorMessage;
if (($oUser == null)) { if (($oUser == null))
{
$aVars['bNoUser'] = true; $aVars['bNoUser'] = true;
} else { }
else
{
$aVars['bNoUser'] = false; $aVars['bNoUser'] = false;
$aVars['sUserName'] = $oUser->GetFriendlyName(); $aVars['sUserName'] = $oUser->GetFriendlyName();
$oEncryptedToken = $oUser->Get('reset_pwd_token'); $oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken)) { if (!$oEncryptedToken->CheckPassword($sToken))
{
$aVars['bBadToken'] = true; $aVars['bBadToken'] = true;
} else { }
else
{
$aVars['bBadToken'] = false; $aVars['bBadToken'] = false;
} }
} }
@@ -319,15 +342,21 @@ class LoginWebPage extends NiceWebPage
$aVars['sAuthUser'] = $sAuthUser; $aVars['sAuthUser'] = $sAuthUser;
$aVars['sToken'] = $sToken; $aVars['sToken'] = $sToken;
if (($oUser == null)) { if (($oUser == null))
{
$aVars['bNoUser'] = true; $aVars['bNoUser'] = true;
} else { }
else
{
$aVars['bNoUser'] = false; $aVars['bNoUser'] = false;
$oEncryptedToken = $oUser->Get('reset_pwd_token'); $oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken)) { if (!$oEncryptedToken->CheckPassword($sToken))
{
$aVars['bBadToken'] = true; $aVars['bBadToken'] = true;
} else { }
else
{
$aVars['bBadToken'] = false; $aVars['bBadToken'] = false;
// Trash the token and change the password // Trash the token and change the password
$oUser->Set('reset_pwd_token', new ormPassword()); $oUser->Set('reset_pwd_token', new ormPassword());
@@ -384,7 +413,7 @@ class LoginWebPage extends NiceWebPage
// Note: This will destroy the session, and not just the session data! // Note: This will destroy the session, and not just the session data!
} }
public static function SecureConnectionRequired() static function SecureConnectionRequired()
{ {
return MetaModel::GetConfig()->GetSecureConnectionRequired(); return MetaModel::GetConfig()->GetSecureConnectionRequired();
} }
@@ -394,7 +423,7 @@ class LoginWebPage extends NiceWebPage
* @param string $sString * @param string $sString
* @return bool True if the string contains some typical UTF-8 multi-byte sequences * @return bool True if the string contains some typical UTF-8 multi-byte sequences
*/ */
public static function LooksLikeUTF8($sString) static function LooksLikeUTF8($sString)
{ {
return preg_match('%(?: return preg_match('%(?:
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
@@ -417,19 +446,22 @@ class LoginWebPage extends NiceWebPage
protected static function Login($iOnExit) protected static function Login($iOnExit)
{ {
self::$iOnExit = $iOnExit; self::$iOnExit = $iOnExit;
if (self::SecureConnectionRequired() && !utils::IsConnectionSecure()) { if (self::SecureConnectionRequired() && !utils::IsConnectionSecure())
{
// Non secured URL... request for a secure connection // Non secured URL... request for a secure connection
throw new Exception('Secure connection required!'); throw new Exception('Secure connection required!');
} }
$bLoginDebug = MetaModel::GetConfig()->Get('login_debug'); $bLoginDebug = MetaModel::GetConfig()->Get('login_debug');
if (Session::Get('login_state') == self::LOGIN_STATE_ERROR) { if (Session::Get('login_state') == self::LOGIN_STATE_ERROR)
{
Session::Set('login_state', self::LOGIN_STATE_START); Session::Set('login_state', self::LOGIN_STATE_START);
} }
$sLoginState = Session::Get('login_state'); $sLoginState = Session::Get('login_state');
$sSessionLog = ''; $sSessionLog = '';
if ($bLoginDebug) { if ($bLoginDebug)
{
IssueLog::Info("---------------------------------"); IssueLog::Info("---------------------------------");
IssueLog::Info($_SERVER['REQUEST_URI']); IssueLog::Info($_SERVER['REQUEST_URI']);
IssueLog::Info("--> Entering Login FSM with state: [$sLoginState]"); IssueLog::Info("--> Entering Login FSM with state: [$sLoginState]");
@@ -440,30 +472,38 @@ class LoginWebPage extends NiceWebPage
$iErrorCode = self::EXIT_CODE_OK; $iErrorCode = self::EXIT_CODE_OK;
// Finite state machine loop // Finite state machine loop
while (true) { while (true)
try { {
try
{
$aLoginPlugins = self::GetLoginPluginList(); $aLoginPlugins = self::GetLoginPluginList();
if (empty($aLoginPlugins)) { if (empty($aLoginPlugins))
{
throw new Exception("Missing login classes"); throw new Exception("Missing login classes");
} }
/** @var iLoginFSMExtension $oLoginFSMExtensionInstance */ /** @var iLoginFSMExtension $oLoginFSMExtensionInstance */
foreach ($aLoginPlugins as $oLoginFSMExtensionInstance) { foreach ($aLoginPlugins as $oLoginFSMExtensionInstance)
if ($bLoginDebug) { {
if ($bLoginDebug)
{
$sCurrSessionLog = session_id().' '.utils::GetSessionLog(); $sCurrSessionLog = session_id().' '.utils::GetSessionLog();
if ($sCurrSessionLog != $sSessionLog) { if ($sCurrSessionLog != $sSessionLog)
{
$sSessionLog = $sCurrSessionLog; $sSessionLog = $sCurrSessionLog;
IssueLog::Info("SESSION: $sSessionLog"); IssueLog::Info("SESSION: $sSessionLog");
} }
IssueLog::Info("Login: state: [$sLoginState] call: ".get_class($oLoginFSMExtensionInstance)); IssueLog::Info("Login: state: [$sLoginState] call: ".get_class($oLoginFSMExtensionInstance));
} }
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode); $iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
if ($iResponse == self::LOGIN_FSM_RETURN) { if ($iResponse == self::LOGIN_FSM_RETURN)
{
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState])); EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
Session::WriteClose(); Session::WriteClose();
return $iErrorCode; // Asked to exit FSM, generally login OK return $iErrorCode; // Asked to exit FSM, generally login OK
} }
if ($iResponse == self::LOGIN_FSM_ERROR) { if ($iResponse == self::LOGIN_FSM_ERROR)
{
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState])); EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
$sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error $sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error
// An error was detected, skip the other plugins turn // An error was detected, skip the other plugins turn
@@ -475,7 +515,9 @@ class LoginWebPage extends NiceWebPage
// Every plugin has nothing else to do in this state, go forward // Every plugin has nothing else to do in this state, go forward
$sLoginState = self::AdvanceLoginFSMState($sLoginState); $sLoginState = self::AdvanceLoginFSMState($sLoginState);
Session::Set('login_state', $sLoginState); Session::Set('login_state', $sLoginState);
} catch (Exception $e) { }
catch (Exception $e)
{
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['state' => $_SESSION['login_state']])); EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['state' => $_SESSION['login_state']]));
IssueLog::Error($e->getTraceAsString()); IssueLog::Error($e->getTraceAsString());
static::ResetSession(); static::ResetSession();
@@ -495,23 +537,30 @@ class LoginWebPage extends NiceWebPage
*/ */
public static function GetLoginPluginList($sInterface = 'iLoginFSMExtension', $bFilterWithMode = true) public static function GetLoginPluginList($sInterface = 'iLoginFSMExtension', $bFilterWithMode = true)
{ {
$aAllPlugins = []; $aAllPlugins = array();
if ($bFilterWithMode) { if ($bFilterWithMode)
{
$sCurrentLoginMode = Session::Get('login_mode', ''); $sCurrentLoginMode = Session::Get('login_mode', '');
} else { }
else
{
$sCurrentLoginMode = ''; $sCurrentLoginMode = '';
} }
/** @var iLoginExtension $oLoginExtensionInstance */ /** @var iLoginExtension $oLoginExtensionInstance */
foreach (MetaModel::EnumPlugins($sInterface) as $oLoginExtensionInstance) { foreach (MetaModel::EnumPlugins($sInterface) as $oLoginExtensionInstance)
{
$aLoginModes = $oLoginExtensionInstance->ListSupportedLoginModes(); $aLoginModes = $oLoginExtensionInstance->ListSupportedLoginModes();
$aLoginModes = (is_array($aLoginModes) ? $aLoginModes : []); $aLoginModes = (is_array($aLoginModes) ? $aLoginModes : array());
foreach ($aLoginModes as $sLoginMode) { foreach ($aLoginModes as $sLoginMode)
{
// Keep only the plugins for the current login mode + before + after // Keep only the plugins for the current login mode + before + after
if (empty($sCurrentLoginMode) || ($sLoginMode == $sCurrentLoginMode) || ($sLoginMode == 'before') || ($sLoginMode == 'after')) { if (empty($sCurrentLoginMode) || ($sLoginMode == $sCurrentLoginMode) || ($sLoginMode == 'before') || ($sLoginMode == 'after'))
if (!isset($aAllPlugins[$sLoginMode])) { {
$aAllPlugins[$sLoginMode] = []; if (!isset($aAllPlugins[$sLoginMode]))
{
$aAllPlugins[$sLoginMode] = array();
} }
$aAllPlugins[$sLoginMode][] = $oLoginExtensionInstance; $aAllPlugins[$sLoginMode][] = $oLoginExtensionInstance;
break; // Stop here to avoid registering a plugin twice break; // Stop here to avoid registering a plugin twice
@@ -520,10 +569,12 @@ class LoginWebPage extends NiceWebPage
} }
// Order and filter by the config list of allowed types (allowed_login_types) // Order and filter by the config list of allowed types (allowed_login_types)
$aAllowedLoginModes = array_merge(['before'], MetaModel::GetConfig()->GetAllowedLoginTypes(), ['after']); $aAllowedLoginModes = array_merge(array('before'), MetaModel::GetConfig()->GetAllowedLoginTypes(), array('after'));
$aPlugins = []; $aPlugins = array();
foreach ($aAllowedLoginModes as $sAllowedMode) { foreach ($aAllowedLoginModes as $sAllowedMode)
if (isset($aAllPlugins[$sAllowedMode])) { {
if (isset($aAllPlugins[$sAllowedMode]))
{
$aPlugins = array_merge($aPlugins, $aAllPlugins[$sAllowedMode]); $aPlugins = array_merge($aPlugins, $aAllPlugins[$sAllowedMode]);
} }
} }
@@ -539,7 +590,8 @@ class LoginWebPage extends NiceWebPage
*/ */
private static function AdvanceLoginFSMState($sLoginState) private static function AdvanceLoginFSMState($sLoginState)
{ {
switch ($sLoginState) { switch ($sLoginState)
{
case self::LOGIN_STATE_START: case self::LOGIN_STATE_START:
return self::LOGIN_STATE_MODE_DETECTION; return self::LOGIN_STATE_MODE_DETECTION;
@@ -586,7 +638,8 @@ class LoginWebPage extends NiceWebPage
public static function CheckUser($sAuthUser, $sAuthPassword = '', $sAuthentication = 'external') public static function CheckUser($sAuthUser, $sAuthPassword = '', $sAuthentication = 'external')
{ {
$oUser = self::FindUser($sAuthUser, true, ucfirst(strtolower($sAuthentication))); $oUser = self::FindUser($sAuthUser, true, ucfirst(strtolower($sAuthentication)));
if (is_null($oUser)) { if (is_null($oUser))
{
return false; return false;
} }
@@ -615,7 +668,8 @@ class LoginWebPage extends NiceWebPage
{ {
// User is Ok, let's save it in the session and proceed with normal login // User is Ok, let's save it in the session and proceed with normal login
$bLoginSuccess = UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language $bLoginSuccess = UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
if (!$bLoginSuccess) { if (!$bLoginSuccess)
{
throw new Exception("Bad user"); throw new Exception("Bad user");
} }
if (MetaModel::GetConfig()->Get('log_usage')) { if (MetaModel::GetConfig()->Get('log_usage')) {
@@ -642,10 +696,12 @@ class LoginWebPage extends NiceWebPage
*/ */
public static function CheckLoggedUser(&$iErrorCode) public static function CheckLoggedUser(&$iErrorCode)
{ {
if (Session::IsSet('auth_user')) { if (Session::IsSet('auth_user'))
{
// Already authenticated // Already authenticated
$bRet = UserRights::Login(Session::Get('auth_user')); // Login & set the user's language $bRet = UserRights::Login(Session::Get('auth_user')); // Login & set the user's language
if ($bRet) { if ($bRet)
{
$iErrorCode = self::EXIT_CODE_OK; $iErrorCode = self::EXIT_CODE_OK;
return self::LOGIN_FSM_RETURN; return self::LOGIN_FSM_RETURN;
} }
@@ -671,7 +727,8 @@ class LoginWebPage extends NiceWebPage
public static function SetLoginModeAndReload($sNewLoginMode) public static function SetLoginModeAndReload($sNewLoginMode)
{ {
if (Session::Get('login_mode') == $sNewLoginMode) { if (Session::Get('login_mode') == $sNewLoginMode)
{
return; return;
} }
Session::Set('login_mode', $sNewLoginMode); Session::Set('login_mode', $sNewLoginMode);
@@ -681,7 +738,8 @@ class LoginWebPage extends NiceWebPage
public static function HTTPReload() public static function HTTPReload()
{ {
$sOriginURL = utils::GetCurrentAbsoluteUrl(); $sOriginURL = utils::GetCurrentAbsoluteUrl();
if (!utils::StartsWith($sOriginURL, utils::GetAbsoluteUrlAppRoot())) { if (!utils::StartsWith($sOriginURL, utils::GetAbsoluteUrlAppRoot()))
{
// If the found URL does not start with the configured AppRoot URL // If the found URL does not start with the configured AppRoot URL
$sOriginURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php'; $sOriginURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
} }
@@ -695,6 +753,7 @@ class LoginWebPage extends NiceWebPage
exit; exit;
} }
/** /**
* Provisioning API: Find a User * Provisioning API: Find a User
* *
@@ -708,21 +767,26 @@ class LoginWebPage extends NiceWebPage
*/ */
public static function FindUser($sAuthUser, $bMustBeValid = true, $sType = 'External') public static function FindUser($sAuthUser, $bMustBeValid = true, $sType = 'External')
{ {
try { try
$aArgs = ['login' => $sAuthUser]; {
$aArgs = array('login' => $sAuthUser);
$sUserClass = "User$sType"; $sUserClass = "User$sType";
$oSearch = DBObjectSearch::FromOQL("SELECT $sUserClass WHERE login = :login"); $oSearch = DBObjectSearch::FromOQL("SELECT $sUserClass WHERE login = :login");
if ($bMustBeValid) { if ($bMustBeValid)
{
$oSearch->AddCondition('status', 'enabled'); $oSearch->AddCondition('status', 'enabled');
} }
$oSet = new DBObjectSet($oSearch, [], $aArgs); $oSet = new DBObjectSet($oSearch, array(), $aArgs);
if ($oSet->CountExceeds(0)) { if ($oSet->CountExceeds(0))
{
/** @var User $oUser */ /** @var User $oUser */
$oUser = $oSet->Fetch(); $oUser = $oSet->Fetch();
return $oUser; return $oUser;
} }
} catch (Exception $e) { }
catch (Exception $e)
{
IssueLog::Error($e->getMessage()); IssueLog::Error($e->getMessage());
} }
return null; return null;
@@ -741,15 +805,19 @@ class LoginWebPage extends NiceWebPage
{ {
/** @var \Person $oPerson */ /** @var \Person $oPerson */
$oPerson = null; $oPerson = null;
try { try
{
$oSearch = new DBObjectSearch('Person'); $oSearch = new DBObjectSearch('Person');
$oSearch->AddCondition('email', $sEmail); $oSearch->AddCondition('email', $sEmail);
$oSet = new DBObjectSet($oSearch); $oSet = new DBObjectSet($oSearch);
if ($oSet->CountExceeds(1)) { if ($oSet->CountExceeds(1))
{
throw new Exception(Dict::S('UI:Login:Error:MultipleContactsHaveSameEmail')); throw new Exception(Dict::S('UI:Login:Error:MultipleContactsHaveSameEmail'));
} }
$oPerson = $oSet->Fetch(); $oPerson = $oSet->Fetch();
} catch (Exception $e) { }
catch (Exception $e)
{
IssueLog::Error($e->getMessage()); IssueLog::Error($e->getMessage());
} }
return $oPerson; return $oPerson;
@@ -768,14 +836,16 @@ class LoginWebPage extends NiceWebPage
* *
* @return \Person * @return \Person
*/ */
public static function ProvisionPerson($sFirstName, $sLastName, $sEmail, $sOrganization, $aAdditionalParams = []) public static function ProvisionPerson($sFirstName, $sLastName, $sEmail, $sOrganization, $aAdditionalParams = array())
{ {
/** @var Person $oPerson */ /** @var Person $oPerson */
$oPerson = null; $oPerson = null;
try { try
{
CMDBObject::SetTrackOrigin('custom-extension'); CMDBObject::SetTrackOrigin('custom-extension');
$sInfo = 'External User provisioning'; $sInfo = 'External User provisioning';
if (Session::IsSet('login_mode')) { if (Session::IsSet('login_mode'))
{
$sInfo .= " (".Session::Get('login_mode').")"; $sInfo .= " (".Session::Get('login_mode').")";
} }
CMDBObject::SetTrackInfo($sInfo); CMDBObject::SetTrackInfo($sInfo);
@@ -785,15 +855,19 @@ class LoginWebPage extends NiceWebPage
$oPerson->Set('name', $sLastName); $oPerson->Set('name', $sLastName);
$oPerson->Set('email', $sEmail); $oPerson->Set('email', $sEmail);
$oOrg = MetaModel::GetObjectByName('Organization', $sOrganization, false); $oOrg = MetaModel::GetObjectByName('Organization', $sOrganization, false);
if (is_null($oOrg)) { if (is_null($oOrg))
{
throw new Exception(Dict::S('UI:Login:Error:WrongOrganizationName')); throw new Exception(Dict::S('UI:Login:Error:WrongOrganizationName'));
} }
$oPerson->Set('org_id', $oOrg->GetKey()); $oPerson->Set('org_id', $oOrg->GetKey());
foreach ($aAdditionalParams as $sAttCode => $sValue) { foreach ($aAdditionalParams as $sAttCode => $sValue)
{
$oPerson->Set($sAttCode, $sValue); $oPerson->Set($sAttCode, $sValue);
} }
$oPerson->DBInsert(); $oPerson->DBInsert();
} catch (Exception $e) { }
catch (Exception $e)
{
IssueLog::Error($e->getMessage()); IssueLog::Error($e->getMessage());
} }
return $oPerson; return $oPerson;
@@ -812,23 +886,27 @@ class LoginWebPage extends NiceWebPage
*/ */
public static function ProvisionUser($sAuthUser, $oPerson, $aRequestedProfiles) public static function ProvisionUser($sAuthUser, $oPerson, $aRequestedProfiles)
{ {
if (!MetaModel::IsValidClass('URP_Profiles')) { if (!MetaModel::IsValidClass('URP_Profiles'))
{
IssueLog::Error("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry."); IssueLog::Error("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry.");
return null; return null;
} }
/** @var UserExternal $oUser */ /** @var UserExternal $oUser */
$oUser = null; $oUser = null;
try { try
{
CMDBObject::SetTrackOrigin('custom-extension'); CMDBObject::SetTrackOrigin('custom-extension');
$sInfo = 'External User provisioning'; $sInfo = 'External User provisioning';
if (Session::IsSet('login_mode')) { if (Session::IsSet('login_mode'))
{
$sInfo .= " (".Session::Get('login_mode').")"; $sInfo .= " (".Session::Get('login_mode').")";
} }
CMDBObject::SetTrackInfo($sInfo); CMDBObject::SetTrackInfo($sInfo);
$oUser = MetaModel::GetObjectByName('UserExternal', $sAuthUser, false); $oUser = MetaModel::GetObjectByName('UserExternal', $sAuthUser, false);
if (is_null($oUser)) { if (is_null($oUser))
{
$oUser = MetaModel::NewObject('UserExternal'); $oUser = MetaModel::NewObject('UserExternal');
$oUser->Set('login', $sAuthUser); $oUser->Set('login', $sAuthUser);
$oUser->Set('contactid', $oPerson->GetKey()); $oUser->Set('contactid', $oPerson->GetKey());
@@ -838,33 +916,41 @@ class LoginWebPage extends NiceWebPage
// read all the existing profiles // read all the existing profiles
$oProfilesSearch = new DBObjectSearch('URP_Profiles'); $oProfilesSearch = new DBObjectSearch('URP_Profiles');
$oProfilesSet = new DBObjectSet($oProfilesSearch); $oProfilesSet = new DBObjectSet($oProfilesSearch);
$aAllProfiles = []; $aAllProfiles = array();
while ($oProfile = $oProfilesSet->Fetch()) { while ($oProfile = $oProfilesSet->Fetch())
{
$aAllProfiles[mb_strtolower($oProfile->GetName())] = $oProfile->GetKey(); $aAllProfiles[mb_strtolower($oProfile->GetName())] = $oProfile->GetKey();
} }
$aProfiles = []; $aProfiles = array();
foreach ($aRequestedProfiles as $sRequestedProfile) { foreach ($aRequestedProfiles as $sRequestedProfile)
{
$sRequestedProfile = mb_strtolower($sRequestedProfile); $sRequestedProfile = mb_strtolower($sRequestedProfile);
if (isset($aAllProfiles[$sRequestedProfile])) { if (isset($aAllProfiles[$sRequestedProfile]))
{
$aProfiles[] = $aAllProfiles[$sRequestedProfile]; $aProfiles[] = $aAllProfiles[$sRequestedProfile];
} }
} }
if (empty($aProfiles)) { if (empty($aProfiles))
{
throw new Exception(Dict::S('UI:Login:Error:NoValidProfiles')); throw new Exception(Dict::S('UI:Login:Error:NoValidProfiles'));
} }
// Now synchronize the profiles // Now synchronize the profiles
$sOrigin = 'External User provisioning'; $sOrigin = 'External User provisioning';
if (Session::IsSet('login_mode')) { if (Session::IsSet('login_mode'))
{
$sOrigin .= " (".Session::Get('login_mode').")"; $sOrigin .= " (".Session::Get('login_mode').")";
} }
$aExistingProfiles = self::SynchronizeProfiles($oUser, $aProfiles, $sOrigin); $aExistingProfiles = self::SynchronizeProfiles($oUser, $aProfiles, $sOrigin);
if ($oUser->IsModified()) { if ($oUser->IsModified())
{
$oUser->DBWrite(); $oUser->DBWrite();
} }
} catch (Exception $e) { }
catch (Exception $e)
{
IssueLog::Error($e->getMessage()); IssueLog::Error($e->getMessage());
} }
@@ -878,15 +964,23 @@ class LoginWebPage extends NiceWebPage
*/ */
protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT) protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT)
{ {
$ret = call_user_func([self::$sHandlerClass, 'Dispatch'], $sRequestedPortalId); $ret = call_user_func(array(self::$sHandlerClass, 'Dispatch'), $sRequestedPortalId);
if ($ret === true) { if ($ret === true)
{
return self::EXIT_CODE_OK; return self::EXIT_CODE_OK;
} elseif ($ret === false) { }
else if($ret === false)
{
throw new Exception('Nowhere to go: Your combination of user Profiles denies you access to any '.ITOP_APPLICATION_SHORT.' portal. Please contact your administrator'); throw new Exception('Nowhere to go: Your combination of user Profiles denies you access to any '.ITOP_APPLICATION_SHORT.' portal. Please contact your administrator');
} else { }
if ($iOnExit == self::EXIT_RETURN) { else
{
if ($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED; return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED;
} else { }
else
{
// No rights to be here, redirect to the portal // No rights to be here, redirect to the portal
header('Location: '.$ret); header('Location: '.$ret);
die(); die();
@@ -908,7 +1002,7 @@ class LoginWebPage extends NiceWebPage
* @return int|mixed|string * @return int|mixed|string
* @throws \Exception * @throws \Exception
*/ */
public static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT) static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT)
{ {
$sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice'; $sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice';
return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit); return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit);
@@ -925,18 +1019,23 @@ class LoginWebPage extends NiceWebPage
* @return int|mixed|string * @return int|mixed|string
* @throws \Exception * @throws \Exception
*/ */
public static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT) static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
{ {
$operation = utils::ReadParam('loginop', ''); $operation = utils::ReadParam('loginop', '');
$sMessage = self::HandleOperations($operation); // May exit directly $sMessage = self::HandleOperations($operation); // May exit directly
$iRet = self::Login($iOnExit); $iRet = self::Login($iOnExit);
if ($iRet == self::EXIT_CODE_OK) { if ($iRet == self::EXIT_CODE_OK)
if ($bMustBeAdmin && !UserRights::IsAdministrator()) { {
if ($iOnExit == self::EXIT_RETURN) { if ($bMustBeAdmin && !UserRights::IsAdministrator())
{
if ($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_MUSTBEADMIN; return self::EXIT_CODE_MUSTBEADMIN;
} else { }
else
{
require_once(APPROOT.'/setup/setuppage.class.inc.php'); require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError')); $oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n"); $oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
@@ -945,52 +1044,69 @@ class LoginWebPage extends NiceWebPage
exit; exit;
} }
} }
$iRet = call_user_func([self::$sHandlerClass, 'ChangeLocation'], $sRequestedPortalId, $iOnExit); $iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
} }
if ($iOnExit == self::EXIT_RETURN) { if ($iOnExit == self::EXIT_RETURN)
{
return $iRet; return $iRet;
} else { }
else
{
return $sMessage; return $sMessage;
} }
} }
protected static function HandleOperations($operation) protected static function HandleOperations($operation)
{ {
$sMessage = ''; // most of the operations never return, but some can return a message to be displayed $sMessage = ''; // most of the operations never return, but some can return a message to be displayed
if ($operation == 'logoff') { if ($operation == 'logoff')
{
self::ResetSession(); self::ResetSession();
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm(false /* not a failed attempt */); $oPage->DisplayLoginForm(false /* not a failed attempt */);
$oPage->output(); $oPage->output();
exit; exit;
} elseif ($operation == 'forgot_pwd') { }
else if ($operation == 'forgot_pwd')
{
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DisplayForgotPwdForm(); $oPage->DisplayForgotPwdForm();
$oPage->output(); $oPage->output();
exit; exit;
} elseif ($operation == 'forgot_pwd_go') { }
else if ($operation == 'forgot_pwd_go')
{
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->ForgotPwdGo(); $oPage->ForgotPwdGo();
$oPage->output(); $oPage->output();
exit; exit;
} elseif ($operation == 'reset_pwd') { }
else if ($operation == 'reset_pwd')
{
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DisplayResetPwdForm(); $oPage->DisplayResetPwdForm();
$oPage->output(); $oPage->output();
exit; exit;
} elseif ($operation == 'do_reset_pwd') { }
else if ($operation == 'do_reset_pwd')
{
try { try {
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DoResetPassword(); $oPage->DoResetPassword();
} catch (CoreCannotSaveObjectException $e) { }
catch (CoreCannotSaveObjectException $e)
{
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DisplayResetPwdForm($e->getIssue()); $oPage->DisplayResetPwdForm($e->getIssue());
} }
$oPage->output(); $oPage->output();
exit; exit;
} elseif ($operation == 'change_pwd') { }
if (Session::IsSet('auth_user')) { else if ($operation == 'change_pwd')
{
if (Session::IsSet('auth_user'))
{
$sAuthUser = Session::Get('auth_user'); $sAuthUser = Session::Get('auth_user');
$sIssue = Session::Get('pwd_issue'); $sIssue = Session::Get('pwd_issue');
Session::Unset('pwd_issue'); Session::Unset('pwd_issue');
@@ -1002,13 +1118,16 @@ class LoginWebPage extends NiceWebPage
$oPage->output(); $oPage->output();
exit; exit;
} }
} elseif ($operation == 'check_pwd_policy') { }
else if ($operation == 'check_pwd_policy')
{
$sAuthUser = Session::Get('auth_user'); $sAuthUser = Session::Get('auth_user');
UserRights::Login($sAuthUser); // Set the user's language UserRights::Login($sAuthUser); // Set the user's language
$aPwdMap = []; $aPwdMap = array();
foreach (['new_pwd', 'retype_new_pwd'] as $postedPwd) { foreach (array('new_pwd', 'retype_new_pwd') as $postedPwd)
{
$oUser = new UserLocal(); $oUser = new UserLocal();
$oUser->ValidatePassword($_POST[$postedPwd]); $oUser->ValidatePassword($_POST[$postedPwd]);
@@ -1018,21 +1137,27 @@ class LoginWebPage extends NiceWebPage
echo json_encode($aPwdMap); echo json_encode($aPwdMap);
die(); die();
} }
if ($operation == 'do_change_pwd') { if ($operation == 'do_change_pwd')
if (Session::IsSet('auth_user')) { {
if (Session::IsSet('auth_user'))
{
$sAuthUser = Session::Get('auth_user'); $sAuthUser = Session::Get('auth_user');
UserRights::Login($sAuthUser); // Set the user's language UserRights::Login($sAuthUser); // Set the user's language
$sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data'); $sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data'); $sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');
try { try
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd)))) { {
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
{
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm(true); // old pwd was wrong $oPage->DisplayChangePwdForm(true); // old pwd was wrong
$oPage->output(); $oPage->output();
exit; exit;
} }
} catch (CoreCannotSaveObjectException $e) { }
catch (CoreCannotSaveObjectException $e)
{
$oPage = self::NewLoginWebPage(); $oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm(true, $e->getIssue()); // password policy was not met. $oPage->DisplayChangePwdForm(true, $e->getIssue()); // password policy was not met.
$oPage->output(); $oPage->output();
@@ -1046,24 +1171,23 @@ class LoginWebPage extends NiceWebPage
protected static function Dispatch($sRequestedPortalId) protected static function Dispatch($sRequestedPortalId)
{ {
if ($sRequestedPortalId === null) { if ($sRequestedPortalId === null) return true; // allowed to any portal => return true
return true;
} // allowed to any portal => return true
$aPortalsConf = PortalDispatcherData::GetData(); $aPortalsConf = PortalDispatcherData::GetData();
$aDispatchers = []; $aDispatchers = array();
foreach ($aPortalsConf as $sPortalId => $aConf) { foreach($aPortalsConf as $sPortalId => $aConf)
{
$sHandlerClass = $aConf['handler']; $sHandlerClass = $aConf['handler'];
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId); $aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
} }
if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed()) { if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed())
{
return true; return true;
} }
foreach ($aDispatchers as $sPortalId => $oDispatcher) { foreach($aDispatchers as $sPortalId => $oDispatcher)
if ($oDispatcher->IsUserAllowed()) { {
return $oDispatcher->GetUrl(); if ($oDispatcher->IsUserAllowed()) return $oDispatcher->GetUrl();
}
} }
return false; // nothing matched !! return false; // nothing matched !!
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* Copyright (C) 2013-2024 Combodo SAS * Copyright (C) 2013-2024 Combodo SAS
* *
@@ -18,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
*/ */
// //
// Maintenance message display functions // Maintenance message display functions
// Only included by approot.inc.php // Only included by approot.inc.php
@@ -33,7 +33,8 @@ function _MaintenanceSetupPageMessage($sTitle, $sMessage)
{ {
// Web Page // Web Page
@include_once(APPROOT.'setup/setuppage.class.inc.php'); @include_once(APPROOT.'setup/setuppage.class.inc.php');
if (class_exists('SetupPage')) { if (class_exists('SetupPage'))
{
$oP = new ErrorPage($sTitle); $oP = new ErrorPage($sTitle);
$oP->p("<h2 class=\"center\">$sMessage</h2>"); $oP->p("<h2 class=\"center\">$sMessage</h2>");
$oP->add_ready_script( $oP->add_ready_script(
@@ -41,9 +42,12 @@ function _MaintenanceSetupPageMessage($sTitle, $sMessage)
// Reload in 30s to check if maintenance is over // Reload in 30s to check if maintenance is over
setTimeout(function(){ window.location.reload(); }, 30000); setTimeout(function(){ window.location.reload(); }, 30000);
JS JS
); );
$oP->output(); $oP->output();
} else { }
else
{
_MaintenanceTextMessage($sMessage); _MaintenanceTextMessage($sMessage);
} }
} }
@@ -74,13 +78,14 @@ function _MaintenanceHtmlMessage($sMessage)
*/ */
function _MaintenanceJsonMessage($sTitle, $sMessage) function _MaintenanceJsonMessage($sTitle, $sMessage)
{ {
if (class_exists('JsonPage')) { if (class_exists('JsonPage'))
{
$oP = new JsonPage($sTitle); $oP = new JsonPage($sTitle);
$oP->add_header('Access-Control-Allow-Origin: *'); $oP->add_header('Access-Control-Allow-Origin: *');
$aMessage = [ $aMessage = [
'code' => 100, 'code' => 100,
'message' => $sMessage, 'message' =>$sMessage
]; ];
$oP->AddData($aMessage); $oP->AddData($aMessage);

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -13,6 +12,7 @@ use Combodo\iTop\Application\WebPage\WebPage;
require_once(APPROOT.'/application/utils.inc.php'); require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT."/application/user.dashboard.class.inc.php"); require_once(APPROOT."/application/user.dashboard.class.inc.php");
/** /**
* This class manipulates, stores and displays the navigation menu used in the application * This class manipulates, stores and displays the navigation menu used in the application
* In order to improve the modularity of the data model and to ease the update/migration * In order to improve the modularity of the data model and to ease the update/migration
@@ -51,40 +51,43 @@ class ApplicationMenu
/** /**
* @var bool * @var bool
*/ */
public static $bAdditionalMenusLoaded = false; static $bAdditionalMenusLoaded = false;
/** /**
* @var array * @var array
*/ */
public static $aRootMenus = []; static $aRootMenus = array();
/** /**
* @var array * @var array
*/ */
public static $aMenusIndex = []; static $aMenusIndex = array();
/** /**
* @var array * @var array
*/ */
public static $aMenusById = []; static $aMenusById = [];
/** /**
* @var string * @var string
*/ */
public static $sFavoriteSiloQuery = 'SELECT Organization'; static $sFavoriteSiloQuery = 'SELECT Organization';
/** /**
* @return void * @return void
*/ */
public static function LoadAdditionalMenus() public static function LoadAdditionalMenus()
{ {
if (!self::$bAdditionalMenusLoaded) { if (!self::$bAdditionalMenusLoaded)
{
// Build menus from module handlers // Build menus from module handlers
// //
/** @var \ModuleHandlerApiInterface $oPHPClass */ /** @var \ModuleHandlerApiInterface $oPHPClass */
foreach (MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass) { foreach(MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass)
{
$oPHPClass::OnMenuCreation(); $oPHPClass::OnMenuCreation();
} }
// Build menus from the menus themselves (e.g. the ShortcutContainerMenuNode will do that) // Build menus from the menus themselves (e.g. the ShortcutContainerMenuNode will do that)
// //
foreach (self::$aRootMenus as $aMenu) { foreach(self::$aRootMenus as $aMenu)
{
$oMenuNode = self::GetMenuNode($aMenu['index']); $oMenuNode = self::GetMenuNode($aMenu['index']);
$oMenuNode->PopulateChildMenus(); $oMenuNode->PopulateChildMenus();
} }
@@ -121,7 +124,8 @@ class ApplicationMenu
*/ */
public static function CheckMenuIdEnabled($sMenuId) public static function CheckMenuIdEnabled($sMenuId)
{ {
if (self::IsMenuIdEnabled($sMenuId) === false) { if (self::IsMenuIdEnabled($sMenuId) === false)
{
require_once(APPROOT.'/setup/setuppage.class.inc.php'); require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError')); $oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessRestricted')."</h1>\n"); $oP->add("<h1>".Dict::S('UI:Login:Error:AccessRestricted')."</h1>\n");
@@ -155,18 +159,22 @@ class ApplicationMenu
public static function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank) public static function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank)
{ {
$index = self::GetMenuIndexById($oMenuNode->GetMenuId()); $index = self::GetMenuIndexById($oMenuNode->GetMenuId());
if ($index == -1) { if ($index == -1)
{
// The menu does not already exist, insert it // The menu does not already exist, insert it
$index = count(self::$aMenusIndex); $index = count(self::$aMenusIndex);
if ($iParentIndex == -1) { if ($iParentIndex == -1)
{
$sParentId = ''; $sParentId = '';
self::$aRootMenus[] = ['rank' => $fRank, 'index' => $index]; self::$aRootMenus[] = array ('rank' => $fRank, 'index' => $index);
} else { }
else
{
/** @var \MenuNode $oNode */ /** @var \MenuNode $oNode */
$oNode = self::$aMenusIndex[$iParentIndex]['node']; $oNode = self::$aMenusIndex[$iParentIndex]['node'];
$sParentId = $oNode->GetMenuId(); $sParentId = $oNode->GetMenuId();
self::$aMenusIndex[$iParentIndex]['children'][] = ['rank' => $fRank, 'index' => $index]; self::$aMenusIndex[$iParentIndex]['children'][] = array ('rank' => $fRank, 'index' => $index);
} }
// Note: At the time when 'parent', 'rank' and 'source_file' have been added for the reflection API, // Note: At the time when 'parent', 'rank' and 'source_file' have been added for the reflection API,
@@ -174,9 +182,11 @@ class ApplicationMenu
// //
$aBacktrace = debug_backtrace(); $aBacktrace = debug_backtrace();
$sFile = isset($aBacktrace[2]["file"]) ? $aBacktrace[2]["file"] : $aBacktrace[1]["file"]; $sFile = isset($aBacktrace[2]["file"]) ? $aBacktrace[2]["file"] : $aBacktrace[1]["file"];
self::$aMenusIndex[$index] = ['node' => $oMenuNode, 'children' => [], 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile]; self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
self::$aMenusById[$oMenuNode->GetMenuId()] = $index; self::$aMenusById[$oMenuNode->GetMenuId()] = $index;
} else { }
else
{
// the menu already exists, let's combine the conditions that make it visible // the menu already exists, let's combine the conditions that make it visible
/** @var \MenuNode $oNode */ /** @var \MenuNode $oNode */
$oNode = self::$aMenusIndex[$index]['node']; $oNode = self::$aMenusIndex[$index]['node'];
@@ -206,7 +216,7 @@ class ApplicationMenu
* @throws \DictExceptionMissingString * @throws \DictExceptionMissingString
* @since 3.0.0 * @since 3.0.0
*/ */
public static function GetMenusCount($aExtraParams = []) public static function GetMenusCount($aExtraParams = array())
{ {
$aMenuGroups = static::GetMenuGroups($aExtraParams); $aMenuGroups = static::GetMenuGroups($aExtraParams);
@@ -249,16 +259,18 @@ class ApplicationMenu
* @throws \DictExceptionMissingString * @throws \DictExceptionMissingString
* @since 3.0.0 * @since 3.0.0
*/ */
public static function GetMenuGroups($aExtraParams = []) public static function GetMenuGroups($aExtraParams = array())
{ {
self::LoadAdditionalMenus(); self::LoadAdditionalMenus();
// Sort the root menu based on the rank // Sort the root menu based on the rank
usort(self::$aRootMenus, ['ApplicationMenu', 'CompareOnRank']); usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$aMenuGroups = []; $aMenuGroups = [];
foreach (static::$aRootMenus as $aMenuGroup) { foreach(static::$aRootMenus as $aMenuGroup)
if (!static::CanDisplayMenu($aMenuGroup)) { {
if(!static::CanDisplayMenu($aMenuGroup))
{
continue; continue;
} }
@@ -309,23 +321,26 @@ class ApplicationMenu
* @throws \Exception * @throws \Exception
* @since 3.0.0 * @since 3.0.0
*/ */
public static function GetSubMenuNodes($sMenuGroupIdx, $aExtraParams = []) public static function GetSubMenuNodes($sMenuGroupIdx, $aExtraParams = array())
{ {
$aSubMenuItems = self::GetChildren($sMenuGroupIdx); $aSubMenuItems = self::GetChildren($sMenuGroupIdx);
// Sort the children based on the rank // Sort the children based on the rank
usort($aSubMenuItems, ['ApplicationMenu', 'CompareOnRank']); usort($aSubMenuItems, array('ApplicationMenu', 'CompareOnRank'));
$aSubMenuNodes = []; $aSubMenuNodes = [];
foreach ($aSubMenuItems as $aSubMenuItem) { foreach($aSubMenuItems as $aSubMenuItem)
if (!static::CanDisplayMenu($aSubMenuItem)) { {
if(!static::CanDisplayMenu($aSubMenuItem))
{
continue; continue;
} }
$sSubMenuItemIdx = $aSubMenuItem['index']; $sSubMenuItemIdx = $aSubMenuItem['index'];
$oSubMenuNode = static::GetMenuNode($sSubMenuItemIdx); $oSubMenuNode = static::GetMenuNode($sSubMenuItemIdx);
if (!$oSubMenuNode->IsEnabled()) { if(!$oSubMenuNode->IsEnabled())
{
continue; continue;
} }
@@ -351,15 +366,21 @@ class ApplicationMenu
private static function CanDisplayMenu($aMenu) private static function CanDisplayMenu($aMenu)
{ {
$oMenuNode = self::GetMenuNode($aMenu['index']); $oMenuNode = self::GetMenuNode($aMenu['index']);
if ($oMenuNode->IsEnabled()) { if ($oMenuNode->IsEnabled())
{
$aChildren = self::GetChildren($aMenu['index']); $aChildren = self::GetChildren($aMenu['index']);
if (count($aChildren) > 0) { if (count($aChildren) > 0)
foreach ($aChildren as $aSubMenu) { {
if (self::CanDisplayMenu($aSubMenu)) { foreach($aChildren as $aSubMenu)
{
if (self::CanDisplayMenu($aSubMenu))
{
return true; return true;
} }
} }
} else { }
else
{
return true; return true;
} }
} }
@@ -375,10 +396,12 @@ class ApplicationMenu
public static function CompareOnRank($a, $b) public static function CompareOnRank($a, $b)
{ {
$result = 1; $result = 1;
if ($a['rank'] == $b['rank']) { if ($a['rank'] == $b['rank'])
{
$result = 0; $result = 0;
} }
if ($a['rank'] < $b['rank']) { if ($a['rank'] < $b['rank'])
{
$result = -1; $result = -1;
} }
return $result; return $result;
@@ -426,7 +449,8 @@ class ApplicationMenu
{ {
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sMenuId = $oAppContext->GetCurrentValue('menu', null); $sMenuId = $oAppContext->GetCurrentValue('menu', null);
if ($sMenuId === null) { if ($sMenuId === null)
{
$sMenuId = self::GetDefaultMenuId(); $sMenuId = self::GetDefaultMenuId();
} }
return $sMenuId; return $sMenuId;
@@ -438,12 +462,13 @@ class ApplicationMenu
public static function GetDefaultMenuId() public static function GetDefaultMenuId()
{ {
static $sDefaultMenuId = null; static $sDefaultMenuId = null;
if (is_null($sDefaultMenuId)) { if (is_null($sDefaultMenuId))
{
// Make sure the root menu is sorted on 'rank' // Make sure the root menu is sorted on 'rank'
usort(self::$aRootMenus, ['ApplicationMenu', 'CompareOnRank']); usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$oFirstGroup = self::GetMenuNode(self::$aRootMenus[0]['index']); $oFirstGroup = self::GetMenuNode(self::$aRootMenus[0]['index']);
$aChildren = self::$aMenusIndex[$oFirstGroup->GetIndex()]['children']; $aChildren = self::$aMenusIndex[$oFirstGroup->GetIndex()]['children'];
usort($aChildren, ['ApplicationMenu', 'CompareOnRank']); usort($aChildren, array('ApplicationMenu', 'CompareOnRank'));
$oMenuNode = self::GetMenuNode($aChildren[0]['index']); $oMenuNode = self::GetMenuNode($aChildren[0]['index']);
$sDefaultMenuId = $oMenuNode->GetMenuId(); $sDefaultMenuId = $oMenuNode->GetMenuId();
} }
@@ -457,11 +482,13 @@ class ApplicationMenu
public static function GetRootMenuId($sMenuId) public static function GetRootMenuId($sMenuId)
{ {
$iMenuIndex = self::GetMenuIndexById($sMenuId); $iMenuIndex = self::GetMenuIndexById($sMenuId);
if ($iMenuIndex == -1) { if ($iMenuIndex == -1)
{
return ''; return '';
} }
$oMenu = ApplicationMenu::GetMenuNode($iMenuIndex); $oMenu = ApplicationMenu::GetMenuNode($iMenuIndex);
while ($oMenu->GetParentIndex() != -1) { while ($oMenu->GetParentIndex() != -1)
{
$oMenu = ApplicationMenu::GetMenuNode($oMenu->GetParentIndex()); $oMenu = ApplicationMenu::GetMenuNode($oMenu->GetParentIndex());
} }
return $oMenu->GetMenuId(); return $oMenu->GetMenuId();
@@ -548,17 +575,17 @@ abstract class MenuNode
{ {
$this->sMenuId = $sMenuId; $this->sMenuId = $sMenuId;
$this->iParentIndex = $iParentIndex; $this->iParentIndex = $iParentIndex;
$this->aReflectionProperties = []; $this->aReflectionProperties = array();
if (utils::IsNotNullOrEmptyString($sEnableClass)) { if (utils::IsNotNullOrEmptyString($sEnableClass)) {
$this->aReflectionProperties['enable_class'] = $sEnableClass; $this->aReflectionProperties['enable_class'] = $sEnableClass;
$this->aReflectionProperties['enable_action'] = $iActionCode; $this->aReflectionProperties['enable_action'] = $iActionCode;
$this->aReflectionProperties['enable_permission'] = $iAllowedResults; $this->aReflectionProperties['enable_permission'] = $iAllowedResults;
$this->aReflectionProperties['enable_stimulus'] = $sEnableStimulus; $this->aReflectionProperties['enable_stimulus'] = $sEnableStimulus;
} }
$this->m_aEnableClasses = [$sEnableClass]; $this->m_aEnableClasses = array($sEnableClass);
$this->m_aEnableActions = [$iActionCode]; $this->m_aEnableActions = array($iActionCode);
$this->m_aEnableActionResults = [$iAllowedResults]; $this->m_aEnableActionResults = array($iAllowedResults);
$this->m_aEnableStimuli = [$sEnableStimulus]; $this->m_aEnableStimuli = array($sEnableStimulus);
$this->index = ApplicationMenu::InsertMenu($this, $iParentIndex, $fRank); $this->index = ApplicationMenu::InsertMenu($this, $iParentIndex, $fRank);
} }
@@ -612,6 +639,7 @@ abstract class MenuNode
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData()); $oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
DBSearchHelper::AddContextFilter($oSearch); DBSearchHelper::AddContextFilter($oSearch);
$oSet = new DBObjectSet($oSearch); $oSet = new DBObjectSet($oSearch);
$iCount = $oSet->CountWithLimit(99); $iCount = $oSet->CountWithLimit(99);
if ($iCount > 99) { if ($iCount > 99) {
@@ -662,7 +690,8 @@ abstract class MenuNode
*/ */
public function PopulateChildMenus() public function PopulateChildMenus()
{ {
foreach (ApplicationMenu::GetChildren($this->GetIndex()) as $aMenu) { foreach (ApplicationMenu::GetChildren($this->GetIndex()) as $aMenu)
{
$index = $aMenu['index']; $index = $aMenu['index'];
$oMenu = ApplicationMenu::GetMenuNode($index); $oMenu = ApplicationMenu::GetMenuNode($index);
$oMenu->PopulateChildMenus(); $oMenu->PopulateChildMenus();
@@ -697,7 +726,8 @@ abstract class MenuNode
*/ */
public function AddCondition(MenuNode $oMenuNode) public function AddCondition(MenuNode $oMenuNode)
{ {
foreach ($oMenuNode->m_aEnableClasses as $index => $sClass) { foreach($oMenuNode->m_aEnableClasses as $index => $sClass )
{
$this->m_aEnableClasses[] = $sClass; $this->m_aEnableClasses[] = $sClass;
$this->m_aEnableActions[] = $oMenuNode->m_aEnableActions[$index]; $this->m_aEnableActions[] = $oMenuNode->m_aEnableActions[$index];
$this->m_aEnableActionResults[] = $oMenuNode->m_aEnableActionResults[$index]; $this->m_aEnableActionResults[] = $oMenuNode->m_aEnableActionResults[$index];
@@ -710,24 +740,33 @@ abstract class MenuNode
*/ */
public function IsEnabled() public function IsEnabled()
{ {
foreach ($this->m_aEnableClasses as $index => $sClass) { foreach($this->m_aEnableClasses as $index => $sClass)
if ($sClass != null) { {
if (MetaModel::IsValidClass($sClass)) { if ($sClass != null)
if ($this->m_aEnableStimuli[$index] != null) { {
if (!UserRights::IsStimulusAllowed($sClass, $this->m_aEnableStimuli[$index])) { if (MetaModel::IsValidClass($sClass))
{
if ($this->m_aEnableStimuli[$index] != null)
{
if (!UserRights::IsStimulusAllowed($sClass, $this->m_aEnableStimuli[$index]))
{
return false; return false;
} }
} }
if ($this->m_aEnableActions[$index] != null) { if ($this->m_aEnableActions[$index] != null)
{
// Menus access rights ignore the archive mode // Menus access rights ignore the archive mode
utils::PushArchiveMode(false); utils::PushArchiveMode(false);
$iResult = UserRights::IsActionAllowed($sClass, $this->m_aEnableActions[$index]); $iResult = UserRights::IsActionAllowed($sClass, $this->m_aEnableActions[$index]);
utils::PopArchiveMode(); utils::PopArchiveMode();
if (!($iResult & $this->m_aEnableActionResults[$index])) { if (!($iResult & $this->m_aEnableActionResults[$index]))
{
return false; return false;
} }
} }
} else { }
else
{
return false; return false;
} }
} }
@@ -740,7 +779,7 @@ abstract class MenuNode
* @param array $aExtraParams * @param array $aExtraParams
* @return mixed * @return mixed
*/ */
abstract public function RenderContent(WebPage $oPage, $aExtraParams = []); public abstract function RenderContent(WebPage $oPage, $aExtraParams = array());
/** /**
* @param string $sHyperlink * @param string $sHyperlink
@@ -749,13 +788,16 @@ abstract class MenuNode
*/ */
protected function AddParams($sHyperlink, $aExtraParams) protected function AddParams($sHyperlink, $aExtraParams)
{ {
if (count($aExtraParams) > 0) { if (count($aExtraParams) > 0)
$aQuery = []; {
$aQuery = array();
$sSeparator = '?'; $sSeparator = '?';
if (strpos($sHyperlink, '?') !== false) { if (strpos($sHyperlink, '?') !== false)
{
$sSeparator = '&'; $sSeparator = '&';
} }
foreach ($aExtraParams as $sName => $sValue) { foreach($aExtraParams as $sName => $sValue)
{
$aQuery[] = urlencode($sName).'='.urlencode($sValue); $aQuery[] = urlencode($sName).'='.urlencode($sValue);
} }
$sHyperlink .= $sSeparator.implode('&', $aQuery); $sHyperlink .= $sSeparator.implode('&', $aQuery);
@@ -771,7 +813,7 @@ abstract class MenuNode
class MenuGroup extends MenuNode class MenuGroup extends MenuNode
{ {
/** @var string DEFAULT_DECORATION_CLASSES Set to null by default so it is replaced by initials when none is specified */ /** @var string DEFAULT_DECORATION_CLASSES Set to null by default so it is replaced by initials when none is specified */
public const DEFAULT_DECORATION_CLASSES = null; const DEFAULT_DECORATION_CLASSES = null;
/** @var string The CSS classes used to display the menu group's icon */ /** @var string The CSS classes used to display the menu group's icon */
protected $sDecorationClasses = self::DEFAULT_DECORATION_CLASSES; protected $sDecorationClasses = self::DEFAULT_DECORATION_CLASSES;
@@ -791,7 +833,8 @@ class MenuGroup extends MenuNode
{ {
parent::__construct($sMenuId, -1 /* no parent, groups are at root level */, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus); parent::__construct($sMenuId, -1 /* no parent, groups are at root level */, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
if (!empty($sDecorationClasses)) { if(!empty($sDecorationClasses))
{
$this->sDecorationClasses = $sDecorationClasses; $this->sDecorationClasses = $sDecorationClasses;
} }
} }
@@ -832,7 +875,7 @@ class MenuGroup extends MenuNode
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
assert(false); // Shall never be called, groups do not display any content assert(false); // Shall never be called, groups do not display any content
} }
@@ -844,6 +887,7 @@ class MenuGroup extends MenuNode
*/ */
class TemplateMenuNode extends MenuNode class TemplateMenuNode extends MenuNode
{ {
/** /**
* Create a menu item based on a custom template and inserts it into the application's main menu * Create a menu item based on a custom template and inserts it into the application's main menu
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
@@ -859,18 +903,12 @@ class TemplateMenuNode extends MenuNode
{ {
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus); parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
} }
/**
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
return '';
}
/** /**
* @inheritDoc * @inheritDoc
* @throws \Exception * @throws \Exception
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
//DO NOTHING this type of menu is only used for title not clickable //DO NOTHING this type of menu is only used for title not clickable
} }
@@ -904,6 +942,7 @@ class OQLMenuNode extends MenuNode
*/ */
protected $m_aParams; protected $m_aParams;
/** /**
* Create a menu item based on an OQL query and inserts it into the application's main menu * Create a menu item based on an OQL query and inserts it into the application's main menu
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
@@ -924,7 +963,7 @@ class OQLMenuNode extends MenuNode
$this->sOQL = $sOQL; $this->sOQL = $sOQL;
$this->bSearch = $bSearch; $this->bSearch = $bSearch;
$this->bSearchFormOpen = $bSearchFormOpen; $this->bSearchFormOpen = $bSearchFormOpen;
$this->m_aParams = []; $this->m_aParams = array();
$this->aReflectionProperties['oql'] = $sOQL; $this->aReflectionProperties['oql'] = $sOQL;
$this->aReflectionProperties['do_search'] = $bSearch; $this->aReflectionProperties['do_search'] = $bSearch;
// Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects // Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects
@@ -938,7 +977,8 @@ class OQLMenuNode extends MenuNode
public function SetParameters($aParams) public function SetParameters($aParams)
{ {
$this->m_aParams = $aParams; $this->m_aParams = $aParams;
foreach ($aParams as $sKey => $value) { foreach($aParams as $sKey => $value)
{
$this->aReflectionProperties[$sKey] = $value; $this->aReflectionProperties[$sKey] = $value;
} }
} }
@@ -947,11 +987,12 @@ class OQLMenuNode extends MenuNode
* @inheritDoc * @inheritDoc
* @throws \Exception * @throws \Exception
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
$oTag = new ContextTag(ContextTag::TAG_OBJECT_SEARCH); $oTag = new ContextTag(ContextTag::TAG_OBJECT_SEARCH);
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId()); ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
OQLMenuNode::RenderOQLSearch( OQLMenuNode::RenderOQLSearch
(
$this->sOQL, $this->sOQL,
Dict::S($this->sPageTitle), Dict::S($this->sPageTitle),
'Menu_'.$this->GetMenuId(), 'Menu_'.$this->GetMenuId(),
@@ -976,7 +1017,7 @@ class OQLMenuNode extends MenuNode
* @throws DictExceptionMissingString * @throws DictExceptionMissingString
* @throws OQLException * @throws OQLException
*/ */
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = [], $bEnableBreadcrumb = false) public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array(), $bEnableBreadcrumb = false)
{ {
$sUsageId = utils::GetSafeId($sUsageId); $sUsageId = utils::GetSafeId($sUsageId);
$oSearch = DBObjectSearch::FromOQL($sOql); $oSearch = DBObjectSearch::FromOQL($sOql);
@@ -987,14 +1028,15 @@ class OQLMenuNode extends MenuNode
$oBlock = new DisplayBlock($oSearch, DisplayBlock::ENUM_STYLE_SEARCH, false /* Asynchronous */, $aParams); $oBlock = new DisplayBlock($oSearch, DisplayBlock::ENUM_STYLE_SEARCH, false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0); $oBlock->Display($oPage, 0);
$oPage->add("<div class='sf_results_area ibo-add-margin-top-250' data-target='search_results'>"); $oPage->add("<div class='sf_results_area ibo-add-margin-top-250' data-target='search_results'>");
} else { }
else {
$oPage->add("<div class='sf_results_area' data-target='search_results'>"); $oPage->add("<div class='sf_results_area' data-target='search_results'>");
} }
$aExtraParams['panel_class'] =$sClass; $aExtraParams['panel_class'] =$sClass;
$aExtraParams['panel_title'] = $sTitle; $aExtraParams['panel_title'] = $sTitle;
$aExtraParams['panel_icon'] = $sIcon; $aExtraParams['panel_icon'] = $sIcon;
$aParams = array_merge(['table_id' => $sUsageId], $aExtraParams); $aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams); $oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, $sUsageId); $oBlock->Display($oPage, $sUsageId);
@@ -1059,14 +1101,14 @@ class SearchMenuNode extends MenuNode
* @throws \DictExceptionMissingString * @throws \DictExceptionMissingString
* @throws \Exception * @throws \Exception
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId()); ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); $oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
$oSearch = new DBObjectSearch($this->sClass); $oSearch = new DBObjectSearch($this->sClass);
$sUsageId = 'Menu_'.utils::GetSafeId($this->GetMenuId()); $sUsageId = 'Menu_'.utils::GetSafeId($this->GetMenuId());
$aParams = array_merge(['table_id' => $sUsageId], $aExtraParams); $aParams = array_merge(array('table_id' =>$sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams); $oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0); $oBlock->Display($oPage, 0);
} }
@@ -1103,16 +1145,10 @@ class WebPageMenuNode extends MenuNode
* @param bool $bIsLinkInNewWindow for the {@link WebPageMenuNode::IsHyperLinkInNewWindow} method * @param bool $bIsLinkInNewWindow for the {@link WebPageMenuNode::IsHyperLinkInNewWindow} method
*/ */
public function __construct( public function __construct(
$sMenuId, $sMenuId, $sHyperlink, $iParentIndex, $fRank = 0.0, $sEnableClass = null, $iActionCode = null,
$sHyperlink, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null, $bIsLinkInNewWindow = false
$iParentIndex, )
$fRank = 0.0, {
$sEnableClass = null,
$iActionCode = null,
$iAllowedResults = UR_ALLOWED_YES,
$sEnableStimulus = null,
$bIsLinkInNewWindow = false
) {
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus); parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sHyperlink = $sHyperlink; $this->sHyperlink = $sHyperlink;
$this->aReflectionProperties['url'] = $sHyperlink; $this->aReflectionProperties['url'] = $sHyperlink;
@@ -1139,7 +1175,7 @@ class WebPageMenuNode extends MenuNode
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
assert(false); // Shall never be called, the external web page will handle the display by itself assert(false); // Shall never be called, the external web page will handle the display by itself
} }
@@ -1200,8 +1236,10 @@ class NewObjectMenuNode extends MenuNode
$aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself $aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$bActionIsAllowed = false; $bActionIsAllowed = false;
foreach ($aSubClasses as $sCandidateClass) { foreach($aSubClasses as $sCandidateClass)
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) { {
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
$bActionIsAllowed = true; $bActionIsAllowed = true;
break; // Enough for now break; // Enough for now
} }
@@ -1212,7 +1250,7 @@ class NewObjectMenuNode extends MenuNode
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
assert(false); // Shall never be called, the external web page will handle the display by itself assert(false); // Shall never be called, the external web page will handle the display by itself
} }
@@ -1252,9 +1290,7 @@ class DashboardMenuNode extends MenuNode
*/ */
public function GetHyperlink($aExtraParams) public function GetHyperlink($aExtraParams)
{ {
if ($this->sDashboardFile == '') { if ($this->sDashboardFile == '') return '';
return '';
}
return parent::GetHyperlink($aExtraParams); return parent::GetHyperlink($aExtraParams);
} }
@@ -1272,11 +1308,12 @@ class DashboardMenuNode extends MenuNode
* @inheritDoc * @inheritDoc
* @throws \Exception * @throws \Exception
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId()); ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
$oDashboard = $this->GetDashboard(); $oDashboard = $this->GetDashboard();
if ($oDashboard != null) { if ($oDashboard != null)
{
WebResourcesHelper::EnableC3JSToWebPage($oPage); WebResourcesHelper::EnableC3JSToWebPage($oPage);
$sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier'); $sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier');
@@ -1307,7 +1344,9 @@ class DashboardMenuNode extends MenuNode
} }
$oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon, iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); $oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon, iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
} }
} else { }
else
{
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'"); $oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
} }
} }
@@ -1320,9 +1359,12 @@ class DashboardMenuNode extends MenuNode
public function RenderEditor(WebPage $oPage) public function RenderEditor(WebPage $oPage)
{ {
$oDashboard = $this->GetDashboard(); $oDashboard = $this->GetDashboard();
if ($oDashboard != null) { if ($oDashboard != null)
{
$oDashboard->RenderEditor($oPage); $oDashboard->RenderEditor($oPage);
} else { }
else
{
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'"); $oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
} }
} }
@@ -1334,10 +1376,13 @@ class DashboardMenuNode extends MenuNode
public function AddDashlet($oDashlet) public function AddDashlet($oDashlet)
{ {
$oDashboard = $this->GetDashboard(); $oDashboard = $this->GetDashboard();
if ($oDashboard != null) { if ($oDashboard != null)
{
$oDashboard->AddDashlet($oDashlet); $oDashboard->AddDashlet($oDashlet);
$oDashboard->Save(); $oDashboard->Save();
} else { }
else
{
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'"); throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
} }
} }
@@ -1360,7 +1405,7 @@ class ShortcutContainerMenuNode extends MenuNode
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
} }
@@ -1375,9 +1420,10 @@ class ShortcutContainerMenuNode extends MenuNode
// //
$oBMSearch = new DBObjectSearch('Shortcut'); $oBMSearch = new DBObjectSearch('Shortcut');
$oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oBMSet = new DBObjectSet($oBMSearch, ['friendlyname' => true]); // ascending on friendlyname $oBMSet = new DBObjectSet($oBMSearch, array('friendlyname' => true)); // ascending on friendlyname
$fRank = 1; $fRank = 1;
while ($oShortcut = $oBMSet->Fetch()) { while ($oShortcut = $oBMSet->Fetch())
{
$sName = $this->GetMenuId().'_'.$oShortcut->GetKey(); $sName = $this->GetMenuId().'_'.$oShortcut->GetKey();
new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++); new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
} }
@@ -1388,6 +1434,7 @@ class ShortcutContainerMenuNode extends MenuNode
} }
} }
require_once(APPROOT.'application/shortcut.class.inc.php'); require_once(APPROOT.'application/shortcut.class.inc.php');
/** /**
* This class defines a menu item which content is a shortcut. * This class defines a menu item which content is a shortcut.
@@ -1423,22 +1470,15 @@ class ShortcutMenuNode extends MenuNode
public function GetHyperlink($aExtraParams) public function GetHyperlink($aExtraParams)
{ {
$sContext = $this->oShortcut->Get('context'); $sContext = $this->oShortcut->Get('context');
try { $aContext = unserialize($sContext);
$aContext = utils::Unserialize($sContext); if (isset($aContext['menu']))
if (isset($aContext['menu'])) { {
unset($aContext['menu']); unset($aContext['menu']);
} }
foreach ($aContext as $sArgName => $sArgValue) { foreach ($aContext as $sArgName => $sArgValue)
{
$aExtraParams[$sArgName] = $sArgValue; $aExtraParams[$sArgName] = $sArgValue;
} }
} catch (Exception $e) {
IssueLog::Warning("User shortcut corrupted, delete the shortcut", LogChannels::CONSOLE, [
'shortcut_name' => $this->oShortcut->GetName(),
'root_cause' => $e->getMessage(),
]);
// delete the shortcut
$this->oShortcut->DBDelete();
}
return parent::GetHyperlink($aExtraParams); return parent::GetHyperlink($aExtraParams);
} }
@@ -1446,7 +1486,7 @@ class ShortcutMenuNode extends MenuNode
* @inheritDoc * @inheritDoc
* @throws \Exception * @throws \Exception
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId()); ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
$this->oShortcut->RenderContent($oPage, $aExtraParams); $this->oShortcut->RenderContent($oPage, $aExtraParams);
@@ -1483,9 +1523,11 @@ class ShortcutMenuNode extends MenuNode
return true; return true;
} }
public function GetEntriesCount() public function GetEntriesCount()
{ {
return $this->GetEntriesCountFromOQL($this->oShortcut->Get('oql')); return $this->GetEntriesCountFromOQL($this->oShortcut->Get('oql'));
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -34,7 +33,7 @@ interface iNewsroomProvider
* @param User $oUser The user for who to check if the provider is applicable. * @param User $oUser The user for who to check if the provider is applicable.
* return bool * return bool
*/ */
public function IsApplicable(?User $oUser = null); public function IsApplicable(User $oUser = null);
/** /**
* The human readable (localized) label for this provider * The human readable (localized) label for this provider
@@ -119,27 +118,27 @@ abstract class NewsroomProviderBase implements iNewsroomProvider
* {@inheritDoc} * {@inheritDoc}
* @see iNewsroomProvider::GetLabel() * @see iNewsroomProvider::GetLabel()
*/ */
abstract public function GetLabel(); public abstract function GetLabel();
/** /**
* {@inheritDoc} * {@inheritDoc}
* @see iNewsroomProvider::GetFetchURL() * @see iNewsroomProvider::GetFetchURL()
*/ */
abstract public function GetFetchURL(); public abstract function GetFetchURL();
/** /**
* {@inheritDoc} * {@inheritDoc}
* @see iNewsroomProvider::GetMarkAllURL() * @see iNewsroomProvider::GetMarkAllURL()
*/ */
abstract public function GetMarkAllAsReadURL(); public abstract function GetMarkAllAsReadURL();
/** /**
* {@inheritDoc} * {@inheritDoc}
* @see iNewsroomProvider::GetViewAllURL() * @see iNewsroomProvider::GetViewAllURL()
*/ */
abstract public function GetViewAllURL(); public abstract function GetViewAllURL();
public function IsApplicable(?User $oUser = null) public function IsApplicable(User $oUser = null)
{ {
return false; return false;
} }
@@ -150,7 +149,7 @@ abstract class NewsroomProviderBase implements iNewsroomProvider
*/ */
public function GetPlaceholders() public function GetPlaceholders()
{ {
return []; // By default, empty set of placeholders return array(); // By default, empty set of placeholders
} }
public function GetTTL() public function GetTTL()

View File

@@ -1,5 +1,4 @@
<?php <?php
class PortalDispatcher class PortalDispatcher
{ {
protected $sPortalid; protected $sPortalid;
@@ -22,20 +21,25 @@ class PortalDispatcher
$bRet = true; $bRet = true;
$aProfiles = UserRights::ListProfiles($oUser); $aProfiles = UserRights::ListProfiles($oUser);
foreach ($this->aData['deny'] as $sDeniedProfile) { foreach($this->aData['deny'] as $sDeniedProfile)
{
// If one denied profile is present, it's enough => return false // If one denied profile is present, it's enough => return false
if (in_array($sDeniedProfile, $aProfiles)) { if (in_array($sDeniedProfile, $aProfiles))
{
return false; return false;
} }
} }
// If there are some "allow" profiles, then by default the result is false // If there are some "allow" profiles, then by default the result is false
// since the user must have at least one of the profiles to be allowed // since the user must have at least one of the profiles to be allowed
if (count($this->aData['allow']) > 0) { if (count($this->aData['allow']) > 0)
{
$bRet = false; $bRet = false;
} }
foreach ($this->aData['allow'] as $sAllowProfile) { foreach($this->aData['allow'] as $sAllowProfile)
{
// If one "allow" profile is present, it's enough => return true // If one "allow" profile is present, it's enough => return true
if (in_array($sAllowProfile, $aProfiles)) { if (in_array($sAllowProfile, $aProfiles))
{
return true; return true;
} }
} }
@@ -45,9 +49,12 @@ class PortalDispatcher
public function GetURL() public function GetURL()
{ {
$aOverloads = MetaModel::GetConfig()->Get('portal_dispatch_urls'); $aOverloads = MetaModel::GetConfig()->Get('portal_dispatch_urls');
if (array_key_exists($this->sPortalid, $aOverloads)) { if (array_key_exists($this->sPortalid, $aOverloads))
{
$sRet = $aOverloads[$this->sPortalid]; $sRet = $aOverloads[$this->sPortalid];
} else { }
else
{
$sRet = utils::GetAbsoluteUrlAppRoot().$this->aData['url']; $sRet = utils::GetAbsoluteUrlAppRoot().$this->aData['url'];
} }
return $sRet; return $sRet;

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* Copyright (C) 2010-2024 Combodo SAS * Copyright (C) 2010-2024 Combodo SAS
* *
@@ -32,108 +31,101 @@ abstract class Query extends cmdbAbstractObject
*/ */
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "core/cmdb,view_in_gui,application,grant_by_profile", "category" => "core/cmdb,view_in_gui,application,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_query", "db_table" => "priv_query",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "realclass", "db_finalclass_field" => "realclass",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", [ MetaModel::Init_AddAttribute(new AttributeString("name", array(
"allowed_values" => null, "allowed_values" => null,
"sql" => "name", "sql" => "name",
"default_value" => null, "default_value" => null,
"is_null_allowed" => false, "is_null_allowed" => false,
"depends_on" => [], "depends_on" => array(),
])); )));
MetaModel::Init_AddAttribute(new AttributeText("description", [ MetaModel::Init_AddAttribute(new AttributeText("description", array(
"allowed_values" => null, "allowed_values" => null,
"sql" => "description", "sql" => "description",
"default_value" => null, "default_value" => null,
"is_null_allowed" => false, "is_null_allowed" => false,
"depends_on" => [], "depends_on" => array(),
])); )));
MetaModel::Init_AddAttribute(new AttributeEnum("is_template", [ MetaModel::Init_AddAttribute(new AttributeEnum("is_template", array(
'allowed_values' => new ValueSetEnum('yes,no'), 'allowed_values' => new ValueSetEnum('yes,no'),
'sql' => 'is_template', 'sql' => 'is_template',
'default_value' => 'no', 'default_value' => 'no',
'is_null_allowed' => false, 'is_null_allowed' => false,
'depends_on' => [], 'depends_on' => [],
'display_style' => 'radio_horizontal', 'display_style' => 'radio_horizontal',
])); )));
MetaModel::Init_AddAttribute(new AttributeInteger("export_count", [ MetaModel::Init_AddAttribute(new AttributeInteger("export_count", array(
"allowed_values" => null, "allowed_values" => null,
"sql" => "export_count", "sql" => "export_count",
"default_value" => 0, "default_value" => 0,
"is_null_allowed" => false, "is_null_allowed" => false,
"depends_on" => [], "depends_on" => array(),
"tracking_level" => ATTRIBUTE_TRACKING_NONE, "tracking_level" => ATTRIBUTE_TRACKING_NONE,
])); )));
MetaModel::Init_AddAttribute(new AttributeDateTime("export_last_date", [ MetaModel::Init_AddAttribute(new AttributeDateTime("export_last_date", array(
"allowed_values" => null, "allowed_values" => null,
"sql" => "export_last_date", "sql" => "export_last_date",
"default_value" => null, "default_value" => null,
"is_null_allowed" => true, "is_null_allowed" => true,
"depends_on" => [], "depends_on" => array(),
"tracking_level" => ATTRIBUTE_TRACKING_NONE, "tracking_level" => ATTRIBUTE_TRACKING_NONE,
])); )));
MetaModel::Init_AddAttribute(new AttributeExternalKey( MetaModel::Init_AddAttribute(new AttributeExternalKey("export_last_user_id",
"export_last_user_id", array(
[
"targetclass"=>'User', "targetclass"=>'User',
"allowed_values"=>null, "allowed_values"=>null,
"sql"=>'user_id', "sql"=>'user_id',
"is_null_allowed"=>true, "is_null_allowed"=>true,
"depends_on" => [], "depends_on"=>array(),
"display_style"=>'select', "display_style"=>'select',
"always_load_in_tables"=>false, "always_load_in_tables"=>false,
"on_target_delete"=>DEL_SILENT, "on_target_delete"=>DEL_SILENT,
"tracking_level" => ATTRIBUTE_TRACKING_NONE, "tracking_level" => ATTRIBUTE_TRACKING_NONE,
] )));
));
MetaModel::Init_AddAttribute(new AttributeExternalField( MetaModel::Init_AddAttribute(new AttributeExternalField("export_last_user_contact",
"export_last_user_contact", array(
[
"allowed_values"=>null, "allowed_values"=>null,
"extkey_attcode"=> "export_last_user_id", "extkey_attcode"=> "export_last_user_id",
"target_attcode"=>"contactid", "target_attcode"=>"contactid",
"tracking_level" => ATTRIBUTE_TRACKING_NONE, "tracking_level" => ATTRIBUTE_TRACKING_NONE,
] )));
));
// Display lists // Display lists
MetaModel::Init_SetZListItems( MetaModel::Init_SetZListItems('details',
'details', array('name', 'is_template', 'description')); // Attributes to be displayed for the complete details
['name', 'is_template', 'description'] MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['description']); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', ['name', 'description', 'is_template']); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'is_template')); // Criteria of the std search form
MetaModel::Init_SetZListItems( MetaModel::Init_SetZListItems('default_search',
'default_search', array('name', 'description', 'is_template')); // Criteria of the default search form
['name', 'description', 'is_template']
); // Criteria of the default search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
} }
/** /**
* @inheritdoc * @inheritdoc
* *
* @since 3.1.0 * @since 3.1.0
*/ */
public function GetAttributeFlags($sAttCode, &$aReasons = [], $sTargetState = '') public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
{ {
// read only attribute // read only attribute
if (in_array($sAttCode, ['export_count', 'export_last_date', 'export_last_user_id'])){ if (in_array($sAttCode, ['export_count', 'export_last_date', 'export_last_user_id'])){
@@ -143,6 +135,7 @@ abstract class Query extends cmdbAbstractObject
return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
} }
/** /**
* Return export url. * Return export url.
* *
@@ -151,7 +144,7 @@ abstract class Query extends cmdbAbstractObject
* @return string|null * @return string|null
* @since 3.1.0 * @since 3.1.0
*/ */
abstract public function GetExportUrl(?array $aValues = null): ?string; abstract public function GetExportUrl(array $aValues = null) : ?string;
/** /**
* Update last export information. * Update last export information.
@@ -180,54 +173,51 @@ class QueryOQL extends Query
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "core/cmdb,view_in_gui,application,grant_by_profile", "category" => "core/cmdb,view_in_gui,application,grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => ['oql', 'is_template'], "reconc_keys" => array('oql', 'is_template'),
"db_table" => "priv_query_oql", "db_table" => "priv_query_oql",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("oql", [ MetaModel::Init_AddAttribute(new AttributeOQL("oql", array(
"allowed_values" => null, "allowed_values" => null,
"sql" => "oql", "sql" => "oql",
"default_value" => null, "default_value" => null,
"is_null_allowed" => false, "is_null_allowed" => false,
"depends_on" => [], "depends_on" => array(),
])); )));
MetaModel::Init_AddAttribute(new AttributeText("fields", [ MetaModel::Init_AddAttribute(new AttributeText("fields", array(
"allowed_values" => null, "allowed_values" => null,
"sql" => "fields", "sql" => "fields",
"default_value" => null, "default_value" => null,
"is_null_allowed" => true, "is_null_allowed" => true,
"depends_on" => [], "depends_on" => array(),
])); )));
// Rolled back to AttributeText until AttributeQueryAttCodeSet can manage fields order correctly // Rolled back to AttributeText until AttributeQueryAttCodeSet can manage fields order correctly
//MetaModel::Init_AddAttribute(new AttributeQueryAttCodeSet("fields", array("allowed_values"=>null,"max_items" => 1000, "query_field" => "oql", "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array('oql')))); //MetaModel::Init_AddAttribute(new AttributeQueryAttCodeSet("fields", array("allowed_values"=>null,"max_items" => 1000, "query_field" => "oql", "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array('oql'))));
// Display lists // Display lists
MetaModel::Init_SetZListItems( MetaModel::Init_SetZListItems('details',
'details', array(
[ 'col:col1' => array('fieldset:Query:baseinfo' => array('name', 'is_template', 'description', 'oql', 'fields')),
'col:col1' => ['fieldset:Query:baseinfo' => ['name', 'is_template', 'description', 'oql', 'fields']], 'col:col2' => array('fieldset:Query:exportInfo' => array('export_count', 'export_last_date', 'export_last_user_id', 'export_last_user_contact'))
'col:col2' => ['fieldset:Query:exportInfo' => ['export_count', 'export_last_date', 'export_last_user_id', 'export_last_user_contact']], )
]
); // Attributes to be displayed for the complete details ); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['description']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems( MetaModel::Init_SetZListItems('standard_search',
'standard_search', array('name', 'description', 'is_template', 'fields', 'oql')); // Criteria of the std search form
['name', 'description', 'is_template', 'fields', 'oql']
); // Criteria of the std search form
} }
/** @inheritdoc */ /** @inheritdoc */
public function GetExportUrl(?array $aValues = null): ?string public function GetExportUrl(array $aValues = null) : ?string
{ {
try{ try{
// retrieve attributes // retrieve attributes
@@ -247,12 +237,13 @@ class QueryOQL extends Query
} }
return $sUrl; return $sUrl;
} catch (Exception $e) { }
catch(Exception $e){
return null; return null;
} }
} }
public function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = []) function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{ {
$aFieldsMap = parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams); $aFieldsMap = parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
$oPage->add_script("$('[name=\"attr_oql\"]').addClass('ibo-query-oql ibo-is-code'); $('[data-attribute-code=\"oql\"]').addClass('ibo-query-oql ibo-is-code');"); $oPage->add_script("$('[name=\"attr_oql\"]').addClass('ibo-query-oql ibo-is-code'); $('[data-attribute-code=\"oql\"]').addClass('ibo-query-oql ibo-is-code');");
@@ -276,14 +267,16 @@ class QueryOQL extends Query
if (count($aParameters) == 0) { if (count($aParameters) == 0) {
$oBlock = new DisplayBlock($oSearch, 'list'); $oBlock = new DisplayBlock($oSearch, 'list');
$aExtraParams = [ $aExtraParams = array(
//'menu' => $sShowMenu, //'menu' => $sShowMenu,
'table_id' => 'query_preview_'.$this->getKey(), 'table_id' => 'query_preview_'.$this->getKey(),
]; );
$sBlockId = 'block_query_preview_'.$this->GetKey(); // make a unique id (edition occuring in the same DOM) $sBlockId = 'block_query_preview_'.$this->GetKey(); // make a unique id (edition occuring in the same DOM)
$oBlock->Display($oPage, $sBlockId, $aExtraParams); $oBlock->Display($oPage, $sBlockId, $aExtraParams);
} }
} catch (OQLException $e) { }
catch
(OQLException $e) {
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc()) $oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc())
->SetIsClosable(false) ->SetIsClosable(false)
->SetIsCollapsible(false); ->SetIsCollapsible(false);
@@ -294,6 +287,7 @@ class QueryOQL extends Query
return $aFieldsMap; return $aFieldsMap;
} }
// Rolled back until 'fields' can be properly managed by AttributeQueryAttCodeSet // Rolled back until 'fields' can be properly managed by AttributeQueryAttCodeSet
// //
// public function ComputeValues() // public function ComputeValues()
@@ -329,3 +323,5 @@ class QueryOQL extends Query
// } // }
} }
?>

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -19,6 +18,7 @@
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings; use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
use Combodo\iTop\Application\WebPage\WebPage; use Combodo\iTop\Application\WebPage\WebPage;
/** /**
* Persistent class Shortcut and derived * Persistent class Shortcut and derived
* Shortcuts of any kind * Shortcuts of any kind
@@ -31,32 +31,32 @@ abstract class Shortcut extends DBObject implements iDisplay
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "gui,view_in_gui", "category" => "gui,view_in_gui",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_shortcut", "db_table" => "priv_shortcut",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "realclass", "db_finalclass_field" => "realclass",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", ["targetclass" => "User", "allowed_values" => null, "sql" => "user_id", "is_null_allowed" => true, "on_target_delete" => DEL_AUTO, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("name", ["allowed_values" => null, "sql" => "name", "default_value" => null, "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("context", ["allowed_values" => null, "sql" => "context", "default_value" => null, "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeText("context", array("allowed_values"=>null, "sql"=>"context", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['name', 'context']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'context')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['name']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('name')); // Attributes to be displayed for a list
// Search criteria // Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
} }
abstract public function RenderContent(WebPage $oPage, $aExtraParams = []); abstract public function RenderContent(WebPage $oPage, $aExtraParams = array());
protected function OnInsert() protected function OnInsert()
{ {
@@ -137,13 +137,13 @@ EOF
return ''; return '';
} }
public function DisplayDetails(WebPage $oPage, $bEditMode = false) function DisplayDetails(WebPage $oPage, $bEditMode = false)
{ {
} }
public function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = []) function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{ {
return []; return array();
} }
// End of the minimal implementation of iDisplay // End of the minimal implementation of iDisplay
} }
@@ -152,39 +152,41 @@ class ShortcutOQL extends Shortcut
{ {
public static function Init() public static function Init()
{ {
$aParams = $aParams = array
[ (
"category" => "gui,view_in_gui", "category" => "gui,view_in_gui",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => [], "reconc_keys" => array(),
"db_table" => "priv_shortcut_oql", "db_table" => "priv_shortcut_oql",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
]; );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("oql", ["allowed_values" => null, "sql" => "oql", "default_value" => null, "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeOQL("oql", array("allowed_values"=>null, "sql"=>"oql", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("auto_reload", ["allowed_values" => new ValueSetEnum('none,custom'), "sql" => "auto_reload", "default_value" => "none", "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeEnum("auto_reload", array("allowed_values"=>new ValueSetEnum('none,custom'), "sql"=>"auto_reload", "default_value"=>"none", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("auto_reload_sec", ["allowed_values" => null, "sql" => "auto_reload_sec", "default_value" => 60, "is_null_allowed" => false, "depends_on" => []])); MetaModel::Init_AddAttribute(new AttributeInteger("auto_reload_sec", array("allowed_values"=>null, "sql"=>"auto_reload_sec", "default_value"=>60, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', ['name', 'context', 'oql']); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'context', 'oql')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['name']); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('name')); // Attributes to be displayed for a list
// Search criteria // Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
} }
public function RenderContent(WebPage $oPage, $aExtraParams = []) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
$oPage->set_title($this->Get('name')); $oPage->set_title($this->Get('name'));
switch ($this->Get('auto_reload')) { switch($this->Get('auto_reload'))
{
case 'custom': case 'custom':
$iRate = (int)$this->Get('auto_reload_sec'); $iRate = (int)$this->Get('auto_reload_sec');
if ($iRate > 0) { if ($iRate > 0)
{
// Must a string otherwise it can be evaluated to 'true' and defaults to "standard" refresh rate! // Must a string otherwise it can be evaluated to 'true' and defaults to "standard" refresh rate!
$aExtraParams['auto_reload'] = (string)$iRate; $aExtraParams['auto_reload'] = (string)$iRate;
} }
@@ -196,9 +198,12 @@ class ShortcutOQL extends Shortcut
$bSearchPane = true; $bSearchPane = true;
$bSearchOpen = true; $bSearchOpen = true;
try { try
{
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams, true); OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams, true);
} catch (Exception $e) { }
catch (Exception $e)
{
throw new Exception("The OQL shortcut '".$this->Get('name')."' (id: ".$this->GetKey().") could not be displayed: ".$e->getMessage()); throw new Exception("The OQL shortcut '".$this->Get('name')."' (id: ".$this->GetKey().") could not be displayed: ".$e->getMessage());
} }
@@ -221,9 +226,12 @@ class ShortcutOQL extends Shortcut
// Find a unique default name // Find a unique default name
// -> The class of the query + an index if necessary // -> The class of the query + an index if necessary
if ($sOQL == null) { if ($sOQL == null)
{
$sDefault = ''; $sDefault = '';
} else { }
else
{
$oBMSearch = new DBObjectSearch('Shortcut'); $oBMSearch = new DBObjectSearch('Shortcut');
$oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oBMSet = new DBObjectSet($oBMSearch); $oBMSet = new DBObjectSet($oBMSearch);

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -24,6 +23,7 @@ require_once(APPROOT.'core/contexttag.class.inc.php');
require_once(APPROOT.'core/kpi.class.inc.php'); require_once(APPROOT.'core/kpi.class.inc.php');
require_once(APPROOT.'setup/setuputils.class.inc.php'); require_once(APPROOT.'setup/setuputils.class.inc.php');
/** /**
* File to include to initialize the datamodel in memory * File to include to initialize the datamodel in memory
* *
@@ -36,10 +36,12 @@ ExecutionKPI::EnableMemory(1);
// This storage is freed on error (case of allowed memory exhausted) // This storage is freed on error (case of allowed memory exhausted)
$sReservedMemory = str_repeat('*', 1024 * 1024); $sReservedMemory = str_repeat('*', 1024 * 1024);
register_shutdown_function(function () { register_shutdown_function(function()
{
global $sReservedMemory; global $sReservedMemory;
$sReservedMemory = null; $sReservedMemory = null;
if (!is_null($err = error_get_last()) && ($err['type'] == E_ERROR)) { if (!is_null($err = error_get_last()) && ($err['type'] == E_ERROR))
{
// Remove stack trace from MySQLException (since 2.7.2 see N°3174) // Remove stack trace from MySQLException (since 2.7.2 see N°3174)
$sMessage = $err['message']; $sMessage = $err['message'];
if (strpos($sMessage, 'MySQLException') !== false) { if (strpos($sMessage, 'MySQLException') !== false) {
@@ -69,30 +71,38 @@ $oKPI->ComputeAndReport("Session Start");
$sSwitchEnv = utils::ReadParam('switch_env', null); $sSwitchEnv = utils::ReadParam('switch_env', null);
$bAllowCache = true; $bAllowCache = true;
if (($sSwitchEnv != null) && file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE) && (Session::Get('itop_env') !== $sSwitchEnv)) { if (($sSwitchEnv != null) && file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE) &&( Session::Get('itop_env') !== $sSwitchEnv))
{
Session::Set('itop_env', $sSwitchEnv); Session::Set('itop_env', $sSwitchEnv);
$sEnv = $sSwitchEnv; $sEnv = $sSwitchEnv;
$bAllowCache = false; $bAllowCache = false;
// Reset the opcache since otherwise the PHP "model" files may still be cached !! // Reset the opcache since otherwise the PHP "model" files may still be cached !!
if (function_exists('opcache_reset')) { if (function_exists('opcache_reset'))
{
// Zend opcode cache // Zend opcode cache
opcache_reset(); opcache_reset();
} }
if (function_exists('apc_clear_cache')) { if (function_exists('apc_clear_cache'))
{
// APC(u) cache // APC(u) cache
apc_clear_cache(); apc_clear_cache();
} }
// TODO: reset the credentials as well ?? // TODO: reset the credentials as well ??
} elseif (Session::IsSet('itop_env')) { }
else if (Session::IsSet('itop_env'))
{
$sEnv = Session::Get('itop_env'); $sEnv = Session::Get('itop_env');
} else { }
else
{
$sEnv = ITOP_DEFAULT_ENV; $sEnv = ITOP_DEFAULT_ENV;
Session::Set('itop_env', ITOP_DEFAULT_ENV); Session::Set('itop_env', ITOP_DEFAULT_ENV);
} }
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE; $sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
try { try {
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv); MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
} catch (MySQLException $e) { }
catch (MySQLException $e) {
IssueLog::Debug($e->getMessage()); IssueLog::Debug($e->getMessage());
throw new MySQLException('Could not connect to the DB server', []); throw new MySQLException('Could not connect to the DB server', []);
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* Copyright (C) 2013-2024 Combodo SAS * Copyright (C) 2013-2024 Combodo SAS
* *
@@ -26,7 +25,7 @@
*/ */
class ThemeHandler class ThemeHandler
{ {
public const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg']; const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
/** @var \CompileCSSService */ /** @var \CompileCSSService */
private static $oCompileCSSService; private static $oCompileCSSService;
@@ -51,7 +50,7 @@ class ThemeHandler
'imports' => [], 'imports' => [],
'stylesheets' => [ 'stylesheets' => [
'main' => '../css/backoffice/main.scss', 'main' => '../css/backoffice/main.scss',
], ]
], ],
]; ];
} }
@@ -64,7 +63,8 @@ class ThemeHandler
{ {
try { try {
$sThemeId = utils::GetConfig()->Get('backoffice_default_theme'); $sThemeId = utils::GetConfig()->Get('backoffice_default_theme');
} catch (CoreException $oCompileException) { }
catch (CoreException $oCompileException) {
// Fallback on our default theme in case the config. is not available yet // Fallback on our default theme in case the config. is not available yet
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation(); $aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
$sThemeId = $aDefaultTheme['name']; $sThemeId = $aDefaultTheme['name'];
@@ -85,7 +85,8 @@ class ThemeHandler
if (true === utils::GetConfig()->Get('user_preferences.allow_backoffice_theme_override')) { if (true === utils::GetConfig()->Get('user_preferences.allow_backoffice_theme_override')) {
$sThemeId = appUserPreferences::GetPref('backoffice_theme', null); $sThemeId = appUserPreferences::GetPref('backoffice_theme', null);
} }
} catch (Exception $oException) { }
catch (Exception $oException) {
// Do nothing, already handled by $sThemeId null by default // Do nothing, already handled by $sThemeId null by default
} }
@@ -200,7 +201,8 @@ class ThemeHandler
if (static::ShouldThemeSignatureCheckBeForced($sThemeId)) { if (static::ShouldThemeSignatureCheckBeForced($sThemeId)) {
static::CompileTheme($sThemeId); static::CompileTheme($sThemeId);
} }
} catch (CoreException $oCompileException) { }
catch (CoreException $oCompileException) {
// Fallback on our default theme (should always be compilable) in case the previous theme doesn't exists // Fallback on our default theme (should always be compilable) in case the previous theme doesn't exists
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation(); $aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
$sThemeId = $aDefaultTheme['name']; $sThemeId = $aDefaultTheme['name'];
@@ -256,16 +258,13 @@ class ThemeHandler
* @throws \CoreException * @throws \CoreException
* @return boolean: indicate whether theme compilation occured * @return boolean: indicate whether theme compilation occured
*/ */
public static function CompileTheme($sThemeId, $bSetup = false, $sSetupCompilationTimestamp = "", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null) public static function CompileTheme($sThemeId, $bSetup=false, $sSetupCompilationTimestamp="", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null) {
{
if ($sSetupCompilationTimestamp === "") { if ($sSetupCompilationTimestamp === "") {
$sSetupCompilationTimestamp = microtime(true); $sSetupCompilationTimestamp = microtime(true);
} }
$sSetupCompilationTimestampInSecunds = (strpos($sSetupCompilationTimestamp, '.') !== false) ? explode( $sSetupCompilationTimestampInSecunds = (strpos($sSetupCompilationTimestamp, '.') !== false) ? explode('.',
'.', $sSetupCompilationTimestamp)[0] : $sSetupCompilationTimestamp;
$sSetupCompilationTimestamp
)[0] : $sSetupCompilationTimestamp;
$sEnv = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'; $sEnv = APPROOT.'env-'.utils::GetCurrentEnvironment().'/';
@@ -331,8 +330,10 @@ class ThemeHandler
$iStyleLastModified = $oFindStylesheetObject->GetLastModified(); $iStyleLastModified = $oFindStylesheetObject->GetLastModified();
$aIncludedImages=static::GetIncludedImages($aThemeParametersWithVersion, $oFindStylesheetObject->GetAllStylesheetPaths(), $sThemeId); $aIncludedImages=static::GetIncludedImages($aThemeParametersWithVersion, $oFindStylesheetObject->GetAllStylesheetPaths(), $sThemeId);
foreach ($aIncludedImages as $sImage) { foreach ($aIncludedImages as $sImage)
if (is_file($sImage)) { {
if (is_file($sImage))
{
$iStylesheetLastModified = @filemtime($sImage); $iStylesheetLastModified = @filemtime($sImage);
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified; $iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
} }
@@ -342,7 +343,8 @@ class ThemeHandler
$iFilemetime = @filemtime($sThemeCssPath); $iFilemetime = @filemtime($sThemeCssPath);
$bFileExists = file_exists($sThemeCssPath); $bFileExists = file_exists($sThemeCssPath);
$bVarSignatureChanged=false; $bVarSignatureChanged=false;
if ($bFileExists && $bSetup) { if ($bFileExists && $bSetup)
{
$sPrecompiledSignature = static::GetSignature($sThemeCssPath); $sPrecompiledSignature = static::GetSignature($sThemeCssPath);
//check variable signature has changed which is independant from any file modification //check variable signature has changed which is independant from any file modification
if (!empty($sPrecompiledSignature)){ if (!empty($sPrecompiledSignature)){
@@ -352,17 +354,22 @@ class ThemeHandler
} }
} }
if (!$bFileExists || $bVarSignatureChanged || (is_writable($sThemeFolderPath) && ($iFilemetime < $iStyleLastModified))) { if (!$bFileExists || $bVarSignatureChanged || (is_writable($sThemeFolderPath) && ($iFilemetime < $iStyleLastModified)))
{
// Dates don't match. Second chance: check if the already compiled stylesheet exists and is consistent based on its signature // Dates don't match. Second chance: check if the already compiled stylesheet exists and is consistent based on its signature
$sActualSignature = static::ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages); $sActualSignature = static::ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages);
if ($bFileExists && !$bSetup) { if ($bFileExists && !$bSetup)
{
$sPrecompiledSignature = static::GetSignature($sThemeCssPath); $sPrecompiledSignature = static::GetSignature($sThemeCssPath);
} }
if (!empty($sPrecompiledSignature) && $sActualSignature == $sPrecompiledSignature) { if (!empty($sPrecompiledSignature) && $sActualSignature == $sPrecompiledSignature)
{
touch($sThemeCssPath); // Stylesheet is up to date, mark it as more recent to speedup next time touch($sThemeCssPath); // Stylesheet is up to date, mark it as more recent to speedup next time
} else { }
else
{
// Alas, we really need to recompile // Alas, we really need to recompile
// Add the signature to the generated CSS file so that the file can be used as a precompiled stylesheet if needed // Add the signature to the generated CSS file so that the file can be used as a precompiled stylesheet if needed
$sSignatureComment = $sSignatureComment =
@@ -374,16 +381,14 @@ $sActualSignature
*/ */
CSS; CSS;
if (!static::$oCompileCSSService) { if (!static::$oCompileCSSService)
{
static::$oCompileCSSService = new CompileCSSService(); static::$oCompileCSSService = new CompileCSSService();
} }
//store it again to change $version with latest compiled time //store it again to change $version with latest compiled time
SetupLog::Info("Compiling theme $sThemeId..."); SetupLog::Info("Compiling theme $sThemeId...");
$sTmpThemeCssContent = static::$oCompileCSSService->CompileCSSFromSASS( $sTmpThemeCssContent = static::$oCompileCSSService->CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths,
$sTmpThemeScssContent, $aThemeParametersWithVersion);
$aImportsPaths,
$aThemeParametersWithVersion
);
SetupLog::Info("$sThemeId theme compilation done."); SetupLog::Info("$sThemeId theme compilation done.");
file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters)); file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters));
file_put_contents($sThemeCssPath, $sSignatureComment.$sTmpThemeCssContent); file_put_contents($sThemeCssPath, $sSignatureComment.$sTmpThemeCssContent);
@@ -408,14 +413,13 @@ CSS;
* @return string * @return string
* @throws \Exception * @throws \Exception
*/ */
public static function ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages) public static function ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages) {
{
$aSignature = [ $aSignature = [
'variables' => md5(json_encode($aThemeParameters['variables'])), 'variables' => md5(json_encode($aThemeParameters['variables'])),
'stylesheets' => [], 'stylesheets' => [],
'variable_imports' => [], 'variable_imports' => [],
'images' => [], 'images' => [],
'utility_imports' => [], 'utility_imports' => []
]; ];
$oFindStylesheetObject = new FindStylesheetObject(); $oFindStylesheetObject = new FindStylesheetObject();
@@ -457,7 +461,8 @@ CSS;
} }
} }
foreach ($aIncludedImages as $sImage) { foreach ($aIncludedImages as $sImage)
{
if (is_file($sImage)) { if (is_file($sImage)) {
$sUri = str_replace(self::GetAppRootWithSlashes(), '', $sImage); $sUri = str_replace(self::GetAppRootWithSlashes(), '', $sImage);
$aSignature['images'][$sUri] = md5_file($sImage); $aSignature['images'][$sUri] = md5_file($sImage);
@@ -492,11 +497,14 @@ CSS;
'aFoundVariables' => $aFoundVariables, 'aFoundVariables' => $aFoundVariables,
]; ];
foreach ($aStylesheetFiles as $sStylesheetFile) { foreach ($aStylesheetFiles as $sStylesheetFile)
{
$aRes = static::GetAllUrlFromScss($aThemeParametersVariables, $sStylesheetFile); $aRes = static::GetAllUrlFromScss($aThemeParametersVariables, $sStylesheetFile);
/** @var array $aVal */ /** @var array $aVal */
foreach ($aMap as $key => $aVal) { foreach($aMap as $key => $aVal)
if (array_key_exists($key, $aMap)) { {
if (array_key_exists($key, $aMap))
{
$aMap[$key] = array_merge($aVal, $aRes[$key]); $aMap[$key] = array_merge($aVal, $aRes[$key]);
} }
} }
@@ -505,14 +513,17 @@ CSS;
$aMap = static::ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFiles); $aMap = static::ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFiles);
$aImages = []; $aImages = [];
foreach ($aMap ['aCompleteUrls'] as $sUri => $sUrl) { foreach ($aMap ['aCompleteUrls'] as $sUri => $sUrl)
{
$sImg = $sUrl; $sImg = $sUrl;
if (preg_match("/(.*)\?/", $sUrl, $aMatches)) { if (preg_match("/(.*)\?/", $sUrl, $aMatches))
{
$sImg=$aMatches[1]; $sImg=$aMatches[1];
} }
if (static::HasImageExtension($sImg) if (static::HasImageExtension($sImg)
&& ! array_key_exists($sImg, $aImages)) { && ! array_key_exists($sImg, $aImages))
{
$sFilePath = utils::RealPath($sImg, APPROOT); $sFilePath = utils::RealPath($sImg, APPROOT);
if ($sFilePath !== false) { if ($sFilePath !== false) {
$sFilePathWithSlashes = str_replace('\\', '/', $sFilePath); $sFilePathWithSlashes = str_replace('\\', '/', $sFilePath);
@@ -544,7 +555,7 @@ CSS;
public static function CanonicalizePath($path) public static function CanonicalizePath($path)
{ {
$path = explode('/', str_replace('//','/', $path)); $path = explode('/', str_replace('//','/', $path));
$stack = []; $stack = array();
foreach ($path as $seg) { foreach ($path as $seg) {
if ($seg == '..') { if ($seg == '..') {
// Ignore this segment, remove last segment from stack // Ignore this segment, remove last segment from stack
@@ -575,8 +586,10 @@ CSS;
public static function ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFile) public static function ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFile)
{ {
$sContent=""; $sContent="";
foreach ($aStylesheetFile as $sStylesheetFile) { foreach ($aStylesheetFile as $sStylesheetFile)
if (is_file($sStylesheetFile)) { {
if (is_file($sStylesheetFile))
{
$sContent .= '\n' . file_get_contents($sStylesheetFile); $sContent .= '\n' . file_get_contents($sStylesheetFile);
} }
} }
@@ -609,26 +622,40 @@ CSS;
public static function FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent, $bForceEmptyValueWhenNotFound=false) public static function FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent, $bForceEmptyValueWhenNotFound=false)
{ {
$aNewMissingVars = []; $aNewMissingVars = [];
if (!empty($aMissingVariables)) { if (!empty($aMissingVariables))
foreach ($aMissingVariables as $var) { {
if (array_key_exists($var, $aThemeParametersVariables)) { foreach ($aMissingVariables as $var)
{
if (array_key_exists($var, $aThemeParametersVariables))
{
$aFoundVariables[$var] = $aThemeParametersVariables[$var]; $aFoundVariables[$var] = $aThemeParametersVariables[$var];
} else { }
if (preg_match_all("/\\\$$var\s*:\s*[\"']{0,1}(.*)[\"']{0,1};/", $sContent, $aValues)) { else
{
if (preg_match_all("/\\\$$var\s*:\s*[\"']{0,1}(.*)[\"']{0,1};/", $sContent, $aValues))
{
$sValue = $aValues[1][0]; $sValue = $aValues[1][0];
if (preg_match_all("/([^!]+)!/", $sValue, $aSubValues)) { if (preg_match_all("/([^!]+)!/", $sValue, $aSubValues))
{
$sValue = trim($aSubValues[1][0], ' "\''); $sValue = trim($aSubValues[1][0], ' "\'');
} }
if (strpos($sValue, '$') === false) { if (strpos($sValue, '$') === false)
{
$aFoundVariables[$var] = $sValue; $aFoundVariables[$var] = $sValue;
} else { }
else{
$aNewMissingVars[] = $var; $aNewMissingVars[] = $var;
} }
} else { }
if ($bForceEmptyValueWhenNotFound) { else
{
if ($bForceEmptyValueWhenNotFound)
{
$aFoundVariables[$var] = ''; $aFoundVariables[$var] = '';
} else { }
else
{
$aNewMissingVars[] = $var; $aNewMissingVars[] = $var;
} }
} }
@@ -649,23 +676,32 @@ CSS;
*/ */
public static function ResolveUrls($aFoundVariables, array $aToCompleteUrls, array $aCompleteUrls) public static function ResolveUrls($aFoundVariables, array $aToCompleteUrls, array $aCompleteUrls)
{ {
if (!empty($aFoundVariables)) { if (!empty($aFoundVariables))
{
$aFoundVariablesWithEmptyValue = []; $aFoundVariablesWithEmptyValue = [];
foreach ($aFoundVariables as $aFoundVariable => $sValue) { foreach ($aFoundVariables as $aFoundVariable => $sValue)
{
$aFoundVariablesWithEmptyValue[$aFoundVariable] = ''; $aFoundVariablesWithEmptyValue[$aFoundVariable] = '';
} }
foreach ($aToCompleteUrls as $sUrlTemplate) { foreach ($aToCompleteUrls as $sUrlTemplate)
{
unset($aToCompleteUrls[$sUrlTemplate]); unset($aToCompleteUrls[$sUrlTemplate]);
$sResolvedUrl = static::ResolveUrl($sUrlTemplate, $aFoundVariables); $sResolvedUrl = static::ResolveUrl($sUrlTemplate, $aFoundVariables);
if ($sResolvedUrl == false) { if ($sResolvedUrl == false)
{
$aToCompleteUrls[$sUrlTemplate] = $sUrlTemplate; $aToCompleteUrls[$sUrlTemplate] = $sUrlTemplate;
} else { }
else
{
$sUri = static::ResolveUrl($sUrlTemplate, $aFoundVariablesWithEmptyValue); $sUri = static::ResolveUrl($sUrlTemplate, $aFoundVariablesWithEmptyValue);
$aExplodedUri = explode('?', $sUri); $aExplodedUri = explode('?', $sUri);
if (empty($aExplodedUri)) { if (empty($aExplodedUri))
{
$aCompleteUrls[$sUri] = $sResolvedUrl; $aCompleteUrls[$sUri] = $sResolvedUrl;
} else { }
else
{
$aCompleteUrls[$aExplodedUri[0]] = $sResolvedUrl; $aCompleteUrls[$aExplodedUri[0]] = $sResolvedUrl;
} }
} }
@@ -690,31 +726,41 @@ CSS;
$aMissingVariables = []; $aMissingVariables = [];
$aFoundVariables = []; $aFoundVariables = [];
if (is_file($sStylesheetFile)) { if (is_file($sStylesheetFile))
{
$sContent = file_get_contents($sStylesheetFile); $sContent = file_get_contents($sStylesheetFile);
if (preg_match_all("/url\s*\((.*)\)/", $sContent, $aMatches)) { if (preg_match_all("/url\s*\((.*)\)/", $sContent, $aMatches))
foreach ($aMatches[1] as $path) { {
foreach ($aMatches[1] as $path)
{
$iRemainingClosingParenthesisPos = strpos($path, ')'); $iRemainingClosingParenthesisPos = strpos($path, ')');
if ($iRemainingClosingParenthesisPos !== false){ if ($iRemainingClosingParenthesisPos !== false){
$path = substr($path, 0, $iRemainingClosingParenthesisPos); $path = substr($path, 0, $iRemainingClosingParenthesisPos);
} }
if (!array_key_exists($path, $aCompleteUrls) if (!array_key_exists($path, $aCompleteUrls)
&& !array_key_exists($path, $aToCompleteUrls)) { && !array_key_exists($path, $aToCompleteUrls))
if (preg_match_all("/\\$([\w\-_]+)/", $path, $aCurrentVars)) { {
if (preg_match_all("/\\$([\w\-_]+)/", $path, $aCurrentVars))
{
/** @var string $aCurrentVars */ /** @var string $aCurrentVars */
foreach ($aCurrentVars[1] as $var) { foreach ($aCurrentVars[1] as $var)
if (!array_key_exists($var, $aMissingVariables)) { {
if (!array_key_exists($var, $aMissingVariables))
{
$aMissingVariables[$var] = $var; $aMissingVariables[$var] = $var;
} }
} }
$aToCompleteUrls[$path] = $path; $aToCompleteUrls[$path] = $path;
} else { }
else
{
$aCompleteUrls[$path] = trim($path, "\"'"); $aCompleteUrls[$path] = trim($path, "\"'");
} }
} }
} }
} }
if (!empty($aMissingVariables)) { if (!empty($aMissingVariables))
{
list($aMissingVariables, $aFoundVariables) = static::FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent); list($aMissingVariables, $aFoundVariables) = static::FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent);
list($aToCompleteUrls, $aCompleteUrls) = static::ResolveUrls($aFoundVariables, $aToCompleteUrls, $aCompleteUrls); list($aToCompleteUrls, $aCompleteUrls) = static::ResolveUrls($aFoundVariables, $aToCompleteUrls, $aCompleteUrls);
} }
@@ -724,7 +770,7 @@ CSS;
'aCompleteUrls' => $aCompleteUrls, 'aCompleteUrls' => $aCompleteUrls,
'aToCompleteUrls' => $aToCompleteUrls, 'aToCompleteUrls' => $aToCompleteUrls,
'aMissingVariables' => $aMissingVariables, 'aMissingVariables' => $aMissingVariables,
'aFoundVariables' => $aFoundVariables, 'aFoundVariables' => $aFoundVariables
]; ];
} }
@@ -740,7 +786,8 @@ CSS;
{ {
$aPattern= []; $aPattern= [];
$aReplacement= []; $aReplacement= [];
foreach ($aFoundVariables as $aFoundVariable => $aFoundVariableValue) { foreach ($aFoundVariables as $aFoundVariable => $aFoundVariableValue)
{
//XX + $key + YY //XX + $key + YY
$aPattern[]="/['\"]\s*\+\s*\\\$" . $aFoundVariable . "[\s\+]+\s*['\"]/"; $aPattern[]="/['\"]\s*\+\s*\\\$" . $aFoundVariable . "[\s\+]+\s*['\"]/";
$aReplacement[]=$aFoundVariableValue; $aReplacement[]=$aFoundVariableValue;
@@ -752,7 +799,8 @@ CSS;
$aReplacement[]=$aFoundVariableValue; $aReplacement[]=$aFoundVariableValue;
} }
$sResolvedUrl=preg_replace($aPattern, $aReplacement, $sUrlTemplate); $sResolvedUrl=preg_replace($aPattern, $aReplacement, $sUrlTemplate);
if (strpos($sResolvedUrl, "+") !== false) { if (strpos($sResolvedUrl, "+")!==false)
{
return false; return false;
} }
return trim($sResolvedUrl, "\"'"); return trim($sResolvedUrl, "\"'");
@@ -766,14 +814,17 @@ CSS;
*/ */
private static function HasImageExtension($path) private static function HasImageExtension($path)
{ {
foreach (static::IMAGE_EXTENSIONS as $sExt) { foreach (static::IMAGE_EXTENSIONS as $sExt)
if (endsWith($path, $sExt)) { {
if (endsWith($path, $sExt))
{
return true; return true;
} }
} }
return false; return false;
} }
/** /**
* @since 3.0.0 N°2982 * @since 3.0.0 N°2982
* Extract the signature for a generated CSS file. * Extract the signature for a generated CSS file.
@@ -792,13 +843,16 @@ CSS;
$iCount = 0; $iCount = 0;
$sPreviousLine = ''; $sPreviousLine = '';
$hFile = @fopen($sFilepath, "r"); $hFile = @fopen($sFilepath, "r");
if ($hFile !== false) { if ($hFile !== false)
{
$sLine = ''; $sLine = '';
do { do
{
$iCount++; $iCount++;
$sPreviousLine = $sLine; $sPreviousLine = $sLine;
$sLine = rtrim(fgets($hFile)); // Remove the trailing \n $sLine = rtrim(fgets($hFile)); // Remove the trailing \n
} while (($sLine !== false) && ($sLine != '=== SIGNATURE END ===') && ($iCount <= 100)); }
while (($sLine !== false) && ($sLine != '=== SIGNATURE END ===') && ($iCount <= 100));
fclose($hFile); fclose($hFile);
} }
return $sPreviousLine; return $sPreviousLine;
@@ -813,7 +867,8 @@ CSS;
public static function GetVarSignature($JsonSignature) public static function GetVarSignature($JsonSignature)
{ {
$aJsonArray = json_decode($JsonSignature, true); $aJsonArray = json_decode($JsonSignature, true);
if (array_key_exists('variables', $aJsonArray)) { if (array_key_exists('variables', $aJsonArray))
{
return $aJsonArray['variables']; return $aJsonArray['variables'];
} }
return false; return false;
@@ -837,7 +892,8 @@ CSS;
$oFindStylesheetObject->ResetLastStyleSheet(); $oFindStylesheetObject->ResetLastStyleSheet();
} }
foreach ($aImportsPaths as $sPath) { foreach($aImportsPaths as $sPath)
{
$sAlterableFileURI = $sFileURI; $sAlterableFileURI = $sFileURI;
$sFilePath = $sPath.'/'.$sAlterableFileURI; $sFilePath = $sPath.'/'.$sAlterableFileURI;
$sImportedFile = realpath($sFilePath); $sImportedFile = realpath($sFilePath);
@@ -862,7 +918,8 @@ CSS;
} }
if ((file_exists($sImportedFile)) if ((file_exists($sImportedFile))
&& (!$oFindStylesheetObject->AlreadyFetched($sImportedFile))) { && (!$oFindStylesheetObject->AlreadyFetched($sImportedFile)))
{
if ($bImports){ if ($bImports){
$oFindStylesheetObject->AddImport($sAlterableFileURI, $sImportedFile); $oFindStylesheetObject->AddImport($sAlterableFileURI, $sImportedFile);
}else{ }else{
@@ -895,7 +952,8 @@ CSS;
{ {
$iPos = strrpos($sSubject, $sSearch); $iPos = strrpos($sSubject, $sSearch);
if ($iPos !== false) { if($iPos !== false)
{
$sSubject = substr_replace($sSubject, $sReplace, $iPos, strlen($sSearch)); $sSubject = substr_replace($sSubject, $sReplace, $iPos, strlen($sSearch));
} }
@@ -924,21 +982,22 @@ CSS;
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths) public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths)
{ {
$aThemeParametersVariable = []; $aThemeParametersVariable = [];
if (array_key_exists('variables', $aThemeParameters))
{
if (is_array($aThemeParameters['variables']))
{
$aThemeParametersVariable = array_merge([], $aThemeParameters['variables']);
}
}
if (array_key_exists('variable_imports', $aThemeParameters)) { if (array_key_exists('variable_imports', $aThemeParameters))
if (is_array($aThemeParameters['variable_imports'])) { {
if (is_array($aThemeParameters['variable_imports']))
{
$aThemeParametersVariable = array_merge($aThemeParametersVariable, static::GetVariablesFromFile($aThemeParameters['variable_imports'], $aImportsPaths)); $aThemeParametersVariable = array_merge($aThemeParametersVariable, static::GetVariablesFromFile($aThemeParameters['variable_imports'], $aImportsPaths));
} }
} }
// Variables defined in theme XML have the priority over variables defined in XML imports files
// They're defined after so they overwrite previous parameters
if (array_key_exists('variables', $aThemeParameters)) {
if (is_array($aThemeParameters['variables'])) {
$aThemeParametersVariable = array_merge($aThemeParametersVariable, $aThemeParameters['variables']);
}
}
$aThemeParametersVariable['$version'] = $bSetupCompilationTimestamp; $aThemeParametersVariable['$version'] = $bSetupCompilationTimestamp;
return $aThemeParametersVariable; return $aThemeParametersVariable;
} }
@@ -950,10 +1009,10 @@ CSS;
* @return array * @return array
* @since 3.0.0 N°3593 * @since 3.0.0 N°3593
*/ */
public static function GetVariablesFromFile($aVariableFiles, $aImportsPaths) public static function GetVariablesFromFile($aVariableFiles, $aImportsPaths){
{
$aVariablesResults = []; $aVariablesResults = [];
foreach ($aVariableFiles as $sVariableFile) { foreach ($aVariableFiles as $sVariableFile)
{
foreach($aImportsPaths as $sPath) { foreach($aImportsPaths as $sPath) {
$sFilePath = $sPath.'/'.$sVariableFile; $sFilePath = $sPath.'/'.$sVariableFile;
$sImportedFile = realpath($sFilePath); $sImportedFile = realpath($sFilePath);
@@ -970,10 +1029,9 @@ CSS;
} }
} }
} }
array_map(function ($sVariableValue) { array_map( function($sVariableValue) { return ltrim($sVariableValue); }, $aVariablesResults );
return ltrim($sVariableValue);
}, $aVariablesResults);
return $aVariablesResults; return $aVariablesResults;
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
/** /**
* Copyright (C) 2013-2024 Combodo SAS * Copyright (C) 2013-2024 Combodo SAS
* *
@@ -30,8 +29,7 @@ class ThemeHandlerService
{ {
} }
public function CompileTheme($sThemeId, $bSetup = false, $sSetupCompilationTimestamp = "", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null) public function CompileTheme($sThemeId, $bSetup = false, $sSetupCompilationTimestamp = "", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null){
{
return ThemeHandler::CompileTheme($sThemeId, $bSetup, $sSetupCompilationTimestamp, $aThemeParameters, $aImportsPaths, $sWorkingPath); return ThemeHandler::CompileTheme($sThemeId, $bSetup, $sSetupCompilationTimestamp, $aThemeParameters, $aImportsPaths, $sWorkingPath);
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -38,11 +37,13 @@ class privUITransaction
public static function GetNewTransactionId() public static function GetNewTransactionId()
{ {
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled'); $bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled) { if (!$bTransactionsEnabled)
{
return 'notransactions'; // Any value will do return 'notransactions'; // Any value will do
} }
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage'); $sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false)) { if (!class_exists($sClass, false))
{
IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions."); IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions.");
$sClass = 'privUITransactionSession'; $sClass = 'privUITransactionSession';
} }
@@ -61,11 +62,13 @@ class privUITransaction
public static function IsTransactionValid($id, $bRemoveTransaction = true) public static function IsTransactionValid($id, $bRemoveTransaction = true)
{ {
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled'); $bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled) { if (!$bTransactionsEnabled)
{
return true; // All values are valid return true; // All values are valid
} }
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage'); $sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false)) { if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession'; $sClass = 'privUITransactionSession';
} }
@@ -80,11 +83,13 @@ class privUITransaction
public static function RemoveTransaction($id) public static function RemoveTransaction($id)
{ {
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled'); $bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled) { if (!$bTransactionsEnabled)
{
return; // Nothing to do return; // Nothing to do
} }
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage'); $sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false)) { if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession'; $sClass = 'privUITransactionSession';
} }
@@ -109,13 +114,14 @@ class privUITransactionSession
*/ */
public static function GetNewTransactionId() public static function GetNewTransactionId()
{ {
if (!Session::IsSet('transactions')) { if (!Session::IsSet('transactions'))
{
Session::Set('transactions', []); Session::Set('transactions', []);
} }
// Strictly speaking, the two lines below should be grouped together // Strictly speaking, the two lines below should be grouped together
// by a critical section // by a critical section
// sem_acquire($rSemIdentified); // sem_acquire($rSemIdentified);
$id = static::GetUserPrefix().str_replace(['.', ' '], '', microtime()); $id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime());
Session::Set(['transactions', $id], true); Session::Set(['transactions', $id], true);
// sem_release($rSemIdentified); // sem_release($rSemIdentified);
@@ -133,13 +139,16 @@ class privUITransactionSession
public static function IsTransactionValid($id, $bRemoveTransaction = true) public static function IsTransactionValid($id, $bRemoveTransaction = true)
{ {
$bResult = false; $bResult = false;
if (Session::IsSet('transactions')) { if (Session::IsSet('transactions'))
{
// Strictly speaking, the eight lines below should be grouped together // Strictly speaking, the eight lines below should be grouped together
// inside the same critical section as above // inside the same critical section as above
// sem_acquire($rSemIdentified); // sem_acquire($rSemIdentified);
if (Session::IsSet(['transactions', $id])) { if (Session::IsSet(['transactions', $id]))
{
$bResult = true; $bResult = true;
if ($bRemoveTransaction) { if ($bRemoveTransaction)
{
Session::Unset(['transactions', $id]); Session::Unset(['transactions', $id]);
} }
} }
@@ -155,11 +164,13 @@ class privUITransactionSession
*/ */
public static function RemoveTransaction($id) public static function RemoveTransaction($id)
{ {
if (Session::IsSet('transactions')) { if (Session::IsSet('transactions'))
{
// Strictly speaking, the three lines below should be grouped together // Strictly speaking, the three lines below should be grouped together
// inside the same critical section as above // inside the same critical section as above
// sem_acquire($rSemIdentified); // sem_acquire($rSemIdentified);
if (Session::IsSet(['transactions', $id])) { if (Session::IsSet(['transactions', $id]))
{
Session::Unset(['transactions', $id]); Session::Unset(['transactions', $id]);
} }
// sem_release($rSemIdentified); // sem_release($rSemIdentified);
@@ -186,7 +197,7 @@ class privUITransactionSession
class privUITransactionFile class privUITransactionFile
{ {
/** @var int Value to use when no user logged */ /** @var int Value to use when no user logged */
public const UNAUTHENTICATED_USER_ID = -666; const UNAUTHENTICATED_USER_ID = -666;
/** /**
* @return int current user id, or {@see self::UNAUTHENTICATED_USER_ID} if no user logged * @return int current user id, or {@see self::UNAUTHENTICATED_USER_ID} if no user logged
@@ -217,18 +228,22 @@ class privUITransactionFile
*/ */
public static function GetNewTransactionId() public static function GetNewTransactionId()
{ {
if (!is_dir(utils::GetDataPath().'transactions')) { if (!is_dir(utils::GetDataPath().'transactions'))
if (!is_writable(APPROOT.'data')) { {
if (!is_writable(APPROOT.'data'))
{
throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.'); throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.');
} }
// condition avoids race condition N°2345 // condition avoids race condition N°2345
// See https://github.com/kalessil/phpinspectionsea/blob/master/docs/probable-bugs.md#mkdir-race-condition // See https://github.com/kalessil/phpinspectionsea/blob/master/docs/probable-bugs.md#mkdir-race-condition
if (!mkdir($concurrentDirectory = utils::GetDataPath().'transactions') && !is_dir($concurrentDirectory)) { if (!mkdir($concurrentDirectory = utils::GetDataPath().'transactions') && !is_dir($concurrentDirectory))
{
throw new Exception('Failed to create the directory "'.utils::GetDataPath().'transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.'); throw new Exception('Failed to create the directory "'.utils::GetDataPath().'transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.');
} }
} }
if (!is_writable(utils::GetDataPath().'transactions')) { if (!is_writable(utils::GetDataPath().'transactions'))
{
throw new Exception('The directory "'.utils::GetDataPath().'transactions" must be writable to the application.'); throw new Exception('The directory "'.utils::GetDataPath().'transactions" must be writable to the application.');
} }
@@ -262,7 +277,8 @@ class privUITransactionFile
// Constraint the transaction file within utils::GetDataPath().'transactions' // Constraint the transaction file within utils::GetDataPath().'transactions'
$sTransactionDir = realpath(utils::GetDataPath().'transactions'); $sTransactionDir = realpath(utils::GetDataPath().'transactions');
$sFilepath = utils::RealPath($sTransactionDir.'/'.$id, $sTransactionDir); $sFilepath = utils::RealPath($sTransactionDir.'/'.$id, $sTransactionDir);
if (($sFilepath === false) || (strlen($sTransactionDir) == strlen($sFilepath))) { if (($sFilepath === false) || (strlen($sTransactionDir) == strlen($sFilepath)))
{
return false; return false;
} }
@@ -281,11 +297,15 @@ class privUITransactionFile
return false; return false;
} }
if ($bRemoveTransaction) { if ($bRemoveTransaction)
{
$bResult = @unlink($sFilepath); $bResult = @unlink($sFilepath);
if (!$bResult) { if (!$bResult)
{
self::Error('IsTransactionValid: FAILED to remove transaction '.$id); self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
} else { }
else
{
self::Info('IsTransactionValid: OK. Removed transaction: '.$id); self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
} }
} }
@@ -330,8 +350,10 @@ class privUITransactionFile
$iLimit = time() - 24*3600; $iLimit = time() - 24*3600;
$sPattern = $sTransactionDir ? "$sTransactionDir/*" : utils::GetDataPath().'transactions/*'; $sPattern = $sTransactionDir ? "$sTransactionDir/*" : utils::GetDataPath().'transactions/*';
$aTransactions = glob($sPattern); $aTransactions = glob($sPattern);
foreach ($aTransactions as $sFileName) { foreach($aTransactions as $sFileName)
if (filemtime($sFileName) < $iLimit) { {
if (filemtime($sFileName) < $iLimit)
{
@unlink($sFileName); @unlink($sFileName);
self::Info('CleanupOldTransactions: Deleted transaction: '.$sFileName); self::Info('CleanupOldTransactions: Deleted transaction: '.$sFileName);
} }
@@ -345,9 +367,10 @@ class privUITransactionFile
protected static function GetPendingTransactions() protected static function GetPendingTransactions()
{ {
clearstatcache(); clearstatcache();
$aResult = []; $aResult = array();
$aTransactions = glob(utils::GetDataPath().'transactions/'.self::GetUserPrefix().'*'); $aTransactions = glob(utils::GetDataPath().'transactions/'.self::GetUserPrefix().'*');
foreach ($aTransactions as $sFileName) { foreach($aTransactions as $sFileName)
{
$aResult[] = date('Y-m-d H:i:s', filemtime($sFileName)).' - '.basename($sFileName); $aResult[] = date('Y-m-d H:i:s', filemtime($sFileName)).' - '.basename($sFileName);
} }
sort($aResult); sort($aResult);
@@ -381,8 +404,7 @@ class privUITransactionFile
self::Write('Error | '.$sText); self::Write('Error | '.$sText);
} }
protected static function IsLogEnabled() protected static function IsLogEnabled() {
{
$oConfig = MetaModel::GetConfig(); $oConfig = MetaModel::GetConfig();
if (is_null($oConfig)) { if (is_null($oConfig)) {
return false; return false;

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -55,8 +54,8 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
*/ */
class UIExtKeyWidget class UIExtKeyWidget
{ {
public const ENUM_OUTPUT_FORMAT_CSV = 'csv'; const ENUM_OUTPUT_FORMAT_CSV = 'csv';
public const ENUM_OUTPUT_FORMAT_JSON = 'json'; const ENUM_OUTPUT_FORMAT_JSON = 'json';
protected $iId; protected $iId;
protected $sTargetClass; protected $sTargetClass;
@@ -88,20 +87,10 @@ class UIExtKeyWidget
* @since 2.7.7 3.0.1 3.1.0 N°3129 Add default value for $aArgs for PHP 8.0 compat * @since 2.7.7 3.0.1 3.1.0 N°3129 Add default value for $aArgs for PHP 8.0 compat
*/ */
public static function DisplayFromAttCode( public static function DisplayFromAttCode(
$oPage, $oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '',
$sAttCode, $aArgs = [], $bSearchMode = false, &$sInputType = ''
$sClass, )
$sTitle, {
$oAllowedValues,
$value,
$iInputId,
$bMandatory,
$sFieldName = '',
$sFormPrefix = '',
$aArgs = [],
$bSearchMode = false,
&$sInputType = ''
) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sTargetClass = $oAttDef->GetTargetClass(); $sTargetClass = $oAttDef->GetTargetClass();
$iMaxComboLength = $oAttDef->GetMaximumComboLength(); $iMaxComboLength = $oAttDef->GetMaximumComboLength();
@@ -113,7 +102,8 @@ class UIExtKeyWidget
} }
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode); $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
if (!$bSearchMode) { if (!$bSearchMode) {
switch ($sDisplayStyle) { switch ($sDisplayStyle)
{
case 'radio': case 'radio':
case 'radio_horizontal': case 'radio_horizontal':
case 'radio_vertical': case 'radio_vertical':
@@ -124,38 +114,12 @@ class UIExtKeyWidget
case 'select': case 'select':
case 'list': case 'list':
default: default:
return $oWidget->DisplaySelect( return $oWidget->DisplaySelect($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value,
$oPage, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, $sInputType);
$iMaxComboLength,
$bAllowTargetCreation,
$sTitle,
$oAllowedValues,
$value,
$bMandatory,
$sFieldName,
$sFormPrefix,
$aArgs,
$sInputType
);
} }
} else { } else {
return $oWidget->Display( return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId,
$oPage, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle, true, $sInputType);
$iMaxComboLength,
$bAllowTargetCreation,
$sTitle,
$oAllowedValues,
$value,
$iInputId,
$bMandatory,
$sFieldName,
$sFormPrefix,
$aArgs,
null,
$sDisplayStyle,
true,
$sInputType
);
} }
} }
@@ -194,7 +158,7 @@ class UIExtKeyWidget
* @since 3.0.0 N°2508 - Include Obsolescence icon within list and autocomplete * @since 3.0.0 N°2508 - Include Obsolescence icon within list and autocomplete
* @since 3.0.0 N°3750 new $sInputType parameter * @since 3.0.0 N°3750 new $sInputType parameter
*/ */
public function DisplaySelect(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = [], &$sInputType = '') public function DisplaySelect(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), &$sInputType = '')
{ {
$sTitle = addslashes($sTitle); $sTitle = addslashes($sTitle);
$oPage->LinkScriptFromAppRoot('js/extkeywidget.js'); $oPage->LinkScriptFromAppRoot('js/extkeywidget.js');
@@ -317,17 +281,21 @@ EOF
$oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );"); $oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );");
} }
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">"; $sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
} else { }
else
{
// Too many choices, use an autocomplete // Too many choices, use an autocomplete
// Check that the given value is allowed // Check that the given value is allowed
$oSearch = $oAllowedValues->GetFilter(); $oSearch = $oAllowedValues->GetFilter();
$oSearch->AddCondition('id', $value); $oSearch->AddCondition('id', $value);
$oSet = new DBObjectSet($oSearch); $oSet = new DBObjectSet($oSearch);
if ($oSet->Count() == 0) { if ($oSet->Count() == 0)
{
$value = null; $value = null;
} }
if (is_null($value) || ($value == 0)) { // Null values are displayed as '' if (is_null($value) || ($value == 0)) // Null values are displayed as ''
{
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : ''; $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
} else { } else {
$sDisplayValue = $this->GetObjectName($value); $sDisplayValue = $this->GetObjectName($value);
@@ -408,30 +376,36 @@ JS
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">"; $sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
if (is_null($oAllowedValues)) { if (is_null($oAllowedValues))
{
throw new Exception('Implementation: null value for allowed values definition'); throw new Exception('Implementation: null value for allowed values definition');
} }
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData()); $oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count. // We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
if (!$oAllowedValues->CountExceeds($iMaxComboLength)) { if (!$oAllowedValues->CountExceeds($iMaxComboLength))
{
// Discrete list of values, use a SELECT or RADIO buttons depending on the config // Discrete list of values, use a SELECT or RADIO buttons depending on the config
$sValidationField = null; $sValidationField = null;
$bVertical = ($sDisplayStyle != 'radio_horizontal'); $bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false; $bExtensions = false;
$oAllowedValues->Rewind(); $oAllowedValues->Rewind();
$aAllowedValues = []; $aAllowedValues = array();
while ($oObj = $oAllowedValues->Fetch()) { while($oObj = $oAllowedValues->Fetch())
{
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName(); $aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
} }
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField); $sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
$aEventsList[] ='change'; $aEventsList[] ='change';
} else { }
else
{
$sHTMLValue .= "unable to display. Too much values"; $sHTMLValue .= "unable to display. Too much values";
} }
$sHTMLValue .= '<div class="ibo-input-select--action-buttons">'; $sHTMLValue .= '<div class="ibo-input-select--action-buttons">';
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) { if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>"; $sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
$oPage->add_ready_script( $oPage->add_ready_script(
<<<JS <<<JS
@@ -442,7 +416,8 @@ JS
JS JS
); );
} }
if ($bCreate && $bExtensions) { if ($bCreate && $bExtensions)
{
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject'; $sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>"; $sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
@@ -496,7 +471,7 @@ JS
* *
* @since 3.0.0 N°3750 new $sInputType parameter * @since 3.0.0 N°3750 new $sInputType parameter
*/ */
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = [], $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true, &$sInputType = '') public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true, &$sInputType = '')
{ {
if (!is_null($bSearchMode)) { if (!is_null($bSearchMode)) {
$this->bSearchMode = $bSearchMode; $this->bSearchMode = $bSearchMode;
@@ -546,7 +521,7 @@ JS
$bVertical = ($sDisplayStyle != 'radio_horizontal'); $bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false; $bExtensions = false;
$oAllowedValues->Rewind(); $oAllowedValues->Rewind();
$aAllowedValues = []; $aAllowedValues = array();
while ($oObj = $oAllowedValues->Fetch()) { while ($oObj = $oAllowedValues->Fetch()) {
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName(); $aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
} }
@@ -599,14 +574,14 @@ EOF
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW; $sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW;
if (($this->bSearchMode) && $bSearchMultiple) { if (($this->bSearchMode) && $bSearchMultiple) {
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_MULTIPLE_CHOICES; $sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_MULTIPLE_CHOICES;
$aOptions = [ $aOptions = array(
'header' => true, 'header' => true,
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'), 'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'), 'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
'noneSelectedText' => Dict::S('UI:SearchValue:Any'), 'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
'selectedText' => Dict::S('UI:SearchValue:NbSelected'), 'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
'selectedList' => 1, 'selectedList' => 1,
]; );
$sJSOptions = json_encode($aOptions); $sJSOptions = json_encode($aOptions);
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);"); $oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
} }
@@ -631,7 +606,8 @@ EOF
$value = null; $value = null;
} }
if (is_null($value) || ($value == 0)) { // Null values are displayed as '' if (is_null($value) || ($value == 0)) // Null values are displayed as ''
{
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : ''; $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
} else { } else {
$sDisplayValue = $this->GetObjectName($value); $sDisplayValue = $this->GetObjectName($value);
@@ -697,22 +673,20 @@ JS
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode); $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
/** @var \DBObject $oCurrObject */ /** @var \DBObject $oCurrObject */
$aArgs = $oCurrObject->ToArgsForQuery(); $aArgs = $oCurrObject->ToArgsForQuery();
$aParams = ['query_params' => $aArgs]; $aParams = array('query_params' => $aArgs);
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs); $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs);
$oFilter = $oSet->GetFilter(); $oFilter = $oSet->GetFilter();
} else if (!empty($this->sFilter)) { } else if (!empty($this->sFilter)) {
$aParams = []; $aParams = array();
$oFilter = DBObjectSearch::FromOQL($this->sFilter); $oFilter = DBObjectSearch::FromOQL($this->sFilter);
} else { } else {
$aParams = []; $aParams = array();
$oFilter = new DBObjectSearch($this->sTargetClass); $oFilter = new DBObjectSearch($this->sTargetClass);
} }
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams); $oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$oPage->AddUiBlock($oBlock->GetDisplay( $oPage->AddUiBlock($oBlock->GetDisplay($oPage, 'dtc_'.$this->iId,
$oPage, array(
'dtc_'.$this->iId,
[
'menu' => false, 'menu' => false,
'currentId' => $this->iId, 'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}", 'table_id' => "dr_{$this->iId}",
@@ -720,13 +694,12 @@ JS
'selection_mode' => true, 'selection_mode' => true,
'selection_type' => 'single', 'selection_type' => 'single',
'cssCount' => '#count_'.$this->iId.'_results', 'cssCount' => '#count_'.$this->iId.'_results',
] )
)); ));
$sCancel = Dict::S('UI:Button:Cancel'); $sCancel = Dict::S('UI:Button:Cancel');
$sOK = Dict::S('UI:Button:Ok'); $sOK = Dict::S('UI:Button:Ok');
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm'); $sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$oPage->add( $oPage->add(<<<HTML
<<<HTML
<form id="fr_{$this->iId}" OnSubmit="return oACWidget_{$this->iId}.DoOk();"> <form id="fr_{$this->iId}" OnSubmit="return oACWidget_{$this->iId}.DoOk();">
<div id="dr_{$this->iId}"> <div id="dr_{$this->iId}">
<div><p>{$sEmptyList}</p></div> <div><p>{$sEmptyList}</p></div>
@@ -738,8 +711,7 @@ HTML
); );
$sDialogTitleSanitized = addslashes(utils::HtmlToText($sTitle)); $sDialogTitleSanitized = addslashes(utils::HtmlToText($sTitle));
$oPage->add_ready_script( $oPage->add_ready_script(<<<JS
<<<JS
$('#ac_dlg_{$this->iId}').dialog({ $('#ac_dlg_{$this->iId}').dialog({
width: $(window).width()*0.8, width: $(window).width()*0.8,
height: $(window).height()*0.8, height: $(window).height()*0.8,
@@ -779,12 +751,14 @@ JS
*/ */
public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null) public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null)
{ {
if (is_null($sFilter)) { if (is_null($sFilter))
{
throw new Exception('Implementation: null value for allowed values definition'); throw new Exception('Implementation: null value for allowed values definition');
} }
$oFilter = DBObjectSearch::FromOQL($sFilter); $oFilter = DBObjectSearch::FromOQL($sFilter);
if (strlen($sRemoteClass) > 0) { if (strlen($sRemoteClass) > 0)
{
$oFilter->ChangeClass($sRemoteClass); $oFilter->ChangeClass($sRemoteClass);
} }
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
@@ -792,8 +766,8 @@ JS
// Current extkey value, so we can display event if it is not available anymore (eg. archived). // Current extkey value, so we can display event if it is not available anymore (eg. archived).
$iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode); $iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
$oBlock = new DisplayBlock($oFilter, 'list_search', false, ['query_params' => ['this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId]]); $oBlock = new DisplayBlock($oFilter, 'list_search', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
$oBlock->Display($oP, $this->iId.'_results', ['this' => $oObj, 'cssCount' => '#count_'.$this->iId.'_results', 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode]); // Don't display the 'Actions' menu on the results $oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId.'_results', 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
} }
/** /**
@@ -825,32 +799,38 @@ JS
$oValuesSet->SetSort(false); $oValuesSet->SetSort(false);
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); $oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oValuesSet->SetLimit($iMax); $oValuesSet->SetLimit($iMax);
$aValuesStartWith = $oValuesSet->GetValuesForAutocomplete(['this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId], $sContains, 'start_with'); $aValuesStartWith = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
asort($aValuesStartWith); asort($aValuesStartWith);
$aValues = $aValuesStartWith; $aValues = $aValuesStartWith;
if (sizeof($aValues) < $iMax) { if (sizeof($aValues) < $iMax) {
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(['this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId], $sContains, 'contains'); $aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
asort($aValuesContains); asort($aValuesContains);
$iSize = sizeof($aValues); $iSize = sizeof($aValues);
foreach ($aValuesContains as $sKey => $sFriendlyName) { foreach ($aValuesContains as $sKey => $sFriendlyName)
if (!isset($aValues[$sKey])) { {
if (!isset($aValues[$sKey]))
{
$aValues[$sKey] = $sFriendlyName; $aValues[$sKey] = $sFriendlyName;
if (++$iSize >= $iMax) { if (++$iSize >= $iMax)
{
break; break;
} }
} }
} }
} elseif (!in_array($sContains, $aValues)) { }
$aValuesEquals = $oValuesSet->GetValuesForAutocomplete(['this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId], $sContains, 'equals'); elseif (!in_array($sContains, $aValues))
{
$aValuesEquals = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
// Note: Here we cannot use array_merge as it would reindex the numeric keys starting from 0 when keys are actually the objects ID. // Note: Here we cannot use array_merge as it would reindex the numeric keys starting from 0 when keys are actually the objects ID.
// As a workaround we use array_replace as it does preserve numeric keys. It's ok if some values from $aValuesEquals are replaced with values from $aValues as they contain the same data. // As a workaround we use array_replace as it does preserve numeric keys. It's ok if some values from $aValuesEquals are replaced with values from $aValues as they contain the same data.
$aValues = array_replace($aValuesEquals, $aValues); $aValues = array_replace($aValuesEquals, $aValues);
} }
switch ($sOutputFormat) { switch($sOutputFormat)
{
case static::ENUM_OUTPUT_FORMAT_JSON: case static::ENUM_OUTPUT_FORMAT_JSON:
$aJsonMap = []; $aJsonMap = array();
foreach ($aValues as $sKey => $aValue) { foreach ($aValues as $sKey => $aValue) {
$aElt = ['value' => $sKey, 'label' => utils::EscapeHtml($aValue['label']), 'obsolescence_flag' => $aValue['obsolescence_flag']]; $aElt = ['value' => $sKey, 'label' => utils::EscapeHtml($aValue['label']), 'obsolescence_flag' => $aValue['obsolescence_flag']];
if ($aValue['additional_field'] != '') { if ($aValue['additional_field'] != '') {
@@ -871,7 +851,8 @@ JS
break; break;
case static::ENUM_OUTPUT_FORMAT_CSV: case static::ENUM_OUTPUT_FORMAT_CSV:
foreach ($aValues as $sKey => $aValue) { foreach($aValues as $sKey => $aValue)
{
$oP->add(trim($aValue['label'])."\t".$sKey."\n"); $oP->add(trim($aValue['label'])."\t".$sKey."\n");
} }
break; break;
@@ -893,7 +874,7 @@ JS
*/ */
public function GetObjectName($iObjId, $sFormAttCode = null) public function GetObjectName($iObjId, $sFormAttCode = null)
{ {
$aModifierProps = []; $aModifierProps = array();
$aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode; $aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode;
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps); $oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps);
@@ -903,7 +884,9 @@ JS
} else { } else {
return $oObj->Get($sFormAttCode); return $oObj->Get($sFormAttCode);
} }
} else { }
else
{
return ''; return '';
} }
} }
@@ -922,16 +905,17 @@ JS
// For security reasons: check that the "proposed" class is actually a subclass of the linked class // For security reasons: check that the "proposed" class is actually a subclass of the linked class
// and that the current user is allowed to create objects of this class // and that the current user is allowed to create objects of this class
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass, ENUM_CHILD_CLASSES_ALL); $aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass, ENUM_CHILD_CLASSES_ALL);
$aPossibleClasses = []; $aPossibleClasses = array();
foreach ($aSubClasses as $sCandidateClass) { foreach($aSubClasses as $sCandidateClass)
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) { {
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
} }
} }
$sClassLabel = MetaModel::GetName($this->sTargetClass); $sClassLabel = MetaModel::GetName($this->sTargetClass);
$sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel); $sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);;
;
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']); $oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']);
$oPage->AddSubBlock($oBlock); $oPage->AddSubBlock($oBlock);
$oClassForm = FormUIBlockFactory::MakeStandard(); $oClassForm = FormUIBlockFactory::MakeStandard();
@@ -957,13 +941,15 @@ JS
$oAppContext->InitObjectFromContext($oNewObj); $oAppContext->InitObjectFromContext($oNewObj);
$oNewObj->PrefillForm('creation_from_extkey', $aPrefillFormParam); $oNewObj->PrefillForm('creation_from_extkey', $aPrefillFormParam);
// 2nd set the default values from the constraint on the external key... if any // 2nd set the default values from the constraint on the external key... if any
if (($oCurrObject != null) && ($this->sAttCode != '')) { if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode); $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
$aParams = ['this' => $oCurrObject]; $aParams = array('this' => $oCurrObject);
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams); $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
$aConsts = $oSet->ListConstantFields(); $aConsts = $oSet->ListConstantFields();
$sClassAlias = $oSet->GetFilter()->GetClassAlias(); $sClassAlias = $oSet->GetFilter()->GetClassAlias();
if (isset($aConsts[$sClassAlias])) { if (isset($aConsts[$sClassAlias]))
{
foreach($aConsts[$sClassAlias] as $sAttCode => $value) { foreach($aConsts[$sClassAlias] as $sAttCode => $value) {
$oNewObj->Set($sAttCode, $value); $oNewObj->Set($sAttCode, $value);
} }
@@ -976,17 +962,16 @@ JS
$sClassLabel = MetaModel::GetName($this->sTargetClass); $sClassLabel = MetaModel::GetName($this->sTargetClass);
$sHeaderTitleEscaped = utils::EscapeHtml(Dict::Format('UI:CreationTitle_Class', $sClassLabel)); $sHeaderTitleEscaped = utils::EscapeHtml(Dict::Format('UI:CreationTitle_Class', $sClassLabel));
$oPage->add( $oPage->add(<<<HTML
<<<HTML
<div id="ac_create_{$this->iId}" title="{$sHeaderTitleEscaped}"> <div id="ac_create_{$this->iId}" title="{$sHeaderTitleEscaped}">
<div id="dcr_{$this->iId}"> <div id="dcr_{$this->iId}">
HTML HTML
); );
$aFormExtraParams = [ $aFormExtraParams = array(
'formPrefix' => $this->iId, 'formPrefix' => $this->iId,
'noRelations' => true, 'noRelations' => true,
]; );
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context // Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
FormHelper::DisableAttributeBlobInputs($this->sTargetClass, $aFormExtraParams); FormHelper::DisableAttributeBlobInputs($this->sTargetClass, $aFormExtraParams);
@@ -995,16 +980,14 @@ HTML
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal(FormHelper::ENUM_MANDATORY_BLOB_MODE_CREATE)); $oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal(FormHelper::ENUM_MANDATORY_BLOB_MODE_CREATE));
} }
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, [], $aFormExtraParams); cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), $aFormExtraParams);
$oPage->add( $oPage->add(<<<HTML
<<<HTML
</div> </div>
</div> </div>
HTML HTML
); );
$oPage->add_ready_script( $oPage->add_ready_script(<<<JS
<<<JS
$('#ac_create_{$this->iId}').dialog({ width: $(window).width() * 0.6, height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true}); $('#ac_create_{$this->iId}').dialog({ width: $(window).width() * 0.6, height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true});
$('#dcr_{$this->iId} form').removeAttr('onsubmit'); $('#dcr_{$this->iId} form').removeAttr('onsubmit');
$('#dcr_{$this->iId} form').find('button[type="submit"]').on('click', oACWidget_{$this->iId}.DoCreateObject); $('#dcr_{$this->iId} form').find('button[type="submit"]').on('click', oACWidget_{$this->iId}.DoCreateObject);
@@ -1020,13 +1003,14 @@ JS
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass))); $sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">'); $oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">');
$oPage->add('<table style="width:100%"><tr><td>'); $oPage->add('<table style="width:100%"><tr><td>');
if (is_null($sFilter)) { if (is_null($sFilter))
{
throw new Exception('Implementation: null value for allowed values definition'); throw new Exception('Implementation: null value for allowed values definition');
} }
$oFilter = DBObjectSearch::FromOQL($sFilter); $oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter, [], ['this' => $oObj, 'current_extkey_id' => $currValue]); $oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
$oSet->SetShowObsoleteData(utils::ShowObsoleteData()); $oSet->SetShowObsoleteData(utils::ShowObsoleteData());
@@ -1036,7 +1020,8 @@ JS
$oPage->add('</td></tr></table>'); $oPage->add('</td></tr></table>');
$oPage->add('</div>'); $oPage->add('</div>');
if ($bHasChildLeafs) { if ($bHasChildLeafs)
{
$oPage->add('<span class="treecontrol ibo-button-group" id="treecontrolid"><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:CollapseAll").'</a><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:ExpandAll").'</a></span>'); $oPage->add('<span class="treecontrol ibo-button-group" id="treecontrolid"><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:CollapseAll").'</a><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:ExpandAll").'</a></span>');
} }
@@ -1045,8 +1030,7 @@ JS
$sOkButtonLabel = Dict::S('UI:Button:Ok'); $sOkButtonLabel = Dict::S('UI:Button:Ok');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel'); $sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview({ control: '#treecontrolid', persist: 'false'});\n"); $oPage->add_ready_script("\$('#tree_$this->iId ul').treeview({ control: '#treecontrolid', persist: 'false'});\n");
$oPage->add_ready_script( $oPage->add_ready_script(<<<JS
<<<JS
$('#dlg_tree_$this->iId').dialog({ $('#dlg_tree_$this->iId').dialog({
width: 'auto', width: 'auto',
height: 'auto', height: 'auto',
@@ -1085,7 +1069,8 @@ JS
*/ */
public function DoCreateObject($oPage) public function DoCreateObject($oPage)
{ {
try { try
{
$oObj = MetaModel::NewObject($this->sTargetClass); $oObj = MetaModel::NewObject($this->sTargetClass);
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId); $aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0) { if (count($aErrors) == 0) {
@@ -1103,12 +1088,13 @@ JS
]); ]);
$oObj->DBInsertNoReload(); $oObj->DBInsertNoReload();
return ['name' => $oObj->GetName(), 'id' => $oObj->GetKey()]; return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
} else { } else {
return ['error' => implode(' ', $aErrors), 'id' => 0]; return array('error' => implode(' ', $aErrors), 'id' => 0);
} }
} catch (Exception $e) { }
return ['error' => $e->getMessage(), 'id' => 0]; catch (Exception $e) {
return array('error' => $e->getMessage(), 'id' => 0);
} }
} }
@@ -1124,27 +1110,32 @@ JS
* @throws \CoreUnexpectedValue * @throws \CoreUnexpectedValue
* @throws \MySQLException * @throws \MySQLException
*/ */
public function DumpTree($oP, $oSet, $sParentAttCode, $currValue) function DumpTree($oP, $oSet, $sParentAttCode, $currValue)
{
$aTree = array();
$aNodes = array();
while($oObj = $oSet->Fetch())
{ {
$aTree = [];
$aNodes = [];
while ($oObj = $oSet->Fetch()) {
$iParentId = $oObj->Get($sParentAttCode); $iParentId = $oObj->Get($sParentAttCode);
if (!isset($aTree[$iParentId])) { if (!isset($aTree[$iParentId]))
$aTree[$iParentId] = []; {
$aTree[$iParentId] = array();
} }
$aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName(); $aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName();
$aNodes[$oObj->GetKey()] = $oObj; $aNodes[$oObj->GetKey()] = $oObj;
} }
$aParents = array_keys($aTree); $aParents = array_keys($aTree);
$aRoots = []; $aRoots = array();
foreach ($aParents as $id) { foreach($aParents as $id)
if (!array_key_exists($id, $aNodes)) { {
if (!array_key_exists($id, $aNodes))
{
$aRoots[] = $id; $aRoots[] = $id;
} }
} }
foreach ($aRoots as $iRootId) { foreach($aRoots as $iRootId)
{
$this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue); $this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
} }
@@ -1152,22 +1143,28 @@ JS
return !$bHasOnlyRootNodes; return !$bHasOnlyRootNodes;
} }
public function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue) function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
{ {
$bSelect = true; $bSelect = true;
$bMultiple = false; $bMultiple = false;
$sSelect = ''; $sSelect = '';
if (array_key_exists($iRootId, $aTree)) { if (array_key_exists($iRootId, $aTree))
{
$aSortedRoots = $aTree[$iRootId]; $aSortedRoots = $aTree[$iRootId];
asort($aSortedRoots); asort($aSortedRoots);
$oP->add("<ul>\n"); $oP->add("<ul>\n");
$fUniqueId = microtime(true); $fUniqueId = microtime(true);
foreach ($aSortedRoots as $id => $sName) { foreach($aSortedRoots as $id => $sName)
if ($bSelect) { {
if ($bSelect)
{
$sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : ''; $sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : '';
if ($bMultiple) { if ($bMultiple)
{
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'>&nbsp;'; $sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'>&nbsp;';
} else { }
else
{
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;'; $sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;';
} }
} }

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -64,7 +63,7 @@ class UIHTMLEditorWidget
* *
* @return string The HTML fragment to be inserted into the page * @return string The HTML fragment to be inserted into the page
*/ */
public function Display(WebPage $oPage, array $aArgs = []): string public function Display(WebPage $oPage, array $aArgs = array()) : string
{ {
$iId = $this->m_iId; $iId = $this->m_iId;
$sCode = $this->m_sAttCode.$this->m_sNameSuffix; $sCode = $this->m_sAttCode.$this->m_sNameSuffix;

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -39,7 +38,7 @@ class UILinksWidgetDirect
$this->sAttCode = $sAttCode; $this->sAttCode = $sAttCode;
$this->sInputid = $sInputId; $this->sInputid = $sInputId;
$this->sNameSuffix = $sNameSuffix; $this->sNameSuffix = $sNameSuffix;
$this->aZlist = []; $this->aZlist = array();
$this->sLinkedClass = ''; $this->sLinkedClass = '';
// Compute the list of attributes visible from the given objet: // Compute the list of attributes visible from the given objet:
@@ -48,7 +47,8 @@ class UILinksWidgetDirect
$oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$this->sLinkedClass = $oLinksetDef->GetLinkedClass(); $this->sLinkedClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe(); $sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
switch ($oLinksetDef->GetEditMode()) { switch($oLinksetDef->GetEditMode())
{
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place' case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
$aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'details')); $aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'details'));
break; break;
@@ -57,12 +57,15 @@ class UILinksWidgetDirect
$aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'list')); $aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'list'));
array_unshift($aZList, 'friendlyname'); array_unshift($aZList, 'friendlyname');
} }
foreach ($aZList as $sLinkedAttCode) { foreach($aZList as $sLinkedAttCode)
if ($sLinkedAttCode != $sExtKeyToMe) { {
if ($sLinkedAttCode != $sExtKeyToMe)
{
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode); $oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
if ((!$oAttDef->IsExternalField() || ($oAttDef->GetKeyAttCode() != $sExtKeyToMe)) && if ((!$oAttDef->IsExternalField() || ($oAttDef->GetKeyAttCode() != $sExtKeyToMe)) &&
(!$oAttDef->IsLinkSet())) { (!$oAttDef->IsLinkSet()) )
{
$this->aZlist[] = $sLinkedAttCode; $this->aZlist[] = $sLinkedAttCode;
} }
} }
@@ -98,17 +101,21 @@ class UILinksWidgetDirect
$sRealClass = ''; $sRealClass = '';
//$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>'); //$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>');
$aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself $aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$aPossibleClasses = []; $aPossibleClasses = array();
foreach ($aSubClasses as $sCandidateClass) { foreach($aSubClasses as $sCandidateClass)
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) { {
if ($sCandidateClass == $sProposedRealClass) { if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
if ($sCandidateClass == $sProposedRealClass)
{
$sRealClass = $sProposedRealClass; $sRealClass = $sProposedRealClass;
} }
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
} }
} }
// Only one of the subclasses can be instantiated... // Only one of the subclasses can be instantiated...
if (count($aPossibleClasses) == 1) { if (count($aPossibleClasses) == 1)
{
$aKeys = array_keys($aPossibleClasses); $aKeys = array_keys($aPossibleClasses);
$sRealClass = $aKeys[0]; $sRealClass = $aKeys[0];
} }
@@ -116,11 +123,11 @@ class UILinksWidgetDirect
if ($sRealClass != '') { if ($sRealClass != '') {
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode); $oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe(); $sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$aFieldsFlags = [$sExtKeyToMe => OPT_ATT_HIDDEN]; $aFieldsFlags = array($sExtKeyToMe => OPT_ATT_HIDDEN);
$oObj = DBObject::MakeDefaultInstance($sRealClass); $oObj = DBObject::MakeDefaultInstance($sRealClass);
$aPrefillParam = ['source_obj' => $oSourceObj]; $aPrefillParam = array('source_obj' => $oSourceObj);
$oObj->PrefillForm('creation_from_editinplace', $aPrefillParam); $oObj->PrefillForm('creation_from_editinplace', $aPrefillParam);
$aFormExtraParams = [ $aFormExtraParams = array(
'formPrefix' => $this->sInputid, 'formPrefix' => $this->sInputid,
'noRelations' => true, 'noRelations' => true,
'fieldsFlags' => $aFieldsFlags, 'fieldsFlags' => $aFieldsFlags,
@@ -133,7 +140,7 @@ class UILinksWidgetDirect
JS JS
, ,
], ],
]; );
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context // Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
FormHelper::DisableAttributeBlobInputs($sRealClass, $aFormExtraParams); FormHelper::DisableAttributeBlobInputs($sRealClass, $aFormExtraParams);
@@ -142,13 +149,16 @@ JS
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal(FormHelper::ENUM_MANDATORY_BLOB_MODE_CREATE)); $oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal(FormHelper::ENUM_MANDATORY_BLOB_MODE_CREATE));
} }
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, [], $aFormExtraParams); cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aFormExtraParams);
} else { }
else
{
$sClassLabel = MetaModel::GetName($this->sLinkedClass); $sClassLabel = MetaModel::GetName($this->sLinkedClass);
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)); $oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
$oPage->add('<nobr><select name="class">'); $oPage->add('<nobr><select name="class">');
asort($aPossibleClasses); asort($aPossibleClasses);
foreach ($aPossibleClasses as $sClassName => $sClassLabel) { foreach($aPossibleClasses as $sClassName => $sClassLabel)
{
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>"); $oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
} }
$oPage->add('</select>'); $oPage->add('</select>');
@@ -168,7 +178,7 @@ JS
* @throws \MissingQueryArgument * @throws \MissingQueryArgument
* @throws \OQLException * @throws \OQLException
*/ */
public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked, $aPrefillFormParam = []) public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked, $aPrefillFormParam = array())
{ {
//$oPage->add("<div class=\"wizContainer\" style=\"vertical-align:top;\">\n"); //$oPage->add("<div class=\"wizContainer\" style=\"vertical-align:top;\">\n");
@@ -189,7 +199,8 @@ JS
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode); $oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$valuesDef = $oLinkSetDef->GetValuesDef(); $valuesDef = $oLinkSetDef->GetValuesDef();
if ($valuesDef === null) { if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($this->sLinkedClass); $oFilter = new DBObjectSearch($this->sLinkedClass);
} else { } else {
if (!$valuesDef instanceof ValueSetObjects) { if (!$valuesDef instanceof ValueSetObjects) {
@@ -207,10 +218,8 @@ JS
$oCurrentObj->PrefillForm('search', $aPrefillFormParam); $oCurrentObj->PrefillForm('search', $aPrefillFormParam);
} }
$oBlock = new DisplayBlock($oFilter, 'search', false); $oBlock = new DisplayBlock($oFilter, 'search', false);
$oPage->AddUiBlock($oBlock->GetDisplay( $oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}",
$oPage, array(
"SearchFormToAdd_{$this->sInputid}",
[
'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}", 'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}",
'table_id' => "add_{$this->sInputid}", 'table_id' => "add_{$this->sInputid}",
'table_inner_id' => "ResultsToAdd_{$this->sInputid}", 'table_inner_id' => "ResultsToAdd_{$this->sInputid}",
@@ -218,17 +227,16 @@ JS
'cssCount' => "#count_{$this->sInputid}", 'cssCount' => "#count_{$this->sInputid}",
'query_params' => $oFilter->GetInternalParams(), 'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sHiddenCriteria, 'hidden_criteria' => $sHiddenCriteria,
] )
)); ));
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm'); $sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sCancel = Dict::S('UI:Button:Cancel'); $sCancel = Dict::S('UI:Button:Cancel');
$sAdd = Dict::S('UI:Button:Add'); $sAdd = Dict::S('UI:Button:Add');
$oPage->add( $oPage->add(<<<HTML
<<<HTML
<form id="ObjectsAddForm_{$this->sInputid}"> <form id="ObjectsAddForm_{$this->sInputid}">
<div id="SearchResultsToAdd_{$this->sInputid}"> <div id="SearchResultsToAdd_{$this->sInputid}">
<div style="border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div> <div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div> </div>
<input type="hidden" id="count_{$this->sInputid}" value="0"/> <input type="hidden" id="count_{$this->sInputid}" value="0"/>
</form> </form>
@@ -248,30 +256,38 @@ HTML
* @throws \CoreException * @throws \CoreException
* @throws \OQLException * @throws \OQLException
*/ */
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = [], $oCurrentObj = null, $aPrefillFormParam = []) public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = array(), $oCurrentObj = null, $aPrefillFormParam = array())
{
if ($sRemoteClass == '')
{ {
if ($sRemoteClass == '') {
$sRemoteClass = $this->sLinkedClass; $sRemoteClass = $this->sLinkedClass;
} }
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode); $oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$valuesDef = $oLinkSetDef->GetValuesDef(); $valuesDef = $oLinkSetDef->GetValuesDef();
if ($valuesDef === null) { if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($sRemoteClass); $oFilter = new DBObjectSearch($sRemoteClass);
} else { }
if (!$valuesDef instanceof ValueSetObjects) { else
{
if (!$valuesDef instanceof ValueSetObjects)
{
throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').'); throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').');
} }
$oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression()); $oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
} }
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($sRemoteClass, $this->sClass)) { if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($sRemoteClass, $this->sClass))
{
// Prevent linking to self if the linked object is of the same family // Prevent linking to self if the linked object is of the same family
// and already present in the database // and already present in the database
if (!$oCurrentObj->IsNew()) { if (!$oCurrentObj->IsNew())
{
$oFilter->AddCondition('id', $oCurrentObj->GetKey(), '!='); $oFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
} }
} }
if ($oCurrentObj != null) { if ($oCurrentObj != null)
{
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter); $this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams()); $aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
@@ -280,11 +296,12 @@ HTML
$aPrefillFormParam['filter'] = $oFilter; $aPrefillFormParam['filter'] = $oFilter;
$oCurrentObj->PrefillForm('search', $aPrefillFormParam); $oCurrentObj->PrefillForm('search', $aPrefillFormParam);
} }
if (count($aAlreadyLinked) > 0) { if (count($aAlreadyLinked) > 0)
{
$oFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN'); $oFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN');
} }
$oBlock = new DisplayBlock($oFilter, 'list', false); $oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, "ResultsToAdd_{$this->sInputid}", ['menu' => false, 'cssCount' => '#count_'.$this->sInputid , 'selection_mode' => true, 'table_id' => 'add_'.$this->sInputid]); // Don't display the 'Actions' menu on the results $oBlock->Display($oP, "ResultsToAdd_{$this->sInputid}", array('menu' => false, 'cssCount'=> '#count_'.$this->sInputid , 'selection_mode' => true, 'table_id' => 'add_'.$this->sInputid)); // Don't display the 'Actions' menu on the results
} }
/** /**
@@ -294,7 +311,8 @@ HTML
public function DoAddObjects(WebPage $oP, $oFullSetFilter) public function DoAddObjects(WebPage $oP, $oFullSetFilter)
{ {
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter); $aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
foreach ($aLinkedObjectIds as $iObjectId) { foreach($aLinkedObjectIds as $iObjectId)
{
$oLinkObj = MetaModel::GetObject($this->sLinkedClass, $iObjectId); $oLinkObj = MetaModel::GetObject($this->sLinkedClass, $iObjectId);
$oP->add($this->GetObjectRow($oP, $oLinkObj, $oLinkObj->GetKey())); $oP->add($this->GetObjectRow($oP, $oLinkObj, $oLinkObj->GetKey()));
} }
@@ -307,15 +325,15 @@ HTML
public function GetTableConfig() public function GetTableConfig()
{ {
$aAttribs = []; $aAttribs = array();
$aAttribs['form::select'] = [ $aAttribs['form::select'] = array(
'label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);oWidget".$this->sInputid.".directlinks('instance')._onSelectChange();\" class=\"checkAll\"></input>", 'label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);oWidget".$this->sInputid.".directlinks('instance')._onSelectChange();\" class=\"checkAll\"></input>",
'description' => Dict::S('UI:SelectAllToggle+'), 'description' => Dict::S('UI:SelectAllToggle+'),
]; );
foreach ($this->aZlist as $sLinkedAttCode) { foreach ($this->aZlist as $sLinkedAttCode) {
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode); $oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
$aAttribs[$sLinkedAttCode] = ['label' => MetaModel::GetLabel($this->sLinkedClass, $sLinkedAttCode), 'description' => $oAttDef->GetOrderByHint()]; $aAttribs[$sLinkedAttCode] = array('label' => MetaModel::GetLabel($this->sLinkedClass, $sLinkedAttCode), 'description' => $oAttDef->GetOrderByHint());
} }
return $aAttribs; return $aAttribs;
@@ -330,7 +348,8 @@ HTML
*/ */
public function GetRow($oPage, $sRealClass, $aValues, $iTempId) public function GetRow($oPage, $sRealClass, $aValues, $iTempId)
{ {
if ($sRealClass == '') { if ($sRealClass == '')
{
$sRealClass = $this->sLinkedClass; $sRealClass = $this->sLinkedClass;
} }
$oLinkObj = new $sRealClass(); $oLinkObj = new $sRealClass();
@@ -348,9 +367,10 @@ HTML
protected function GetObjectRow($oPage, $oLinkObj, $iTempId) protected function GetObjectRow($oPage, $oLinkObj, $iTempId)
{ {
$aAttribs = $this->GetTableConfig(); $aAttribs = $this->GetTableConfig();
$aRow = []; $aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.($iTempId).'"/>'; $aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.($iTempId).'"/>';
foreach ($this->aZlist as $sLinkedAttCode) { foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode); $aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
} }
return $oPage->GetTableRow($aRow, $aAttribs); return $oPage->GetTableRow($aRow, $aAttribs);
@@ -366,16 +386,18 @@ HTML
*/ */
public function GetFormRow($oPage, $sRealClass, $aValues, $iTempId) public function GetFormRow($oPage, $sRealClass, $aValues, $iTempId)
{ {
if ($sRealClass == '') { if ($sRealClass == '')
{
$sRealClass = $this->sLinkedClass; $sRealClass = $this->sLinkedClass;
} }
$oLinkObj = new $sRealClass(); $oLinkObj = new $sRealClass();
$oLinkObj->UpdateObjectFromPostedForm($this->sInputid); $oLinkObj->UpdateObjectFromPostedForm($this->sInputid);
$aAttribs = $this->GetTableConfig(); $aAttribs = $this->GetTableConfig();
$aRow = []; $aRow = array();
$aRow[] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.($iTempId).'"/>'; $aRow[] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.($iTempId).'"/>';
foreach ($this->aZlist as $sLinkedAttCode) { foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[] = $oLinkObj->GetAsHTML($sLinkedAttCode); $aRow[] = $oLinkObj->GetAsHTML($sLinkedAttCode);
} }
return $aRow; return $aRow;
@@ -391,22 +413,26 @@ HTML
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sSrcClass = get_class($oSourceObj); $sSrcClass = get_class($oSourceObj);
$sDestClass = $oSearch->GetClass(); $sDestClass = $oSearch->GetClass();
foreach ($oAppContext->GetNames() as $key) { foreach($oAppContext->GetNames() as $key)
{
// Find the value of the object corresponding to each 'context' parameter // Find the value of the object corresponding to each 'context' parameter
$aCallSpec = [$sSrcClass, 'MapContextParam']; $aCallSpec = array($sSrcClass, 'MapContextParam');
$sAttCode = ''; $sAttCode = '';
if (is_callable($aCallSpec)) { if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
} }
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode)) { if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$defaultValue = $oSourceObj->Get($sAttCode); $defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class // Find the attcode for the same 'context' parameter in the destination class
// and sets its value as the default value for the search condition // and sets its value as the default value for the search condition
$aCallSpec = [$sDestClass, 'MapContextParam']; $aCallSpec = array($sDestClass, 'MapContextParam');
$sAttCode = ''; $sAttCode = '';
if (is_callable($aCallSpec)) { if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
} }
@@ -417,6 +443,7 @@ HTML
} }
} }
public function GetClass(): string public function GetClass(): string
{ {
return $this->sClass; return $this->sClass;

View File

@@ -1,5 +1,4 @@
<?php <?php
/* /*
* @copyright Copyright (C) 2010-2024 Combodo SAS * @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -55,7 +54,7 @@ class UILinksWidget
$this->m_sNameSuffix = $sNameSuffix; $this->m_sNameSuffix = $sNameSuffix;
$this->m_bDuplicatesAllowed = $bDuplicatesAllowed; $this->m_bDuplicatesAllowed = $bDuplicatesAllowed;
$this->m_aEditableFields = []; $this->m_aEditableFields = array();
/** @var AttributeLinkedSetIndirect $oAttDef */ /** @var AttributeLinkedSetIndirect $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
@@ -68,33 +67,34 @@ class UILinksWidget
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote); $oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass(); $this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
$this->m_aEditableFields = []; $this->m_aEditableFields = array();
$this->m_aTableConfig = []; $this->m_aTableConfig = array();
$this->m_aTableConfig['form::checkbox'] = [ $this->m_aTableConfig['form::checkbox'] = array(
'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_sInputId.".OnSelectChange();\">", 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_sInputId.".OnSelectChange();\">",
'description' => Dict::S('UI:SelectAllToggle+'), 'description' => Dict::S('UI:SelectAllToggle+'),
]; );
$aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode); $aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode);
foreach ($aLnkAttDefsToDisplay as $oLnkAttDef) { foreach ($aLnkAttDefsToDisplay as $oLnkAttDef)
{
$sLnkAttCode = $oLnkAttDef->GetCode(); $sLnkAttCode = $oLnkAttDef->GetCode();
$this->m_aEditableFields[] = $sLnkAttCode; $this->m_aEditableFields[] = $sLnkAttCode;
$this->m_aTableConfig[$sLnkAttCode] = ['label' => $oLnkAttDef->GetLabel(), 'description' => $oLnkAttDef->GetDescription()]; $this->m_aTableConfig[$sLnkAttCode] = array('label' => $oLnkAttDef->GetLabel(), 'description' => $oLnkAttDef->GetDescription());
} }
$this->m_aTableConfig['static::key'] = [ $this->m_aTableConfig['static::key'] = array(
'label' => MetaModel::GetName($this->m_sRemoteClass), 'label' => MetaModel::GetName($this->m_sRemoteClass),
'description' => MetaModel::GetClassDescription($this->m_sRemoteClass), 'description' => MetaModel::GetClassDescription($this->m_sRemoteClass),
]; );
$this->m_aEditableFields[] = $this->m_sExtKeyToRemote; $this->m_aEditableFields[] = $this->m_sExtKeyToRemote;
$aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass); $aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass);
foreach ($aRemoteAttDefsToDisplay as $oRemoteAttDef) { foreach ($aRemoteAttDefsToDisplay as $oRemoteAttDef) {
$sRemoteAttCode = $oRemoteAttDef->GetCode(); $sRemoteAttCode = $oRemoteAttDef->GetCode();
$this->m_aTableConfig['static::'.$sRemoteAttCode] = [ $this->m_aTableConfig['static::'.$sRemoteAttCode] = array(
'label' => $oRemoteAttDef->GetLabel(), 'label' => $oRemoteAttDef->GetLabel(),
'description' => $oRemoteAttDef->GetDescription(), 'description' => $oRemoteAttDef->GetDescription(),
]; );
} }
} }
@@ -105,6 +105,7 @@ class UILinksWidget
return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId; return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId;
} }
/** /**
* Display the table with the form for editing all the links at once * Display the table with the form for editing all the links at once
* *
@@ -118,6 +119,7 @@ class UILinksWidget
return DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $aConfig, $aData); return DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $aConfig, $aData);
} }
/** /**
* Get the HTML fragment corresponding to the linkset editing widget * Get the HTML fragment corresponding to the linkset editing widget
* *
@@ -155,7 +157,7 @@ class UILinksWidget
* @throws DictExceptionMissingString * @throws DictExceptionMissingString
* @throws Exception * @throws Exception
*/ */
public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = [], $aPrefillFormParam = []) public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = array(), $aPrefillFormParam = array())
{ {
$oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass); $oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass);
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) { if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) {
@@ -181,9 +183,7 @@ class UILinksWidget
$sLinkedSetId = $oBlock->oUILinksWidget->GetLinkedSetId(); $sLinkedSetId = $oBlock->oUILinksWidget->GetLinkedSetId();
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false); $oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay( $oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
$oPage,
"SearchFormToAdd_{$sLinkedSetId}",
[ [
'menu' => false, 'menu' => false,
'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}", 'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}",
@@ -195,8 +195,7 @@ class UILinksWidget
'query_params' => $oFilter->GetInternalParams(), 'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sAlreadyLinkedExpression, 'hidden_criteria' => $sAlreadyLinkedExpression,
'submit_on_load' => false, 'submit_on_load' => false,
] ]));
));
$oBlock->AddForm(); $oBlock->AddForm();
} }
@@ -213,21 +212,25 @@ class UILinksWidget
* @throws \CoreException * @throws \CoreException
* @throws \Exception * @throws \Exception
*/ */
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = [], $oCurrentObj = null) public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = array(), $oCurrentObj = null)
{
if ($sRemoteClass != '')
{ {
if ($sRemoteClass != '') {
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass)); // assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
$oFilter = new DBObjectSearch($sRemoteClass); $oFilter = new DBObjectSearch($sRemoteClass);
} else { }
else
{
// No remote class specified use the one defined in the linkedset // No remote class specified use the one defined in the linkedset
$oFilter = new DBObjectSearch($this->m_sRemoteClass); $oFilter = new DBObjectSearch($this->m_sRemoteClass);
} }
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) { if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
{
$oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN'); $oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
} }
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter); $this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false); $oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", ['menu' => false, 'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'table_id' => 'add_'.$this->m_sAttCode]); // Don't display the 'Actions' menu on the results $oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'table_id' => 'add_'.$this->m_sAttCode)); // Don't display the 'Actions' menu on the results
} }
/** /**
@@ -248,7 +251,7 @@ class UILinksWidget
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false); $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
if (is_object($oLinkedObj)) { if (is_object($oLinkedObj)) {
$oBlock = new BlockIndirectLinkSetEditTable($this); $oBlock = new BlockIndirectLinkSetEditTable($this);
$aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, [], $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids $aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aRow, -$iAdditionId); $oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aRow, -$iAdditionId);
$oP->AddUiBlock($oRow); $oP->AddUiBlock($oRow);
$iAdditionId++; $iAdditionId++;
@@ -277,7 +280,7 @@ class UILinksWidget
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false); $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
if (is_object($oLinkedObj)) { if (is_object($oLinkedObj)) {
$oBlock = new BlockIndirectLinkSetEditTable($this); $oBlock = new BlockIndirectLinkSetEditTable($this);
$aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, [], $oCurrentObj, $iAdditionId, false /* Default value */, $bAllowRemoteExtKeyEdit); // Not yet created link get negative Ids $aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId, false /* Default value */, $bAllowRemoteExtKeyEdit); // Not yet created link get negative Ids
$aData = []; $aData = [];
foreach ($aRow as $item) { foreach ($aRow as $item) {
$aData[] = $item; $aData[] = $item;
@@ -304,30 +307,37 @@ class UILinksWidget
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sSrcClass = get_class($oSourceObj); $sSrcClass = get_class($oSourceObj);
$sDestClass = $oSearch->GetClass(); $sDestClass = $oSearch->GetClass();
foreach ($oAppContext->GetNames() as $key) { foreach($oAppContext->GetNames() as $key)
{
// Find the value of the object corresponding to each 'context' parameter // Find the value of the object corresponding to each 'context' parameter
$aCallSpec = [$sSrcClass, 'MapContextParam']; $aCallSpec = array($sSrcClass, 'MapContextParam');
$sAttCode = ''; $sAttCode = '';
if (is_callable($aCallSpec)) { if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
} }
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode)) { if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$defaultValue = $oSourceObj->Get($sAttCode); $defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class // Find the attcode for the same 'context' parameter in the destination class
// and sets its value as the default value for the search condition // and sets its value as the default value for the search condition
$aCallSpec = [$sDestClass, 'MapContextParam']; $aCallSpec = array($sDestClass, 'MapContextParam');
$sAttCode = ''; $sAttCode = '';
if (is_callable($aCallSpec)) { if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
} }
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue)) { if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
{
// Add Hierarchical condition if hierarchical key // Add Hierarchical condition if hierarchical key
$oAttDef = MetaModel::GetAttributeDef($sDestClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sDestClass, $sAttCode);
if (isset($oAttDef) && ($oAttDef->IsExternalKey())) { if (isset($oAttDef) && ($oAttDef->IsExternalKey()))
try { {
try
{
/** @var AttributeExternalKey $oAttDef */ /** @var AttributeExternalKey $oAttDef */
$sTargetClass = $oAttDef->GetTargetClass(); $sTargetClass = $oAttDef->GetTargetClass();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass); $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
@@ -338,7 +348,8 @@ class UILinksWidget
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); $oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode); $oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
} }
} catch (Exception $e) { }
catch (Exception $e) {
} }
} else { } else {
$oSearch->AddCondition($sAttCode, $defaultValue); $oSearch->AddCondition($sAttCode, $defaultValue);

View File

@@ -1,5 +1,4 @@
<?php <?php
// Copyright (C) 2010-2024 Combodo SAS // Copyright (C) 2010-2024 Combodo SAS
// //
// This file is part of iTop. // This file is part of iTop.
@@ -50,7 +49,7 @@ class UIPasswordWidget
* @param Hash $aArgs Extra context arguments * @param Hash $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page * @return string The HTML fragment to be inserted into the page
*/ */
public function Display(WebPage $oPage, $aArgs = []) public function Display(WebPage $oPage, $aArgs = array())
{ {
$oPage->add_dict_entry('UI:Component:Input:Password:DoesNotMatch'); $oPage->add_dict_entry('UI:Component:Input:Password:DoesNotMatch');
@@ -95,3 +94,4 @@ class UIPasswordWidget
return $sHtmlValue; return $sHtmlValue;
} }
} }
?>

Some files were not shown because too many files have changed in this diff Show More