mirror of
https://github.com/Combodo/iTop.git
synced 2026-06-10 10:02:18 +02:00
Compare commits
5 Commits
feature/96
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af29a00065 | ||
|
|
4406afa089 | ||
|
|
b547d2b0ea | ||
|
|
246de88a2d | ||
|
|
8679665c23 |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 155 KiB |
@@ -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).
|
||||
@@ -7,8 +7,7 @@
|
||||
'git4': 'grey',
|
||||
'git5': 'grey',
|
||||
'git6': 'grey',
|
||||
'git7': 'grey',
|
||||
'git8': 'grey'
|
||||
'git7': 'grey'
|
||||
}, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
|
||||
gitGraph
|
||||
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"
|
||||
checkout support/3.0
|
||||
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
|
||||
commit id: "2024-06-25" tag: "3.2.0-beta1" type: REVERSE
|
||||
commit id: "2024-08-07" tag: "3.2.0"
|
||||
checkout support/2.7
|
||||
commit id: "2025-02-07a" tag: "2.7.12"
|
||||
commit id: "2024-09-13" tag: "3.2.0-2"
|
||||
checkout support/3.1
|
||||
commit id: "2025-02-07b" tag: "3.1.3"
|
||||
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"
|
||||
commit id: "2024-09-27" tag: "3.1.2"
|
||||
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
|
||||
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).
|
||||
|
||||
60
.github/ISSUE_TEMPLATE/bug.yml
vendored
60
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -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"
|
||||
47
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
47
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@@ -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"
|
||||
66
.github/pull_request_template.md
vendored
66
.github/pull_request_template.md
vendored
@@ -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.
|
||||
|
||||
-->
|
||||
|
||||
## Base information
|
||||
| Question | Answer
|
||||
|---------------------------------------------------------------|--------
|
||||
| Related to a SourceForge thread / Another PR / Combodo ticket? | <!-- Put the URL -->
|
||||
| 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
|
||||
|
||||
| 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)
|
||||
|
||||
<!--
|
||||
If it's a bug
|
||||
- Explain the symptom in details
|
||||
- If possible put error messages, logs or screenshots (you can paste image directly in this editor).
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
## 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.
|
||||
-->
|
||||
|
||||
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) -->
|
||||
3. First go there
|
||||
4. Then do that
|
||||
5. ...
|
||||
6. Finally, see that... (what is expected and what is actually happening)
|
||||
2. First go there
|
||||
2. Then do that
|
||||
3. ...
|
||||
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)
|
||||
|
||||
<!--
|
||||
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)
|
||||
-->
|
||||
|
||||
## Proposed solution (bug and enhancement)
|
||||
|
||||
## Proposed solution (bug and enhancement)
|
||||
<!--
|
||||
Explain in details how you are proposing to solve this:
|
||||
- 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)
|
||||
-->
|
||||
|
||||
## Checklist before requesting a review
|
||||
|
||||
## Checklist before requesting a review
|
||||
<!--
|
||||
Don't remove these lines, check them once done.
|
||||
-->
|
||||
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] 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
|
||||
- [ ] 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
|
||||
- ...
|
||||
-->
|
||||
|
||||
- [ ] ...
|
||||
- [ ] ...
|
||||
- [ ] ...
|
||||
|
||||
25
.github/workflows/action.yml
vendored
25
.github/workflows/action.yml
vendored
@@ -1,13 +1,9 @@
|
||||
name: Add PRs to Combodo PRs Dashboard
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
@@ -30,27 +26,18 @@ jobs:
|
||||
|
||||
fi
|
||||
|
||||
- name: Add internal tag if member of the organization
|
||||
- name: Add internal tag if member
|
||||
if: env.is_member == 'true'
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
-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"]}'
|
||||
|
||||
- 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:
|
||||
is_member: ${{ env.is_member }}
|
||||
|
||||
- name: Add PR to the appropriate project
|
||||
uses: actions/add-to-project@v2
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: ${{ env.project_url }}
|
||||
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
|
||||
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -58,9 +58,6 @@ tests/*/vendor/*
|
||||
/tests/php-unit-tests/phpunit.xml
|
||||
/tests/php-unit-tests/postbuild_integration.xml
|
||||
|
||||
# PHP CS Fixer: Cache file
|
||||
/.php-cs-fixer.cache
|
||||
|
||||
|
||||
# Jetbrains
|
||||
/.idea/**
|
||||
|
||||
@@ -4,33 +4,30 @@ You want to contribute to iTop? Many thanks to you! 🎉 👍
|
||||
|
||||
Here are some guidelines that will help us integrate your work!
|
||||
|
||||
## Contributions
|
||||
|
||||
|
||||
|
||||
### Subjects
|
||||
|
||||
You are welcome to create pull requests on any of those subjects:
|
||||
|
||||
* 🐛 bug fix
|
||||
* 🌐 translation / i18n / l10n
|
||||
* 🚸 enhancement
|
||||
|
||||
But before creating a PR, please [create a corresponding issue][itop-issues] 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 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.
|
||||
If you want to implement a **new feature**, please [create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) for review.
|
||||
If you ever want to begin implementation, do so in a fork, and add a link to the corresponding commits in the ticket.
|
||||
|
||||
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
|
||||
impact all existing customers, and could prevent them from upgrading!
|
||||
Combodo has a long experience of datamodel changes: they are very disruptive!
|
||||
All **datamodel modification** should be done in an extension. Beware that such change would
|
||||
impact all existing customers, and could prevent them from
|
||||
upgrading!
|
||||
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.
|
||||
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
|
||||
If you have an idea you're sure would benefit to all of iTop users, you may
|
||||
[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.
|
||||
|
||||
### 📄 License and copyright
|
||||
|
||||
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.
|
||||
@@ -40,33 +37,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].
|
||||
|
||||
[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
|
||||
|
||||
|
||||
## 🔀 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
|
||||
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
|
||||
we don't have a `master` branch anymore.
|
||||
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
|
||||
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
|
||||
- `release/*`: if present, that means we are working on a alpha/beta/rc version for shipping
|
||||
- `support/*`: maintenance branches for older versions
|
||||
|
||||
For example, if no version is currently prepared for shipping we could have:
|
||||
|
||||
- `develop` containing future 3.3.0 version
|
||||
- `support/3.2`: 3.2.x maintenance version
|
||||
- `develop` containing future 3.1.0 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
|
||||
|
||||
@@ -80,11 +92,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
|
||||
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.
|
||||
|
||||
|
||||
### 🎨 PHP styleguide
|
||||
|
||||
Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards).
|
||||
@@ -93,7 +106,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.
|
||||
|
||||
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
|
||||
|
||||
@@ -125,14 +138,14 @@ When your code is working, please:
|
||||
* Rebase your branch on our repo last commit,
|
||||
* Create a pull request. _Detailed procedure to work on fork and create PR is available [in GitHub help pages](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)_.
|
||||
* 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
|
||||
- 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
|
||||
option [won't be available](https://github.com/orgs/community/discussions/5634))
|
||||
- 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
|
||||
* 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))
|
||||
|
||||
|
||||
## 🙏 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 :)
|
||||
|
||||
@@ -146,6 +159,6 @@ We have one sticker per contribution type. You might get multiple stickers with
|
||||
* Beta tester: Test and give feedback on beta releases
|
||||
* Extension developer: Develop and publish an extension
|
||||
|
||||
Here is the design of each sticker:
|
||||
Here is the design of each stickers for year 2024:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -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
|
||||
[5]: https://www.itophub.io/wiki
|
||||
[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
|
||||
[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
|
||||
|
||||
## Developers
|
||||
|
||||
You can find information and instructions about our quality tools and how to run them [here](.doc/developers.md).
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -16,5 +16,5 @@ require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.domain.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
@@ -97,33 +97,77 @@ class RestUtils
|
||||
* @throws Exception
|
||||
* @api
|
||||
*/
|
||||
public static function GetFieldList($sClass, $oData, $sParamName)
|
||||
public static function GetFieldList($sClass, $oData, $sParamName, $bFailIfNotFound = true)
|
||||
{
|
||||
$sFields = self::GetOptionalParam($oData, $sParamName, '*');
|
||||
$aShowFields = [];
|
||||
if ($sFields == '*') {
|
||||
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
|
||||
$aShowFields[$sClass][] = $sAttCode;
|
||||
}
|
||||
} elseif ($sFields == '*+') {
|
||||
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass) {
|
||||
foreach (MetaModel::ListAttributeDefs($sRefClass) as $sAttCode => $oAttDef) {
|
||||
$aShowFields[$sRefClass][] = $sAttCode;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach (explode(',', $sFields) as $sAttCode) {
|
||||
$sAttCode = trim($sAttCode);
|
||||
if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode($sClass, $sAttCode))) {
|
||||
throw new Exception("$sParamName: invalid attribute code '$sAttCode'");
|
||||
}
|
||||
$aShowFields[$sClass][] = $sAttCode;
|
||||
}
|
||||
}
|
||||
|
||||
return $aShowFields;
|
||||
return match($sFields) {
|
||||
'*' => self::GetFieldListForClass($sClass),
|
||||
'*+' => self::GetFieldListForParentClass($sClass),
|
||||
default => self::GetLimitedFieldListForClass($sClass, $sFields, $sParamName, $bFailIfNotFound),
|
||||
};
|
||||
}
|
||||
|
||||
public static function HasRequestedExtendedOutput(string $sFields): bool
|
||||
{
|
||||
return match($sFields) {
|
||||
'*' => false,
|
||||
'*+' => true,
|
||||
default => substr_count($sFields, ':') > 1,
|
||||
};
|
||||
}
|
||||
|
||||
public static function HasRequestedAllOutputFields(string $sFields): bool
|
||||
{
|
||||
return match($sFields) {
|
||||
'*', '*+' => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
protected static function GetFieldListForClass(string $sClass): array
|
||||
{
|
||||
return [$sClass => array_keys(MetaModel::ListAttributeDefs($sClass))];
|
||||
}
|
||||
|
||||
protected static function GetFieldListForParentClass(string $sClass): array
|
||||
{
|
||||
$aFieldList = array();
|
||||
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass) {
|
||||
$aFieldList = array_merge($aFieldList, self::GetFieldListForClass($sRefClass));
|
||||
}
|
||||
return $aFieldList;
|
||||
}
|
||||
|
||||
protected static function GetLimitedFieldListForSingleClass(string $sClass, string $sFields, string $sParamName, bool $bFailIfNotFound = true): array
|
||||
{
|
||||
$aFieldList = [$sClass => []];
|
||||
foreach (explode(',', $sFields) as $sAttCode) {
|
||||
$sAttCode = trim($sAttCode);
|
||||
if (($sAttCode == 'id') || (MetaModel::IsValidAttCode($sClass, $sAttCode))) {
|
||||
$aFieldList[$sClass][] = $sAttCode;
|
||||
} else {
|
||||
if ($bFailIfNotFound) {
|
||||
throw new Exception("$sParamName: invalid attribute code '$sAttCode' for class '$sClass'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aFieldList;
|
||||
}
|
||||
|
||||
protected static function GetLimitedFieldListForClass(string $sClass, string $sFields, string $sParamName, bool $bFailIfNotFound = true): array
|
||||
{
|
||||
if (!str_contains($sFields, ':')) {
|
||||
return self::GetLimitedFieldListForSingleClass($sClass, $sFields, $sParamName, $bFailIfNotFound);
|
||||
}
|
||||
|
||||
$aFieldList = [];
|
||||
$aFieldListParts = explode(';', $sFields);
|
||||
foreach ($aFieldListParts as $sClassFields) {
|
||||
list($sSubClass, $sSubClassFields) = explode(':', $sClassFields);
|
||||
$aFieldList = array_merge($aFieldList, self::GetLimitedFieldListForSingleClass(trim($sSubClass), trim($sSubClassFields), $sParamName, $bFailIfNotFound));
|
||||
}
|
||||
return $aFieldList;
|
||||
}
|
||||
/**
|
||||
* Read and interpret object search criteria from a Rest/Json structure
|
||||
*
|
||||
|
||||
@@ -819,7 +819,6 @@ HTML
|
||||
foreach ($aNotificationClasses as $sNotifClass) {
|
||||
$aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev WHERE Ev.object_id = :id AND Ev.object_class = :class");
|
||||
$aNotifSearches[$sNotifClass]->SetInternalParams($aParams);
|
||||
$aNotifSearches[$sNotifClass]->AllowAllData();
|
||||
$oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass], []);
|
||||
$iNotifsCount += $oNotifSet->Count();
|
||||
}
|
||||
@@ -833,7 +832,6 @@ HTML
|
||||
'menu' => false,
|
||||
'panel_title' => MetaModel::GetName($sNotifClass),
|
||||
'panel_icon' => MetaModel::GetClassIcon($sNotifClass, false),
|
||||
'display_unauthorized_objects' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1035,7 +1033,7 @@ HTML
|
||||
// Add extra data for markup generation
|
||||
// - Attribute code and AttributeDef. class
|
||||
$val['attcode'] = $sAttCode;
|
||||
$val['atttype'] = $oAttDef->GetType();
|
||||
$val['atttype'] = $sAttDefClass;
|
||||
$val['attlabel'] = $sAttLabel;
|
||||
$val['attflags'] = ($bEditMode) ? $this->GetFormAttributeFlags($sAttCode) : OPT_ATT_READONLY;
|
||||
|
||||
@@ -4495,7 +4493,7 @@ HTML;
|
||||
|
||||
$oDivField = FieldUIBlockFactory::MakeLarge("");
|
||||
// UIContentBlockUIBlockFactory::MakeStandard(null,["field_container field_large"]);
|
||||
$oDivField->AddDataAttribute("attribute-type", $oAttDef->GetType());
|
||||
$oDivField->AddDataAttribute("attribute-type", $sAttDefClass);
|
||||
$oDivField->AddDataAttribute("attribute-label", $sAttMetaDataLabel);
|
||||
$oDivField->AddDataAttribute("attribute-flag-hidden", false);
|
||||
$oDivField->AddDataAttribute("attribute-flag-read-only", false);
|
||||
@@ -5064,10 +5062,10 @@ EOF
|
||||
} else {
|
||||
if (count($aObjects) == 1) {
|
||||
$oObj = $aObjects[0];
|
||||
$sSubtitle = Dict::Format('UI:Delete:Confirm_Object', $oObj->GetHyperLink());
|
||||
$sSubtitle = Dict::Format('UI:Delect:Confirm_Object', $oObj->GetHyperLink());
|
||||
} else {
|
||||
$sSubtitle = Dict::Format(
|
||||
'UI:Delete:Confirm_Count_ObjectsOf_Class',
|
||||
'UI:Delect:Confirm_Count_ObjectsOf_Class',
|
||||
count($aObjects),
|
||||
MetaModel::GetName($sClass)
|
||||
);
|
||||
@@ -5634,7 +5632,7 @@ JS
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
*/
|
||||
final public function AddAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', ?string $sReason = null): void
|
||||
final public function AddAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', string $sReason = null): void
|
||||
{
|
||||
if (!isset($this->aAttributesFlags[$sTargetState])) {
|
||||
$this->aAttributesFlags[$sTargetState] = [];
|
||||
@@ -5657,7 +5655,7 @@ JS
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
*/
|
||||
final public function ForceAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', ?string $sReason = null): void
|
||||
final public function ForceAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', string $sReason = null): void
|
||||
{
|
||||
if (!isset($this->aAttributesFlags[$sTargetState])) {
|
||||
$this->aAttributesFlags[$sTargetState] = [];
|
||||
@@ -5698,7 +5696,7 @@ JS
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
*/
|
||||
final public function AddInitialAttributeFlags(string $sAttCode, int $iFlags, ?string $sReason = null)
|
||||
final public function AddInitialAttributeFlags(string $sAttCode, int $iFlags, string $sReason = null)
|
||||
{
|
||||
if (!isset($this->aInitialAttributesFlags)) {
|
||||
$this->aInitialAttributesFlags = [];
|
||||
@@ -5720,7 +5718,7 @@ JS
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
*/
|
||||
final public function ForceInitialAttributeFlags(string $sAttCode, int $iFlags, ?string $sReason = null)
|
||||
final public function ForceInitialAttributeFlags(string $sAttCode, int $iFlags, string $sReason = null)
|
||||
{
|
||||
if (!isset($this->aInitialAttributesFlags)) {
|
||||
$this->aInitialAttributesFlags = [];
|
||||
|
||||
@@ -497,7 +497,7 @@ EOF
|
||||
* @param array $aExtraParams
|
||||
* @param bool $bCanEdit
|
||||
*
|
||||
* @return null|\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)
|
||||
{
|
||||
@@ -1029,7 +1029,7 @@ EOF
|
||||
var dashboard = $('.ibo-dashboard#$sDivId')
|
||||
dashboard.block();
|
||||
$.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) {
|
||||
dashboard.html(data);
|
||||
dashboard.unblock();
|
||||
|
||||
@@ -726,10 +726,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();
|
||||
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
|
||||
}
|
||||
@@ -1383,10 +1379,7 @@ JS
|
||||
|
||||
// Check the classes that can be read (i.e authorized) by this user...
|
||||
foreach ($aClasses as $sAlias => $sClassName) {
|
||||
if (
|
||||
(UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) !== UR_ALLOWED_NO)
|
||||
|| ($aExtraParams['display_unauthorized_objects'] ?? false) === true
|
||||
) {
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ class CoreException extends Exception
|
||||
/**
|
||||
* CoreException constructor.
|
||||
*
|
||||
* ATTENTION: Logging here will break the CI
|
||||
*
|
||||
* @param string $sIssue error message
|
||||
* @param array|null $aContextData key/value array, value MUST implements _toString
|
||||
* @param string $sImpact
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ForgotPasswordApplicationException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ForgotPasswordUserInputException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -1387,7 +1387,7 @@ class DesignerIconSelectionField extends DesignerFormField
|
||||
public function AddAllowedValue($aValue)
|
||||
{
|
||||
// Add a null value to the list of allowed values
|
||||
$this->aAllowedValues = array_merge([$aValue], $this->aAllowedValues ?? [null]);
|
||||
$this->aAllowedValues = array_merge([$aValue], $this->aAllowedValues);
|
||||
}
|
||||
public function EnableUpload($sIconUploadUrl)
|
||||
{
|
||||
|
||||
@@ -75,10 +75,13 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|mixed
|
||||
* @return bool
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,12 +243,15 @@ class LoginTwigRenderer
|
||||
|
||||
public function GetDefaultVars()
|
||||
{
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
|
||||
|
||||
$aVars = [
|
||||
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),
|
||||
'aPluginFormData' => $this->GetPluginFormData(),
|
||||
'sItopVersion' => ITOP_VERSION,
|
||||
'sVersionShort' => $sVersionShort,
|
||||
'sIconUrl' => $sIconUrl,
|
||||
'sDisplayIcon' => $sDisplayIcon,
|
||||
];
|
||||
|
||||
@@ -221,15 +221,15 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
if ($oUser != null) {
|
||||
if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token')) {
|
||||
throw new ForgotPasswordUserInputException('External accounts do not allow password reset');
|
||||
throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible'));
|
||||
}
|
||||
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
|
||||
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
|
||||
@@ -255,21 +255,17 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
case EMAIL_SEND_ERROR:
|
||||
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();
|
||||
$aVars = $oTwigContext->GetDefaultVars();
|
||||
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
|
||||
} catch (Exception $e) {
|
||||
$this->DisplayForgotPwdForm(true, $e->getMessage());
|
||||
}
|
||||
|
||||
$oTwigContext = new LoginTwigRenderer();
|
||||
$aVars = $oTwigContext->GetDefaultVars();
|
||||
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
|
||||
}
|
||||
|
||||
public function DisplayResetPwdForm($sErrorMessage = null)
|
||||
|
||||
@@ -1423,21 +1423,12 @@ class ShortcutMenuNode extends MenuNode
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
$sContext = $this->oShortcut->Get('context');
|
||||
try {
|
||||
$aContext = utils::Unserialize($sContext);
|
||||
if (isset($aContext['menu'])) {
|
||||
unset($aContext['menu']);
|
||||
}
|
||||
foreach ($aContext as $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();
|
||||
$aContext = unserialize($sContext);
|
||||
if (isset($aContext['menu'])) {
|
||||
unset($aContext['menu']);
|
||||
}
|
||||
foreach ($aContext as $sArgName => $sArgValue) {
|
||||
$aExtraParams[$sArgName] = $sArgValue;
|
||||
}
|
||||
return parent::GetHyperlink($aExtraParams);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ interface iNewsroomProvider
|
||||
* @param User $oUser The user for who to check if the provider is applicable.
|
||||
* return bool
|
||||
*/
|
||||
public function IsApplicable(?User $oUser = null);
|
||||
public function IsApplicable(User $oUser = null);
|
||||
|
||||
/**
|
||||
* The human readable (localized) label for this provider
|
||||
@@ -139,7 +139,7 @@ abstract class NewsroomProviderBase implements iNewsroomProvider
|
||||
*/
|
||||
abstract public function GetViewAllURL();
|
||||
|
||||
public function IsApplicable(?User $oUser = null)
|
||||
public function IsApplicable(User $oUser = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ abstract class Query extends cmdbAbstractObject
|
||||
* @return string|null
|
||||
* @since 3.1.0
|
||||
*/
|
||||
abstract public function GetExportUrl(?array $aValues = null): ?string;
|
||||
abstract public function GetExportUrl(array $aValues = null): ?string;
|
||||
|
||||
/**
|
||||
* Update last export information.
|
||||
@@ -227,7 +227,7 @@ class QueryOQL extends Query
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function GetExportUrl(?array $aValues = null): ?string
|
||||
public function GetExportUrl(array $aValues = null): ?string
|
||||
{
|
||||
try {
|
||||
// retrieve attributes
|
||||
|
||||
@@ -924,6 +924,11 @@ CSS;
|
||||
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths)
|
||||
{
|
||||
$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 (is_array($aThemeParameters['variable_imports'])) {
|
||||
@@ -931,14 +936,6 @@ CSS;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
return $aThemeParametersVariable;
|
||||
}
|
||||
@@ -970,9 +967,7 @@ CSS;
|
||||
}
|
||||
}
|
||||
}
|
||||
array_map(function ($sVariableValue) {
|
||||
return ltrim($sVariableValue);
|
||||
}, $aVariablesResults);
|
||||
array_map(function ($sVariableValue) { return ltrim($sVariableValue); }, $aVariablesResults);
|
||||
return $aVariablesResults;
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ JS
|
||||
<<<HTML
|
||||
<form id="ObjectsAddForm_{$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>
|
||||
<input type="hidden" id="count_{$this->sInputid}" value="0"/>
|
||||
</form>
|
||||
|
||||
@@ -27,9 +27,6 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
class UISearchFormForeignKeys
|
||||
{
|
||||
private $m_sRemoteClass;
|
||||
private $m_iInputId;
|
||||
|
||||
public function __construct($sTargetClass, $iInputId = null)
|
||||
{
|
||||
$this->m_sRemoteClass = $sTargetClass;
|
||||
@@ -43,7 +40,7 @@ class UISearchFormForeignKeys
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ShowModalSearchForeignKeys($oPage)
|
||||
public function ShowModalSearchForeignKeys($oPage, $sTitle)
|
||||
{
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
@@ -63,17 +60,52 @@ class UISearchFormForeignKeys
|
||||
]
|
||||
));
|
||||
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$sCancel = Dict::S('UI:Button:Cancel');
|
||||
$sAdd = Dict::S('UI:Button:Add');
|
||||
|
||||
$oPage->add(
|
||||
<<<HTML
|
||||
<form id="ObjectsAddForm_{$this->m_iInputId}">
|
||||
<div id="SearchResultsToAdd_{$this->m_iInputId}" style="vertical-align:top;height:100%;overflow:auto;padding:0;border:0;">
|
||||
<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>
|
||||
<input type="hidden" id="count_{$this->m_iInputId}" value="0"/>
|
||||
</form>
|
||||
HTML
|
||||
);
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$('#dlg_{$this->m_iInputId}').dialog({
|
||||
width: $(window).width()*0.8,
|
||||
height: $(window).height()*0.8,
|
||||
autoOpen: false,
|
||||
modal: true,
|
||||
resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes,
|
||||
buttons: [
|
||||
{
|
||||
text: Dict.S('UI:Button:Cancel'),
|
||||
class: "cancel ibo-is-alternative ibo-is-neutral",
|
||||
click: function() {
|
||||
$('#dlg_{$this->m_iInputId}').dialog('close');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: Dict.S('UI:Button:Add'),
|
||||
id: 'btn_ok_{$this->m_iInputId}',
|
||||
class: "ok ibo-is-regular ibo-is-primary",
|
||||
click: function() {
|
||||
oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});
|
||||
$('#SearchFormToAdd_{$this->m_iInputId} form').on('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);
|
||||
$('#SearchFormToAdd_{$this->m_iInputId}').on('resize', oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
public function GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter)
|
||||
@@ -87,4 +119,31 @@ HTML
|
||||
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for objects to be linked to the current object (i.e "remote" objects)
|
||||
*
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ListResultsSearchForeignKeys(WebPage $oP, $sRemoteClass = '')
|
||||
{
|
||||
if ($sRemoteClass != '') {
|
||||
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
} else {
|
||||
// No remote class specified use the one defined in the linkedset
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
}
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display(
|
||||
$oP,
|
||||
"ResultsToAdd_{$this->m_iInputId}",
|
||||
['menu' => false, 'cssCount' => "#count_{$this->m_iInputId}", 'selection_mode' => true, 'table_id' => "add_{$this->m_iInputId}"]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -122,11 +122,6 @@ class utils
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
|
||||
/**
|
||||
* @var string For module codes (e.g. `itop-portal-base`, `combodo-webhook-integration`, `some-module-code-x.y`, ...)
|
||||
* @since 3.2.3 3.3.0 N°8554
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_MODULE_CODE = 'module_code';
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.7.10 3.0.0
|
||||
@@ -186,9 +181,6 @@ class utils
|
||||
|
||||
protected static function LoadParamFile($sParamFile)
|
||||
{
|
||||
if (utils::RealPath($sParamFile, APPROOT) !== false) {
|
||||
throw new Exception("File '".utils::HtmlEntities($sParamFile)."' should be outside iTop");
|
||||
}
|
||||
if (!file_exists($sParamFile)) {
|
||||
throw new Exception("Could not find the parameter file: '".utils::HtmlEntities($sParamFile)."'");
|
||||
}
|
||||
@@ -284,7 +276,9 @@ class utils
|
||||
}
|
||||
// Read and record the value for switching the archive mode
|
||||
$iCurrent = self::ReadParam('with-archive', $iDefault);
|
||||
Session::Set('archive_mode', $iCurrent);
|
||||
if (Session::IsInitialized()) {
|
||||
Session::Set('archive_mode', $iCurrent);
|
||||
}
|
||||
// Read and use the value for the current page (web services)
|
||||
$iCurrent = self::ReadParam('with_archive', $iCurrent, true);
|
||||
self::$bPageMode = ($iCurrent == 1);
|
||||
@@ -396,7 +390,6 @@ class utils
|
||||
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
|
||||
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
|
||||
* @since 3.2.1-1 N°8242 Allow value to be an array for every filter
|
||||
* @since 3.2.3 3.3.0 N°8554 new case for ENUM_SANITIZATION_FILTER_MODULE_CODE
|
||||
*
|
||||
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
|
||||
*/
|
||||
@@ -484,7 +477,7 @@ class utils
|
||||
);
|
||||
break;
|
||||
|
||||
// For XML / HTML node selector
|
||||
// For XML / HTML node id selector
|
||||
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
|
||||
$retValue = filter_var(
|
||||
$value,
|
||||
@@ -497,15 +490,6 @@ class utils
|
||||
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_MODULE_CODE:
|
||||
// Module codes allow all alphabets letters, numbers, dash and dot characters
|
||||
$retValue = filter_var(
|
||||
$value,
|
||||
FILTER_VALIDATE_REGEXP,
|
||||
['options' => ['regexp' => '/^[\p{L}\d.-]+$/u']]
|
||||
);
|
||||
break;
|
||||
|
||||
// For URL
|
||||
case static::ENUM_SANITIZATION_FILTER_URL:
|
||||
$retValue = filter_var($value, FILTER_SANITIZE_URL);
|
||||
@@ -974,7 +958,7 @@ class utils
|
||||
return self::$oConfig;
|
||||
}
|
||||
|
||||
$sProductionEnvConfigPath = self::GetConfigFilePath(ITOP_DEFAULT_ENV);
|
||||
$sProductionEnvConfigPath = self::GetConfigFilePath('production');
|
||||
if (file_exists($sProductionEnvConfigPath)) {
|
||||
$oProductionEnvDiskConfig = new Config($sProductionEnvConfigPath);
|
||||
self::SetConfig($oProductionEnvDiskConfig);
|
||||
@@ -1300,7 +1284,7 @@ class utils
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function ExecITopScript(string $sScriptName, array $aArguments, ?string $sAuthUser = null, ?string $sAuthPwd = null)
|
||||
public static function ExecITopScript(string $sScriptName, array $aArguments, string $sAuthUser = null, string $sAuthPwd = null)
|
||||
{
|
||||
$aDisabled = explode(', ', ini_get('disable_functions'));
|
||||
if (in_array('exec', $aDisabled)) {
|
||||
@@ -1390,7 +1374,7 @@ class utils
|
||||
* @return string A path to a folder into which any module can store cache data
|
||||
* The corresponding folder is created or cleaned upon code compilation
|
||||
*/
|
||||
public static function GetCachePath(?string $sEnvironment = null): string
|
||||
public static function GetCachePath(string $sEnvironment = null): string
|
||||
{
|
||||
if (is_null($sEnvironment)) {
|
||||
$sEnvironment = MetaModel::GetEnvironment();
|
||||
@@ -1453,12 +1437,6 @@ class utils
|
||||
|
||||
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
|
||||
/** @var \DBObjectSet $param */
|
||||
|
||||
// Check if the user has the right to read the objects of this list, otherwise do not propose any action (eg. configure this list, export, etc.)
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_READ, $param) !== UR_ALLOWED_YES) {
|
||||
break;
|
||||
}
|
||||
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink(true);
|
||||
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
|
||||
@@ -1917,9 +1895,7 @@ SQL;
|
||||
$aResponseHeaders[$sName] = $sValue;
|
||||
}
|
||||
}
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
curl_close($ch);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
@@ -2105,9 +2081,7 @@ SQL;
|
||||
}
|
||||
|
||||
// Remove any remaining nulls (for positions that weren't referenced)
|
||||
$aReplacements = array_filter($aReplacements, static function ($val) {
|
||||
return $val !== null;
|
||||
});
|
||||
$aReplacements = array_filter($aReplacements, static function ($val) { return $val !== null; });
|
||||
} else {
|
||||
// For non-positional, we need to map each position
|
||||
$aReplacements = [];
|
||||
@@ -3146,76 +3120,4 @@ TXT
|
||||
|
||||
return $aTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP unserialize encapsulation, allow throwing exception when not allowed object class is detected (for security hardening)
|
||||
*
|
||||
* @param string $data data to unserialize
|
||||
* @param array $aOptions PHP @unserialise options
|
||||
* @param bool $bThrowNotAllowedObjectClassException flag to throw exception
|
||||
*
|
||||
* @return mixed PHP @unserialise return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function Unserialize(string $data, array $aOptions = ['allowed_classes' => false], bool $bThrowNotAllowedObjectClassException = true): mixed
|
||||
{
|
||||
$data = unserialize($data, $aOptions);
|
||||
|
||||
if ($bThrowNotAllowedObjectClassException) {
|
||||
try {
|
||||
self::AssertNoIncompleteClassDetected($data);
|
||||
} catch (Exception $e) {
|
||||
throw new CoreException('Unserialization failed because an incomplete class was detected.', [], '', $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that data provided doesn't contain any incomplete class.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AssertNoIncompleteClassDetected(mixed $data): void
|
||||
{
|
||||
if (is_object($data)) {
|
||||
if ($data instanceof __PHP_Incomplete_Class) {
|
||||
throw new Exception('__PHP_Incomplete_Class_Name object detected');
|
||||
}
|
||||
foreach (get_object_vars($data) as $property) {
|
||||
self::AssertNoIncompleteClassDetected($property);
|
||||
}
|
||||
} elseif (is_array($data)) {
|
||||
foreach ($data as $value) {
|
||||
self::AssertNoIncompleteClassDetected($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read memory limit from the php.ini file
|
||||
*
|
||||
* @return int Memory limit in bytes
|
||||
*/
|
||||
public static function GetMemoryLimit(): int
|
||||
{
|
||||
$sLimit = ini_get('memory_limit');
|
||||
if ($sLimit == '-1') {
|
||||
return 128 * 1048576;
|
||||
}
|
||||
switch (substr($sLimit, -1)) {
|
||||
case 'M':
|
||||
case 'm':
|
||||
return (int)$sLimit * 1048576;
|
||||
case 'K':
|
||||
case 'k':
|
||||
return (int)$sLimit * 1024;
|
||||
case 'G':
|
||||
case 'g':
|
||||
return (int)$sLimit * 1073741824;
|
||||
default:
|
||||
return (int)$sLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "project",
|
||||
"license": "AGPL-3.0-only",
|
||||
"require": {
|
||||
"php": ">=8.2.0 <8.5.0",
|
||||
"php": ">=8.1.0 <8.4.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -12,50 +12,38 @@
|
||||
"ext-json": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-soap": "*",
|
||||
"apereo/phpcas": "dev-master",
|
||||
"apereo/phpcas": "~1.6.0",
|
||||
"firebase/php-jwt": "^6.4.0",
|
||||
"guzzlehttp/guzzle": "^7.5.1",
|
||||
"league/oauth2-google": "^4.0.1",
|
||||
"nikic/php-parser": "dev-master",
|
||||
"pear/archive_tar": "~1.4.14",
|
||||
"pelago/emogrifier": "^7.2.0",
|
||||
"psr/log": "^3.0.0",
|
||||
"scssphp/scssphp": "dev-combodo/1.x",
|
||||
"scssphp/scssphp": "^1.12.1",
|
||||
"soundasleep/html2text": "~2.1",
|
||||
"symfony/console": "~6.4.0",
|
||||
"symfony/dotenv": "~6.4.0",
|
||||
"symfony/form": "^6.4",
|
||||
"symfony/framework-bundle": "~6.4.0",
|
||||
"symfony/http-foundation": "~6.4.0",
|
||||
"symfony/http-kernel": "~6.4.0",
|
||||
"symfony/runtime": "~6.4.0",
|
||||
"symfony/security-csrf": "~6.4.0",
|
||||
"symfony/mailer": "^6.4",
|
||||
"symfony/security-csrf": "^6.4",
|
||||
"symfony/twig-bundle": "~6.4.0",
|
||||
"symfony/validator" : "~6.4.0",
|
||||
"symfony/var-dumper": "~6.4.0",
|
||||
"symfony/validator" : "^6.4",
|
||||
"symfony/yaml": "~6.4.0",
|
||||
"symfony/mailer": "~6.4.0",
|
||||
"tecnickcom/tcpdf": "^6.6.0",
|
||||
"thenetworg/oauth2-azure": "^2.0",
|
||||
"soundasleep/html2text": "~2.1"
|
||||
"thenetworg/oauth2-azure": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/debug-bundle": "~6.4.0",
|
||||
"symfony/stopwatch": "~6.4.0",
|
||||
"symfony/web-profiler-bundle": "~6.4.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/Combodo/PHP-Parser"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/EsupPortail/phpCAS"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/combodo-itop-libs/scssphp"
|
||||
}
|
||||
],
|
||||
"repositories": [{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/Combodo/PHP-Parser"
|
||||
}],
|
||||
"suggest": {
|
||||
"ext-libsodium": "Required to use the AttributeEncryptedString.",
|
||||
"ext-openssl": "Can be used as a polyfill if libsodium is not installed",
|
||||
@@ -67,7 +55,7 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.2.0"
|
||||
"php": "8.1.0"
|
||||
},
|
||||
"vendor-dir": "lib",
|
||||
"preferred-install": {
|
||||
@@ -75,10 +63,7 @@
|
||||
},
|
||||
"sort-packages": true,
|
||||
"classmap-authoritative": true,
|
||||
"platform-check": true,
|
||||
"allow-plugins": {
|
||||
"symfony/runtime": true
|
||||
}
|
||||
"platform-check": true
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
||||
1011
composer.lock
generated
1011
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -234,11 +234,10 @@ abstract class Action extends cmdbAbstractObject
|
||||
}
|
||||
|
||||
$oActionFilter = DBObjectSearch::FromOQL($sActionQueryOql, $aActionQueryParams);
|
||||
$oActionFilter->AllowAllData();
|
||||
$oSet = new DBObjectSet($oActionFilter, ['date' => false]);
|
||||
|
||||
$sPanelTitle = Dict::Format('Action:last_executions_tab_panel_title', $sActionQueryLimit);
|
||||
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle, 'display_unauthorized_objects' => true]);
|
||||
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle]);
|
||||
|
||||
$oPage->AddUiBlock($oExecutionsListBlock);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
|
||||
@@ -199,7 +199,7 @@ class CellStatus_SearchIssue extends CellStatus_Issue
|
||||
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
|
||||
* @param string|null $sAllowedValuesSearch : used to search all allowed values
|
||||
*/
|
||||
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, ?string $sAllowedValuesSearch = null)
|
||||
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, string $sAllowedValuesSearch = null)
|
||||
{
|
||||
parent::__construct(null, null, $sReason);
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
@@ -876,7 +876,7 @@ class BulkChange
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
protected function CreateObject(&$aResult, $iRow, $aRowData, ?CMDBChange $oChange = null)
|
||||
protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
|
||||
{
|
||||
$oTargetObj = MetaModel::NewObject($this->m_sClass);
|
||||
|
||||
@@ -965,7 +965,7 @@ class BulkChange
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, ?CMDBChange $oChange = null)
|
||||
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null)
|
||||
{
|
||||
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
|
||||
|
||||
@@ -1008,7 +1008,7 @@ class BulkChange
|
||||
*
|
||||
* @throws \BulkChangeException
|
||||
*/
|
||||
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, ?CMDBChange $oChange = null)
|
||||
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, CMDBChange $oChange = null)
|
||||
{
|
||||
$aResult[$iRow] = $this->PrepareMissingObject($oTargetObj, $aErrors);
|
||||
|
||||
@@ -1043,7 +1043,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
|
||||
public function Process(?CMDBChange $oChange = null)
|
||||
public function Process(CMDBChange $oChange = null)
|
||||
{
|
||||
if ($oChange) {
|
||||
CMDBObject::SetCurrentChange($oChange);
|
||||
|
||||
@@ -20,15 +20,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\ParserFactory;
|
||||
use Combodo\iTop\Config\Validator\iTopConfigAstValidator;
|
||||
use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator;
|
||||
|
||||
define('ITOP_APPLICATION', 'iTop');
|
||||
define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
@@ -85,7 +78,6 @@ define('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']');
|
||||
define('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random generated key later (if possible)
|
||||
define('DEFAULT_ENCRYPTION_LIB', 'Mcrypt'); // We'll define the best encryption available later
|
||||
define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
|
||||
/**
|
||||
* Config
|
||||
* configuration data (this class cannot not be localized, because it is responsible for loading the dictionaries)
|
||||
@@ -94,7 +86,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
* @see utils::GetConfig() to load config from the current env, if metamodel is not loaded
|
||||
* @package iTopORM
|
||||
*/
|
||||
#[AllowDynamicProperties] class Config
|
||||
class Config
|
||||
{
|
||||
//protected $m_bIsLoaded = false;
|
||||
protected $m_sFile = '';
|
||||
@@ -879,14 +871,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'ext_auth_variable' => [
|
||||
'type' => 'string',
|
||||
'description' => 'External authentication expression (allowed: $_SERVER[\'key\'], $_COOKIE[\'key\'], $_REQUEST[\'key\'], getallheaders()[\'Header-Name\'])',
|
||||
'default' => '$_SERVER[\'REMOTE_USER\']',
|
||||
'value' => '$_SERVER[\'REMOTE_USER\']',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'login_debug' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Activate the login FSM debug',
|
||||
@@ -944,7 +928,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
'type' => 'string',
|
||||
'description' => 'Actions that are available as direct buttons next to the "Actions" menu',
|
||||
// examples... not used
|
||||
'default' => 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up',
|
||||
'default' => 'UI:Menu:Modify,UI:Menu:New',
|
||||
'value' => 'UI:Menu:Modify',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
@@ -1189,8 +1173,8 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
'tracking_level_linked_set_default' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Default tracking level if not explicitly set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)',
|
||||
'default' => LINKSET_TRACKING_NONE,
|
||||
'value' => LINKSET_TRACKING_NONE,
|
||||
'default' => LINKSET_TRACKING_LIST,
|
||||
'value' => LINKSET_TRACKING_LIST,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
@@ -1629,7 +1613,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'search_manual_submit' => [
|
||||
'type' => 'bool',
|
||||
'type' => 'array',
|
||||
'description' => 'Force manual submit of search all requests',
|
||||
'default' => false,
|
||||
'value' => true,
|
||||
@@ -1756,14 +1740,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.disable_joined_classes_filter' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, scope filters aren\'t applied to joined classes or union classes not directly listed in the SELECT clause.',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.hide_administrators' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
|
||||
@@ -1772,14 +1748,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.disable_exec_forced_login_for_all_enpoints' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, when no delegated authentication module is defined, no login will be forced on modules exec endpoints',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'behind_reverse_proxy' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
|
||||
@@ -1986,6 +1954,11 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
*/
|
||||
protected $m_sDefaultLanguage;
|
||||
|
||||
/**
|
||||
* @var string Name of the PHP variable in which external authentication information is passed by the web server
|
||||
*/
|
||||
protected $m_sExtAuthVariable;
|
||||
|
||||
/**
|
||||
* @var string Encryption key used for all attributes of type "encrypted string". Can be set to a random value
|
||||
* unless you want to import a database from another iTop instance, in which case you must use
|
||||
@@ -2020,24 +1993,17 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
*/
|
||||
protected $m_sAppSecret;
|
||||
|
||||
private bool $bPreserveComments;
|
||||
private bool $bLegacyEvaluation;
|
||||
|
||||
/**
|
||||
* Config constructor.
|
||||
*
|
||||
* @param string|null $sConfigFile
|
||||
* @param bool $bLoadConfig
|
||||
* @param bool $bPreserveComments
|
||||
* @param bool $bLegacyEvaluation
|
||||
*
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function __construct($sConfigFile = null, $bLoadConfig = true, bool $bPreserveComments = false, bool $bLegacyEvaluation = false)
|
||||
public function __construct($sConfigFile = null, $bLoadConfig = true)
|
||||
{
|
||||
$this->bPreserveComments = $bPreserveComments;
|
||||
$this->bLegacyEvaluation = $bLegacyEvaluation;
|
||||
$this->oConfigPlaceholdersResolver = new ConfigPlaceholdersResolver();
|
||||
|
||||
$this->m_sFile = $sConfigFile;
|
||||
@@ -2061,6 +2027,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
$this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL;
|
||||
$this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED;
|
||||
$this->m_sDefaultLanguage = 'EN US';
|
||||
$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
|
||||
$this->m_aCharsets = [];
|
||||
$this->m_bQueryCacheEnabled = DEFAULT_QUERY_CACHE_ENABLED;
|
||||
$this->m_iPasswordHashAlgo = DEFAULT_HASH_ALGO;
|
||||
@@ -2092,7 +2059,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
}
|
||||
$this->Set('app_root_url', $sAppRootUrl);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2139,39 +2105,9 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
// So, I've implemented a solution suggested in the PHP doc (search for phpWrapper)
|
||||
try {
|
||||
ob_start();
|
||||
/*file_put_contents(APPROOT . '/bugged-conf.php', '?'.'>'.trim($sConfigCode));
|
||||
$e = new \Exception('');
|
||||
var_dump($e->getTraceAsString());*/
|
||||
eval('?'.'>'.trim($sConfigCode));
|
||||
$sNoise = trim(ob_get_contents());
|
||||
ob_end_clean();
|
||||
|
||||
if ($this->bPreserveComments || ! $this->bLegacyEvaluation) {
|
||||
try {
|
||||
$oParser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
foreach ($oParser->parse($sConfigCode) as $oNode) {
|
||||
if ($oNode instanceof \PhpParser\Node\Stmt\Expression) {
|
||||
/** @var \PhpParser\Node\Stmt\Expression $oNode */
|
||||
$oExpr = $oNode->expr;
|
||||
if ($oExpr instanceof Assign) {
|
||||
/** @var Assign $oExpr */
|
||||
$oVar = $oExpr->var;
|
||||
if ($oVar instanceof Variable && $oVar->name === "MyModuleSettings") {
|
||||
if ($oExpr->expr instanceof Array_) {
|
||||
$oPhpExpressionEvaluator = new PhpExpressionEvaluator();
|
||||
if (! $this->bLegacyEvaluation) {
|
||||
$aArrayWithComments = $oPhpExpressionEvaluator->GetArray($oExpr->expr, $this->bPreserveComments);
|
||||
$MyModuleSettings = array_replace_recursive($aArrayWithComments, $MyModuleSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Error $e) {
|
||||
var_dump($e);
|
||||
}
|
||||
}
|
||||
} catch (Error $e) {
|
||||
// PHP 7
|
||||
throw new ConfigException(
|
||||
@@ -2237,6 +2173,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
$this->m_aModuleSettings = isset($MyModuleSettings) ? $MyModuleSettings : [];
|
||||
|
||||
$this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US';
|
||||
$this->m_sExtAuthVariable = isset($MySettings['ext_auth_variable']) ? trim($MySettings['ext_auth_variable']) : DEFAULT_EXT_AUTH_VARIABLE;
|
||||
$this->m_sEncryptionKey = isset($MySettings['encryption_key']) ? trim($MySettings['encryption_key']) : $this->m_sEncryptionKey;
|
||||
$this->m_sEncryptionLibrary = isset($MySettings['encryption_library']) ? trim($MySettings['encryption_library']) : $this->m_sEncryptionLibrary;
|
||||
$this->m_aCharsets = isset($MySettings['csv_import_charsets']) ? $MySettings['csv_import_charsets'] : [];
|
||||
@@ -2398,73 +2335,9 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
return explode('|', $this->m_aSettings['allowed_login_types']['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|mixed
|
||||
* @since 3.2.3 return the parsed value instead of an unsecured variable name
|
||||
*/
|
||||
public function GetExternalAuthenticationVariable()
|
||||
{
|
||||
$sExpression = $this->Get('ext_auth_variable');
|
||||
$aParsed = $this->ParseExternalAuthVariableExpression($sExpression);
|
||||
if ($aParsed === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sKey = $aParsed['key'];
|
||||
switch ($aParsed['type']) {
|
||||
case 'server':
|
||||
return $_SERVER[$sKey] ?? false;
|
||||
case 'cookie':
|
||||
return $_COOKIE[$sKey] ?? false;
|
||||
case 'request':
|
||||
return $_REQUEST[$sKey] ?? false;
|
||||
case 'header':
|
||||
if (!function_exists('getallheaders')) {
|
||||
return false;
|
||||
}
|
||||
$aHeaders = getallheaders();
|
||||
if (!is_array($aHeaders)) {
|
||||
return false;
|
||||
}
|
||||
return $aHeaders[$sKey] ?? false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sExpression
|
||||
* @return array|null
|
||||
*/
|
||||
private function ParseExternalAuthVariableExpression($sExpression)
|
||||
{
|
||||
// If it's a configuration parameter it's probably already trimmed, but just in case
|
||||
$sExpression = trim((string) $sExpression);
|
||||
if ($sExpression === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Match $_SERVER/$_COOKIE/$_REQUEST['key'] with optional whitespace and single/double quotes.
|
||||
if (preg_match('/^\$_(SERVER|COOKIE|REQUEST)\s*\[\s*(["\'])\s*([^"\']+)\2\s*\]\s*$/', $sExpression, $aMatches) === 1) {
|
||||
$sContext = strtoupper($aMatches[1]);
|
||||
$sKey = $aMatches[3];
|
||||
return [
|
||||
'type' => strtolower($sContext),
|
||||
'key' => $sKey,
|
||||
'normalized' => '$_'.$sContext.'[\''.$sKey.'\']',
|
||||
];
|
||||
}
|
||||
|
||||
// Match getallheaders()['Header-Name'] in a case-insensitive way.
|
||||
if (preg_match('/^getallheaders\(\)\s*\[\s*(["\'])\s*([^"\']+)\1\s*\]\s*$/i', $sExpression, $aMatches) === 1) {
|
||||
$sKey = $aMatches[2];
|
||||
return [
|
||||
'type' => 'header',
|
||||
'key' => $sKey,
|
||||
'normalized' => 'getallheaders()[\''.$sKey.'\']',
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->m_sExtAuthVariable;
|
||||
}
|
||||
|
||||
public function GetCSVImportCharsets()
|
||||
@@ -2560,7 +2433,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
|
||||
public function SetExternalAuthenticationVariable($sExtAuthVariable)
|
||||
{
|
||||
$this->Set('ext_auth_variable', $sExtAuthVariable);
|
||||
$this->m_sExtAuthVariable = $sExtAuthVariable;
|
||||
}
|
||||
|
||||
public function SetEncryptionKey($sKey)
|
||||
@@ -2614,6 +2487,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
$aSettings['fast_reload_interval'] = $this->m_iFastReloadInterval;
|
||||
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
|
||||
$aSettings['default_language'] = $this->m_sDefaultLanguage;
|
||||
$aSettings['ext_auth_variable'] = $this->m_sExtAuthVariable;
|
||||
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
|
||||
$aSettings['encryption_library'] = $this->m_sEncryptionLibrary;
|
||||
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
|
||||
@@ -2716,6 +2590,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
// Old fashioned remaining values
|
||||
$aOtherValues = [
|
||||
'default_language' => $this->m_sDefaultLanguage,
|
||||
'ext_auth_variable' => $this->m_sExtAuthVariable,
|
||||
'encryption_key' => $this->m_sEncryptionKey,
|
||||
'encryption_library' => $this->m_sEncryptionLibrary,
|
||||
'csv_import_charsets' => $this->m_aCharsets,
|
||||
@@ -2762,15 +2637,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
foreach ($this->m_aModuleSettings as $sModule => $aProperties) {
|
||||
fwrite($hFile, "\t'$sModule' => array (\n");
|
||||
foreach ($aProperties as $sProperty => $value) {
|
||||
if (is_string($value) && false !== strpos($value, 'PhpParserComment')) {
|
||||
$value = preg_replace(
|
||||
["/.*StartPhpParserComment/", "/EndPhpParserComment/"],
|
||||
['', ''],
|
||||
$value
|
||||
);
|
||||
fwrite($hFile, "\t\t$value\n");
|
||||
continue;
|
||||
}
|
||||
$sNiceExport = self::PrettyVarExport($this->oItopConfigParser->GetVarValue('MyModuleSettings', $sProperty), $value, "\t\t");
|
||||
fwrite($hFile, "\t\t'$sProperty' => $sNiceExport,\n");
|
||||
}
|
||||
@@ -2809,13 +2675,14 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
*
|
||||
* @param array $aParamValues
|
||||
* @param ?string $sModulesDir
|
||||
* @param bool $bPreserveModuleSettings
|
||||
*
|
||||
* @return void The current object is modified directly
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function UpdateFromParams($aParamValues, $sModulesDir = null)
|
||||
public function UpdateFromParams($aParamValues, $sModulesDir = null, $bPreserveModuleSettings = false)
|
||||
{
|
||||
if (isset($aParamValues['application_path'])) {
|
||||
$this->Set('app_root_url', $aParamValues['application_path']);
|
||||
@@ -2863,10 +2730,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
} else {
|
||||
$aSelectedModules = null;
|
||||
}
|
||||
|
||||
if (! is_null($sModulesDir)) {
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
}
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
|
||||
if (isset($aParamValues['source_dir'])) {
|
||||
$this->Set('source_dir', $aParamValues['source_dir']);
|
||||
@@ -2884,13 +2748,17 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function UpdateIncludes(string $sModulesDir, $aSelectedModules = null)
|
||||
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
|
||||
{
|
||||
if ($sModulesDir === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the arrays below with default values for the application...
|
||||
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
|
||||
$aAddOns = $oEmptyConfig->GetAddOns();
|
||||
|
||||
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
|
||||
$aModules = ModuleDiscovery::GetAvailableModules([APPROOT.$sModulesDir]);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {
|
||||
@@ -2940,7 +2808,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty format a var_export'ed value so that (if possible) the indentation is preserved on every line
|
||||
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
|
||||
*
|
||||
* @param array $aParserValue
|
||||
* @param mixed $value The value to export
|
||||
@@ -2957,19 +2825,12 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$sExport = var_export($value, true);
|
||||
if (strpos($sExport, 'PhpParserComment')) {
|
||||
$sExport = preg_replace(
|
||||
["/.*StartPhpParserComment/", "/EndPhpParserComment',/"],
|
||||
['', ''],
|
||||
$sExport
|
||||
);
|
||||
}
|
||||
$sNiceExport = str_replace(["\r\n", "\n", "\r"], "\n".$sIndentation, trim($sExport));
|
||||
if (!$bForceIndentation) {
|
||||
/** @var array $aImported */
|
||||
$aImported = null;
|
||||
eval('$aImported='.$sNiceExport.';');
|
||||
// Check if adding the indentations at the beginning of each line
|
||||
// Check if adding the identations at the beginning of each line
|
||||
// did not modify the values (in case of a string containing a line break)
|
||||
if ($aImported != $value) {
|
||||
$sNiceExport = $sExport;
|
||||
@@ -2978,6 +2839,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
|
||||
return $sNiceExport;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ConfigPlaceholdersResolver
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\ExportHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
|
||||
@@ -14,6 +13,7 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectUIBlockFactory
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
|
||||
use Combodo\iTop\Application\Helper\ExportHelper;
|
||||
use Combodo\iTop\Application\WebPage\Page;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
@@ -55,8 +55,6 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
|
||||
|
||||
$this->aStatusInfo['ignore_excel_sanitization'] = (bool)utils::ReadParam('ignore_excel_sanitization', 0, true, utils::ENUM_SANITIZATION_FILTER_INTEGER);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('csv_date_format_radio', '');
|
||||
switch ($sDateFormatRadio) {
|
||||
case 'default':
|
||||
@@ -225,10 +223,6 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oFieldSetDate->AddSubBlock($oRadioCustom);
|
||||
|
||||
$oFieldSetSecurity = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:Security'));
|
||||
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetSecurity));
|
||||
$oFieldSetSecurity->AddSubBlock(ExportHelper::GetInputForSanitizeExcelExport());
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
@@ -270,13 +264,6 @@ EOF
|
||||
default:
|
||||
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
|
||||
}
|
||||
|
||||
// If the option to ignore Excel sanitization is not set or explicitly set to false, apply sanitization
|
||||
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
|
||||
return ExportHelper::SanitizeField($sRet, $this->aStatusInfo['text_qualifier'] ?? '');
|
||||
}
|
||||
|
||||
// The option to ignore Excel sanitization is explicitly set to true: return the raw value without sanitization
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
@@ -350,12 +337,6 @@ EOF
|
||||
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
|
||||
}
|
||||
}
|
||||
|
||||
// If the option to ignore Excel sanitization is not set or absent, sanitize the field
|
||||
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
|
||||
$sField = ExportHelper::SanitizeField($sField, $this->aStatusInfo['text_qualifier']);
|
||||
}
|
||||
|
||||
if ($this->aStatusInfo['charset'] != 'UTF-8') {
|
||||
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
|
||||
// and thus to convert field by field and not the whole row or file at once (see ticket N°991)
|
||||
|
||||
@@ -425,7 +425,7 @@
|
||||
</php_parent>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,grant_by_profile,silo</category>
|
||||
<category>core/cmdb,view_in_gui</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>priv_event_newsroom</db_table>
|
||||
@@ -904,7 +904,7 @@
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>Event</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,grant_by_profile,silo</category>
|
||||
<category>core/cmdb,view_in_gui</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="message" xsi:type="AttributeText"/>
|
||||
|
||||
@@ -2567,7 +2567,7 @@ abstract class DBObject implements iDisplay
|
||||
*
|
||||
* @see \RestUtils::FindObjectFromKey for the same check in the REST endpoint
|
||||
*/
|
||||
final public function CheckChangedExtKeysValues(?callable $oIsObjectLoadableCallback = null)
|
||||
final public function CheckChangedExtKeysValues(callable $oIsObjectLoadableCallback = null)
|
||||
{
|
||||
if (is_null($oIsObjectLoadableCallback)) {
|
||||
$oIsObjectLoadableCallback = function ($sClass, $sId) {
|
||||
@@ -3727,7 +3727,7 @@ abstract class DBObject implements iDisplay
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, ?array $aAttributes = null): void
|
||||
private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, array $aAttributes = null): void
|
||||
{
|
||||
if (is_null($oObject)) {
|
||||
return;
|
||||
@@ -5108,8 +5108,8 @@ abstract class DBObject implements iDisplay
|
||||
protected function GetReferencingObjectsForDeletion($bAllowAllData = false)
|
||||
{
|
||||
$aDependentObjects = [];
|
||||
$aReferencingMe = MetaModel::EnumReferencingClasses(get_class($this));
|
||||
foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) {
|
||||
$aRererencingMe = MetaModel::EnumReferencingClasses(get_class($this));
|
||||
foreach ($aRererencingMe as $sRemoteClass => $aExtKeys) {
|
||||
/** @var \AttributeExternalKey $oExtKeyAttDef */
|
||||
foreach ($aExtKeys as $sExtKeyAttCode => $oExtKeyAttDef) {
|
||||
// skip if external key doesn't require the deletion cascading
|
||||
|
||||
@@ -1932,37 +1932,4 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBObjectSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBObjectSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
|
||||
$aClassesToFilter = $this->GetJoinedClasses();
|
||||
}
|
||||
|
||||
// Apply filter (this is similar to the one in DBSearch but the factorization could make it less readable)
|
||||
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +650,7 @@ abstract class DBSearch
|
||||
*
|
||||
* @throws OQLException
|
||||
*/
|
||||
public static function FromOQL($sQuery, $aParams = null, ?ModelReflection $oMetaModel = null)
|
||||
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel = null)
|
||||
{
|
||||
if (empty($sQuery)) {
|
||||
return null;
|
||||
@@ -1048,7 +1048,21 @@ abstract class DBSearch
|
||||
*/
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
|
||||
{
|
||||
$oSearch = $this->ApplyDataFilters();
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) {
|
||||
foreach ($this->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($aGroupByExpr)) {
|
||||
foreach ($aGroupByExpr as $sAlias => $oGroupByExp) {
|
||||
@@ -1510,33 +1524,4 @@ abstract class DBSearch
|
||||
* @return array{\VariableExpression}
|
||||
*/
|
||||
abstract public function GetExpectedArguments(): array;
|
||||
|
||||
/**
|
||||
* Apply data filters to the search, if needed
|
||||
*
|
||||
* @return DBSearch
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class DBUnionSearch extends DBSearch
|
||||
public function AllowAllData($bAllowAllData = true)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch) {
|
||||
$oSearch->AllowAllData($bAllowAllData);
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,30 +673,4 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
return $aVariableCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBUnionSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBUnionSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
|
||||
return parent::ApplyDataFilters();
|
||||
}
|
||||
|
||||
// Apply filters per sub-search
|
||||
$aFilteredSearches = [];
|
||||
foreach ($this->GetSearches() as $oSubSearch) {
|
||||
// Recursively call ApplyDataFilters on sub-searches
|
||||
$aFilteredSearches[] = $oSubSearch->ApplyDataFilters();
|
||||
}
|
||||
|
||||
$oSearch = new DBUnionSearch($aFilteredSearches);
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,10 +360,10 @@ class DesignElement extends \DOMElement
|
||||
* @param string $sTagName
|
||||
* @param string|null $sDefault
|
||||
*
|
||||
* @return null|string
|
||||
* @return string
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
public function GetChildText($sTagName, $sDefault = null): ?string
|
||||
public function GetChildText($sTagName, $sDefault = null)
|
||||
{
|
||||
$sRet = $sDefault;
|
||||
if ($oChild = $this->GetOptionalElement($sTagName)) {
|
||||
@@ -453,7 +453,7 @@ class DesignElement extends \DOMElement
|
||||
* @throws Exception
|
||||
* @since 3.1.2 3.2.0 N°6974
|
||||
*/
|
||||
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, ?string $sSearchId = null): ?DesignElement
|
||||
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null): ?DesignElement
|
||||
{
|
||||
$oNodes = self::_FindNodes($oParent, $oRefNode, $sSearchId);
|
||||
if ($oNodes instanceof DOMNodeList) {
|
||||
@@ -477,7 +477,7 @@ class DesignElement extends \DOMElement
|
||||
* @return \DOMNodeList|false|mixed
|
||||
* @since 3.1.2 3.2.0 N°6974
|
||||
*/
|
||||
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, ?string $sSearchId = null)
|
||||
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null)
|
||||
{
|
||||
if ($oParent instanceof DOMDocument) {
|
||||
$oDoc = $oParent->firstChild->ownerDocument;
|
||||
|
||||
@@ -632,7 +632,7 @@ class DisplayableGroupNode extends DisplayableNode
|
||||
$this->aObjects = [];
|
||||
}
|
||||
|
||||
public function AddObject(?DBObject $oObj = null)
|
||||
public function AddObject(DBObject $oObj = null)
|
||||
{
|
||||
if (is_object($oObj)) {
|
||||
$sPrevClass = $this->GetObjectClass();
|
||||
|
||||
@@ -26,7 +26,7 @@ class Event extends DBObject implements iDisplay
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -120,7 +120,7 @@ class EventNotification extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -156,7 +156,7 @@ class EventNotificationEmail extends EventNotification
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -178,7 +178,7 @@ class EventNotificationEmail extends EventNotification
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', ['date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_class', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body', 'attachments']); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['date', 'message', 'to', 'cc', 'bcc', 'subject', 'attachments']); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('list', ['date', 'message', 'to', 'subject', 'attachments']); // Attributes to be displayed for a list
|
||||
|
||||
// Search criteria
|
||||
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
@@ -192,7 +192,7 @@ class EventIssue extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -286,7 +286,7 @@ class EventWebService extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -321,7 +321,7 @@ class EventRestService extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -356,7 +356,7 @@ class EventLoginUsage extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -394,7 +394,7 @@ class EventOnObject extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\ExportHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
|
||||
use Combodo\iTop\Application\Helper\ExportHelper;
|
||||
use Combodo\iTop\Application\WebPage\Page;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
@@ -63,8 +63,6 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
|
||||
$this->aStatusInfo['ignore_excel_sanitization'] = (bool)utils::ReadParam('ignore_excel_sanitization', 0, true, utils::ENUM_SANITIZATION_FILTER_INTEGER);
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
@@ -123,10 +121,6 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oFieldSetDate->AddSubBlock($oRadioCustom);
|
||||
|
||||
$oFieldSetSecurity = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:Security'));
|
||||
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetSecurity));
|
||||
$oFieldSetSecurity->AddSubBlock(ExportHelper::GetInputForSanitizeExcelExport());
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
@@ -222,12 +216,6 @@ EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the option to ignore Excel sanitization is not set or absent, sanitize the field
|
||||
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
|
||||
return ExportHelper::SanitizeField($sRet, '');
|
||||
}
|
||||
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
|
||||
@@ -259,18 +259,13 @@ class InlineImage extends DBObject
|
||||
* that refer to an InlineImage (detected via the attribute data-img-id="") so that
|
||||
* the URL is consistent with the current URL of the application.
|
||||
*
|
||||
N°8681 * @param string|null $sHtml The HTML fragment to process
|
||||
* @param string $sHtml The HTML fragment to process
|
||||
*
|
||||
* @return string The modified HTML
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 3.3.0 N°8681 Add type hint for parameters and return value
|
||||
*/
|
||||
public static function FixUrls(string|null $sHtml): string
|
||||
public static function FixUrls($sHtml)
|
||||
{
|
||||
// N°8681 - Ensure to have a string value
|
||||
$sHtml = $sHtml ?? '';
|
||||
|
||||
$aNeedles = [];
|
||||
$aReplacements = [];
|
||||
// Find img tags with an attribute data-img-id
|
||||
@@ -298,46 +293,6 @@ N°8681 * @param string|null $sHtml The HTML fragment to process
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace <img> tags with a data-img-id attribute by the actual image in base64 representation
|
||||
* so that the image can be displayed even if the download URL is not accessible (e.g. in unauthenticated approval templates)
|
||||
*
|
||||
* @param string $sHtml The HTML fragment to process
|
||||
*
|
||||
* @return String The modified HTML
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static function ReplaceInlineImagesWithBase64Representation(string $sHtml): String
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/<img\s+[^>]*'.static::DOM_ATTR_ID.'="(\d+)"[^>]*>/i',
|
||||
function ($matches) {
|
||||
|
||||
// Extract inline image ID from the tag
|
||||
$id = $matches[1];
|
||||
|
||||
try {
|
||||
// Retrieve inline image
|
||||
$oInline = MetaModel::GetObject(InlineImage::class, $id, true, true);
|
||||
$oOrmDocument = $oInline->Get('contents');
|
||||
|
||||
// Replace src image by the base64 representation
|
||||
$sInlineImageAsBase64 = base64_encode($oOrmDocument->GetData());
|
||||
$sDataUri = 'data:'.$oOrmDocument->GetMimeType().';base64,'.$sInlineImageAsBase64;
|
||||
$sImage = preg_replace('/src=["\'][^"\']+["\']/', 'src="'.$sDataUri.'"', $matches[0]);
|
||||
|
||||
// Remove sensitive information (the image ID and secret) from the tag
|
||||
$sImage = preg_replace('/'.static::DOM_ATTR_ID.'="\d+"\s+'.static::DOM_ATTR_SECRET.'="\w+"/', '', $sImage);
|
||||
} catch (Exception $e) {
|
||||
$sImage = '<img src="" alt="'.Dict::S('UI:MissingInlineImage').'">';
|
||||
}
|
||||
|
||||
return $sImage;
|
||||
},
|
||||
$sHtml
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extra attribute data-img-id for images which are based on an actual InlineImage
|
||||
* so that we can later reconstruct the full "src" URL when needed
|
||||
|
||||
@@ -691,7 +691,7 @@ abstract class LogAPI
|
||||
static::$m_oMockMetaModelConfig = $oMetaModelConfig;
|
||||
}
|
||||
|
||||
public static function Exception(string $sMessage, throwable $oException, ?string $sChannel = null, array $aContext = []): void
|
||||
public static function Exception(string $sMessage, throwable $oException, string $sChannel = null, array $aContext = []): void
|
||||
{
|
||||
$aErrorLogs = [];
|
||||
$aErrorLogs[] = static::PrepareErrorLog($sMessage, $oException, $aContext);
|
||||
|
||||
@@ -22,8 +22,6 @@ use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
|
||||
require_once APPROOT.'core/modulehandler.class.inc.php';
|
||||
require_once APPROOT.'core/querymodifier.class.inc.php';
|
||||
@@ -130,7 +128,7 @@ abstract class MetaModel
|
||||
/** @var array */
|
||||
private static $m_aClassToFile = [];
|
||||
/** @var string */
|
||||
protected static $m_sEnvironment = ITOP_DEFAULT_ENV;
|
||||
protected static $m_sEnvironment = 'production';
|
||||
|
||||
/**
|
||||
* Objects currently created/updated.
|
||||
@@ -464,43 +462,6 @@ abstract class MetaModel
|
||||
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetModuleName($sClass)
|
||||
{
|
||||
try {
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$sDir = realpath(dirname($oReflectionClass->getFileName()));
|
||||
$sApproot = realpath(APPROOT);
|
||||
while (($sDir !== $sApproot) && (str_contains($sDir, $sApproot))) {
|
||||
$aFiles = glob("$sDir/module.*.php");
|
||||
if (count($aFiles) > 1) {
|
||||
return 'core';
|
||||
}
|
||||
|
||||
if (count($aFiles) == 0) {
|
||||
$sDir = realpath(dirname($sDir));
|
||||
continue;
|
||||
}
|
||||
|
||||
$sModuleFilePath = $aFiles[0];
|
||||
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, ) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
return $sModuleName;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new CoreException("Cannot find class module", ['class' => $sClass], '', $e);
|
||||
}
|
||||
|
||||
return 'core';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
@@ -5748,7 +5709,7 @@ abstract class MetaModel
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = ITOP_DEFAULT_ENV)
|
||||
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
|
||||
{
|
||||
// Startup on a new environment is not supported
|
||||
static $bStarted = false;
|
||||
|
||||
@@ -1656,7 +1656,7 @@ class PHP_ParserGenerator_Data
|
||||
function emit_code($out, PHP_ParserGenerator_Rule $rp, &$lineno)
|
||||
{
|
||||
$linecnt = 0;
|
||||
|
||||
|
||||
/* Generate code to do the reduce action */
|
||||
if ($rp->code) {
|
||||
$this->tplt_linedir($out, $rp->line, $this->filename);
|
||||
|
||||
@@ -1084,7 +1084,7 @@ static public $yy_action = array(
|
||||
function yy_find_shift_action($iLookAhead)
|
||||
{
|
||||
$stateno = $this->yystack[$this->yyidx]->stateno;
|
||||
|
||||
|
||||
/* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
|
||||
if (!isset(self::$yy_shift_ofst[$stateno])) {
|
||||
// no shift actions
|
||||
@@ -1767,7 +1767,7 @@ throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine,
|
||||
function yy_syntax_error($yymajor, $TOKEN)
|
||||
{
|
||||
#line 25 "..\oql-parser.y"
|
||||
|
||||
|
||||
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
#line 1779 "..\oql-parser.php"
|
||||
}
|
||||
@@ -1806,7 +1806,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
|
||||
// $yyact; /* The parser action. */
|
||||
// $yyendofinput; /* True if we are at the end of input */
|
||||
$yyerrorhit = 0; /* True if yymajor has invoked an error */
|
||||
|
||||
|
||||
/* (re)initialize the parser, if necessary */
|
||||
if ($this->yyidx === null || $this->yyidx < 0) {
|
||||
/* if ($yymajor == 0) return; // not sure why this was here... */
|
||||
@@ -1819,7 +1819,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
|
||||
array_push($this->yystack, $x);
|
||||
}
|
||||
$yyendofinput = ($yymajor==0);
|
||||
|
||||
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(
|
||||
self::$yyTraceFILE,
|
||||
@@ -1828,7 +1828,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
|
||||
self::$yyTokenName[$yymajor]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
$yyact = $this->yy_find_shift_action($yymajor);
|
||||
if ($yymajor < self::YYERRORSYMBOL
|
||||
@@ -2002,7 +2002,7 @@ class OQLParser extends OQLParserRaw
|
||||
$this->m_sSourceQuery = $sQuery;
|
||||
// no constructor - parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
public function doParse($token, $value, $iCurrPosition = 0)
|
||||
{
|
||||
$this->m_iColPrev = $this->m_iCol;
|
||||
@@ -2016,7 +2016,7 @@ class OQLParser extends OQLParserRaw
|
||||
$this->doParse(0, 0);
|
||||
return $this->my_result;
|
||||
}
|
||||
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
// Bug in the original destructor, causing an infinite loop !
|
||||
|
||||
@@ -370,7 +370,7 @@ class ormCaseLog
|
||||
/**
|
||||
* Produces an HTML representation, aimed at being used within the iTop framework
|
||||
*/
|
||||
public function GetAsHTML(?WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
|
||||
public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
|
||||
{
|
||||
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
|
||||
|
||||
|
||||
@@ -340,33 +340,30 @@ class ormDocument
|
||||
* @param string $sContentDisposition Either 'inline' or 'attachment'
|
||||
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
|
||||
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
|
||||
* @param bool $bAllowAllData If true, no rights filtering is applied
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null, $bAllowAllData = false)
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
|
||||
{
|
||||
try {
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, $bAllowAllData);
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, false);
|
||||
if (!is_object($oObj)) {
|
||||
// If access to the document is not granted, check if the access to the host object is allowed
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, true);
|
||||
$bHasHostRights = false;
|
||||
if ($oObj instanceof Attachment) {
|
||||
$sItemClass = $oObj->Get('item_class');
|
||||
$sItemId = $oObj->Get('item_id');
|
||||
$oHost = MetaModel::GetObject($sItemClass, $sItemId, false, false);
|
||||
if (is_object($oHost)) {
|
||||
$bHasHostRights = true;
|
||||
if (!is_object($oHost)) {
|
||||
$oObj = null;
|
||||
}
|
||||
}
|
||||
|
||||
// We could neither read the object nor get a host object matching our rights
|
||||
if ($bHasHostRights !== true) {
|
||||
if (!is_object($oObj)) {
|
||||
throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
|
||||
}
|
||||
}
|
||||
if (($sSecretField != null) && !hash_equals($oObj->Get($sSecretField), $sSecretValue)) {
|
||||
if (($sSecretField != null) && ($oObj->Get($sSecretField) != $sSecretValue)) {
|
||||
usleep(200);
|
||||
throw new Exception("Invalid secret for class '$sClass' - the object does not exist or you are not allowed to view it");
|
||||
}
|
||||
/** @var \ormDocument $oDocument */
|
||||
|
||||
@@ -93,7 +93,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
* @param DBObjectSet|null $oOriginalSet
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($sHostClass, $sAttCode, ?DBObjectSet $oOriginalSet = null)
|
||||
public function __construct($sHostClass, $sAttCode, DBObjectSet $oOriginalSet = null)
|
||||
{
|
||||
$this->sHostClass = $sHostClass;
|
||||
$this->sAttCode = $sAttCode;
|
||||
@@ -470,6 +470,17 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
|| ($this->oOriginalSet->GetFilter()->ToOQL() == $oFellow->oOriginalSet->GetFilter()->ToOQL())) {
|
||||
$bUpdateFromDelta = true;
|
||||
}
|
||||
} else {
|
||||
//@since 3.2.2 N°2364 - API : remove old linkedset persistance
|
||||
/* Goo pattern to use:
|
||||
* $oCISet = $oTicket->Get(‘functioncis_list’);
|
||||
* $oCISet->AddItem(MetaModel::NewObject(‘lnkFunctionCIToTicket’, array(‘ci_id’=> 12345));
|
||||
* $oCISet->RemoveItem(123456);
|
||||
* $oTicket->Set(‘functionalcis_list’, $oCISet);
|
||||
*/
|
||||
if (!ContextTag::Check(ContextTag::TAG_SETUP)) {
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('old pattern - please get previous value of the linked set, modify it and set it back to the host object');
|
||||
}
|
||||
}
|
||||
|
||||
if ($bUpdateFromDelta) {
|
||||
|
||||
@@ -98,9 +98,9 @@ class ormPassword
|
||||
$bResult = false;
|
||||
$aInfo = password_get_info($this->m_sHashed);
|
||||
if (is_null($aInfo["algo"]) || $aInfo["algo"] === 0) {
|
||||
// - Unknown algorithm, assume it's a legacy password
|
||||
//unknown, assume it's a legacy password
|
||||
$sHashedPwd = $this->ComputeHash($sClearTextPassword);
|
||||
$bResult = hash_equals($this->m_sHashed, $sHashedPwd);
|
||||
$bResult = ($this->m_sHashed == $sHashedPwd);
|
||||
} else {
|
||||
$bResult = password_verify($sClearTextPassword, $this->m_sHashed);
|
||||
}
|
||||
|
||||
@@ -43,24 +43,24 @@ final class ormTagSet extends ormSet
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array|null $aItems
|
||||
* @param array $aTagCodes
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue when a code is invalid
|
||||
*/
|
||||
public function SetValues($aItems)
|
||||
public function SetValues($aTagCodes)
|
||||
{
|
||||
if (is_null($aItems)) {
|
||||
$aItems = [];
|
||||
if (is_null($aTagCodes)) {
|
||||
$aTagCodes = [];
|
||||
}
|
||||
if (!is_array($aItems)) {
|
||||
throw new CoreUnexpectedValue("Wrong value {$aItems} for {$this->sClass}:{$this->sAttCode}");
|
||||
if (!is_array($aTagCodes)) {
|
||||
throw new CoreUnexpectedValue("Wrong value {$aTagCodes} for {$this->sClass}:{$this->sAttCode}");
|
||||
}
|
||||
|
||||
$oTags = [];
|
||||
$iCount = 0;
|
||||
$bError = false;
|
||||
foreach ($aItems as $sTagCode) {
|
||||
foreach ($aTagCodes as $sTagCode) {
|
||||
$iCount++;
|
||||
if (($this->iLimit != 0) && ($iCount > $this->iLimit)) {
|
||||
$bError = true;
|
||||
|
||||
@@ -248,6 +248,45 @@ class RestResultWithObjects extends RestResult
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package RESTAPI
|
||||
* @api
|
||||
*/
|
||||
class RestResultWithObjectSets extends RestResultWithObjects
|
||||
{
|
||||
private $current_object = null;
|
||||
|
||||
public function MakeNewObjectSet()
|
||||
{
|
||||
$arr = array();
|
||||
$this->current_object = &$arr;
|
||||
$this->objects[] = &$arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the given object
|
||||
*
|
||||
* @api
|
||||
* @param string $sObjectAlias Name of the subobject, usually the OQL class alias
|
||||
* @param int $iCode An error code (RestResult::OK is no issue has been found)
|
||||
* @param string $sMessage Description of the error if any, an empty string otherwise
|
||||
* @param DBObject $oObject The object being reported
|
||||
* @param array|null $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported.
|
||||
* @param boolean $bExtendedOutput Output all of the link set attributes ?
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function AppendSubObject($sObjectAlias, $iCode, $sMessage, $oObject, $aFieldSpec = null, $bExtendedOutput = false)
|
||||
{
|
||||
$oObjRes = ObjectResult::FromDBObject($oObject, $aFieldSpec, $bExtendedOutput, $iCode, $sMessage);
|
||||
$this->current_object[$sObjectAlias] = $oObjRes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package RESTAPI
|
||||
* @api
|
||||
@@ -500,15 +539,22 @@ class CoreServices implements iRestServiceProvider, iRestInputSanitizer
|
||||
break;
|
||||
|
||||
case 'core/get':
|
||||
$sClass = RestUtils::GetClass($aParams, 'class');
|
||||
$sClassParam = RestUtils::GetMandatoryParam($aParams, 'class');
|
||||
$key = RestUtils::GetMandatoryParam($aParams, 'key');
|
||||
$aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
|
||||
$bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
|
||||
$sShowFields = RestUtils::GetOptionalParam($aParams, 'output_fields', '*');
|
||||
$iLimit = (int)RestUtils::GetOptionalParam($aParams, 'limit', 0);
|
||||
$iPage = (int)RestUtils::GetOptionalParam($aParams, 'page', 1);
|
||||
|
||||
$oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key, $iLimit, self::getOffsetFromLimitAndPage($iLimit, $iPage));
|
||||
$sTargetClass = $oObjectSet->GetFilter()->GetClass();
|
||||
// Validate the class(es)
|
||||
$aClass = explode(',', $sClassParam);
|
||||
foreach ($aClass as $sClass) {
|
||||
if (!MetaModel::IsValidClass(trim($sClass))) {
|
||||
throw new Exception("class '$sClass' is not valid");
|
||||
}
|
||||
}
|
||||
|
||||
$oObjectSet = RestUtils::GetObjectSetFromKey($sClassParam, $key, $iLimit, self::getOffsetFromLimitAndPage($iLimit, $iPage));
|
||||
$sTargetClass = $oObjectSet->GetFilter()->GetClass();
|
||||
|
||||
if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ) != UR_ALLOWED_YES) {
|
||||
$oResult->code = RestResult::UNAUTHORIZED;
|
||||
@@ -519,19 +565,67 @@ class CoreServices implements iRestServiceProvider, iRestInputSanitizer
|
||||
} elseif ($iPage < 1) {
|
||||
$oResult->code = RestResult::INVALID_PAGE;
|
||||
$oResult->message = "The request page number is not valid. It must be an integer greater than 0";
|
||||
} else {
|
||||
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*') {
|
||||
$aFields = $aShowFields[$sClass];
|
||||
//Id is not a valid attribute to optimize
|
||||
if (in_array('id', $aFields)) {
|
||||
unset($aFields[array_search('id', $aFields)]);
|
||||
}
|
||||
$aAttToLoad = [$oObjectSet->GetClassAlias() => $aFields];
|
||||
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
|
||||
} elseif (count($oObjectSet->GetSelectedClasses()) > 1) {
|
||||
$oResult = new RestResultWithObjectSets();
|
||||
$aCache = [];
|
||||
$aShowFields = [];
|
||||
foreach ($oObjectSet->GetSelectedClasses() as $sSelectedClass) {
|
||||
$aShowFields = array_merge( $aShowFields, RestUtils::GetFieldList($sSelectedClass, $aParams, 'output_fields', false));
|
||||
}
|
||||
|
||||
while ($oObjects = $oObjectSet->FetchAssoc()) {
|
||||
$oResult->MakeNewObjectSet();
|
||||
|
||||
foreach ($oObjects as $sAlias => $oObject) {
|
||||
if (!$oObject) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($sAlias, $aCache)) {
|
||||
$sClass = get_class($oObject);
|
||||
$bExtendedOutput = RestUtils::HasRequestedExtendedOutput($sShowFields);
|
||||
|
||||
if (!RestUtils::HasRequestedAllOutputFields($sShowFields)) {
|
||||
$aFields = $aShowFields[$sClass];
|
||||
//Id is not a valid attribute to optimize
|
||||
if ($aFields && in_array('id', $aFields)) {
|
||||
unset($aFields[array_search('id', $aFields)]);
|
||||
}
|
||||
$aAttToLoad = [$sAlias => $aFields];
|
||||
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
|
||||
}
|
||||
$aCache[$sAlias] = [
|
||||
'aShowFields' => $aShowFields,
|
||||
'bExtendedOutput' => $bExtendedOutput,
|
||||
];
|
||||
} else {
|
||||
$aShowFields = $aCache[$sAlias]['aShowFields'];
|
||||
$bExtendedOutput = $aCache[$sAlias]['bExtendedOutput'];
|
||||
}
|
||||
|
||||
$oResult->AppendSubObject($sAlias, 0, '', $oObject, $aShowFields, $bExtendedOutput);
|
||||
}
|
||||
}
|
||||
$oResult->message = "Found: ".$oObjectSet->Count();
|
||||
} else {
|
||||
$aShowFields =[];
|
||||
foreach ($aClass as $sSelectedClass) {
|
||||
$sSelectedClass = trim($sSelectedClass);
|
||||
$aShowFields = array_merge($aShowFields, RestUtils::GetFieldList($sSelectedClass, $aParams, 'output_fields', false));
|
||||
}
|
||||
|
||||
if (!RestUtils::HasRequestedAllOutputFields($sShowFields) && count($aShowFields) == 1) {
|
||||
$aFields = $aShowFields[$sClass];
|
||||
//Id is not a valid attribute to optimize
|
||||
if (in_array('id', $aFields)) {
|
||||
unset($aFields[array_search('id', $aFields)]);
|
||||
}
|
||||
$aAttToLoad = [$oObjectSet->GetClassAlias() => $aFields];
|
||||
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
|
||||
}
|
||||
|
||||
while ($oObject = $oObjectSet->Fetch()) {
|
||||
$oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);
|
||||
$oResult->AddObject(0, '', $oObject, $aShowFields, RestUtils::HasRequestedExtendedOutput($sShowFields));
|
||||
}
|
||||
$oResult->message = "Found: ".$oObjectSet->Count();
|
||||
}
|
||||
|
||||
@@ -658,16 +658,6 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow a user to be impersonated by another one (generally an administrator) in order to troubleshoot rights issues.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function CanBeImpersonated(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1073,9 +1063,6 @@ class UserRights
|
||||
$oUser = self::FindUser($sLogin);
|
||||
if ($oUser) {
|
||||
$bRet = true;
|
||||
if (!$oUser->CanBeImpersonated()) {
|
||||
throw new Exception($oUser->GetName().' cannot be impersonated');
|
||||
}
|
||||
if (is_null(self::$m_oRealUser)) {
|
||||
// First impersonation
|
||||
self::$m_oRealUser = self::$m_oUser;
|
||||
@@ -1195,7 +1182,7 @@ class UserRights
|
||||
return self::$m_oUser->GetKey();
|
||||
} else {
|
||||
// find the id out of the login string
|
||||
$oUser = self::FindUser($sLogin, bAllowDisabledUsers: true);
|
||||
$oUser = self::FindUser($sLogin);
|
||||
if (is_null($oUser)) {
|
||||
return null;
|
||||
}
|
||||
@@ -1388,7 +1375,7 @@ class UserRights
|
||||
if (empty($sLogin)) {
|
||||
$oUser = self::$m_oUser;
|
||||
} else {
|
||||
$oUser = self::FindUser($sLogin, bAllowDisabledUsers: true);
|
||||
$oUser = self::FindUser($sLogin);
|
||||
}
|
||||
if (is_null($oUser)) {
|
||||
return '';
|
||||
|
||||
@@ -56,8 +56,6 @@ $ibo-shame--slider--is-round--border-radius: 20px !default;
|
||||
$ibo-shame--slider--is-round--before--border-radius: 7px !default;
|
||||
|
||||
|
||||
$ibo-blockquote--color: $ibo-body-text-color !default;
|
||||
|
||||
// N°2847 - Recolor svg illustrations with iTop's primary color
|
||||
.ibo-svg-illustration--container > svg *[fill="#6c63ff"]{
|
||||
fill: $ibo-svg-illustration--fill;
|
||||
@@ -128,11 +126,3 @@ input:checked + .slider:before {
|
||||
.slider.round:before {
|
||||
border-radius: $ibo-shame--slider--is-round--before--border-radius;
|
||||
}
|
||||
|
||||
/*
|
||||
Bulma sets blockquote background color through a variable, it affects ckeditor and html display.
|
||||
This rule is needed harmonize the blockquote text color in both contexts.
|
||||
*/
|
||||
.ibo-is-html-content blockquote {
|
||||
color: $ibo-blockquote--color;
|
||||
}
|
||||
@@ -4,4 +4,3 @@
|
||||
*/
|
||||
|
||||
@import "bulk-modify";
|
||||
@import "bulk-export";
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
#form_part_csv_options:has(#ibo-sanitize-excel-export--input:checked), #form_part_xlsx_options:has(#ibo-sanitize-excel-export--input:checked){
|
||||
#ibo-sanitize-excel-export--alert {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,4 @@
|
||||
@import "medallion-with-blocklist";
|
||||
@import "field-badge-within-datatable";
|
||||
@import "jquery-blockui-within-dialog";
|
||||
@import "jquery-blockui-within-datatable";
|
||||
@import "badge-with-badge";
|
||||
@import "extension-details-with-extension-details";
|
||||
@import "jquery-blockui-within-datatable";
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-badge--spacing-left--with-same-block: $ibo-spacing-200 !default;
|
||||
|
||||
.ibo-badge + .ibo-badge {
|
||||
margin-left: $ibo-badge--spacing-left--with-same-block;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-extension-details--margin-top: $ibo-spacing-300 !default;
|
||||
|
||||
.ibo-extension-details + .ibo-extension-details,
|
||||
.ibo-extension-details--information--description .ibo-extension-details {
|
||||
margin-top: $ibo-extension-details--margin-top;
|
||||
}
|
||||
@@ -33,5 +33,4 @@
|
||||
@import "field-badge";
|
||||
@import "file-select";
|
||||
@import "medallion-icon";
|
||||
@import "toast";
|
||||
@import "badge";
|
||||
@import "toast";
|
||||
@@ -1,41 +0,0 @@
|
||||
$ibo-badge--padding-x : $ibo-spacing-200 !default;
|
||||
$ibo-badge--padding-y : $ibo-spacing-100 !default;
|
||||
$ibo-badge--border-radius : $ibo-border-radius-400 !default;
|
||||
|
||||
$ibo-badge-colors: (
|
||||
'primary': ($ibo-color-primary-100, $ibo-color-primary-900),
|
||||
'secondary': ($ibo-color-secondary-100, $ibo-color-secondary-900),
|
||||
'neutral': ($ibo-color-secondary-100, $ibo-color-secondary-900),
|
||||
'information': ($ibo-color-information-100, $ibo-color-information-900),
|
||||
'success': ($ibo-color-success-100, $ibo-color-success-900),
|
||||
'failure': ($ibo-color-danger-100, $ibo-color-danger-900),
|
||||
'warning': ($ibo-color-warning-100,$ibo-color-warning-900),
|
||||
'danger': ($ibo-color-danger-100,$ibo-color-danger-900),
|
||||
'grey' : ($ibo-color-grey-100, $ibo-color-grey-900),
|
||||
'blue-grey': ($ibo-color-blue-grey-100, $ibo-color-blue-grey-900),
|
||||
'blue': ($ibo-color-blue-100, $ibo-color-blue-900),
|
||||
'cyan': ($ibo-color-cyan-100, $ibo-color-cyan-900),
|
||||
'green': ($ibo-color-green-100, $ibo-color-green-900),
|
||||
'orange' : ($ibo-color-orange-100, $ibo-color-orange-900),
|
||||
'red': ($ibo-color-red-100, $ibo-color-red-900),
|
||||
'pink': ($ibo-color-pink-100, $ibo-color-pink-900),
|
||||
) !default;
|
||||
|
||||
|
||||
|
||||
.ibo-badge {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
padding : $ibo-badge--padding-y $ibo-badge--padding-x;
|
||||
border-radius : $ibo-badge--border-radius;
|
||||
@extend %ibo-font-ral-med-50;
|
||||
|
||||
@each $sColor, $aColorValues in $ibo-badge-colors {
|
||||
$bg-color: nth($aColorValues, 1);
|
||||
$text-color: nth($aColorValues, 2);
|
||||
&.ibo-is-#{$sColor} {
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,12 +72,9 @@ $ibo-panel--icon--spacing--as-medallion--is-sticking: $ibo-panel--icon--spacing-
|
||||
$ibo-panel--icon--bottom--as-medallion--is-sticking: -12px !default;
|
||||
$ibo-panel--icon--border--as-medallion--is-sticking: 1px $ibo-panel--base-border-style $ibo-panel--base-border-color !default;
|
||||
|
||||
$ibo-panel--icon-background--size--must-contain: contain !default; // deprecated, to be removed in favor of $ibo-panel--icon-img--size--must-contain
|
||||
$ibo-panel--icon-background--size--must-cover: cover !default; // deprecated, to be removed in favor of $ibo-panel--icon-img--size--must-cover
|
||||
$ibo-panel--icon-background--size--must-zoomout: 66.67% !default; // deprecated, to be removed in favor of $ibo-panel--icon-img--size--must-zoomout
|
||||
$ibo-panel--icon-img--size--must-contain: $ibo-panel--icon-background--size--must-contain !default; // TODO remove when dealing with N°9317
|
||||
$ibo-panel--icon-img--size--must-cover: $ibo-panel--icon-background--size--must-cover !default; // TODO remove when dealing with N°9317
|
||||
$ibo-panel--icon-img--size--must-zoomout: $ibo-panel--icon-background--size--must-zoomout !default; // TODO remove when dealing with N°9317
|
||||
$ibo-panel--icon-background--size--must-contain: contain !default;
|
||||
$ibo-panel--icon-background--size--must-cover: cover !default;
|
||||
$ibo-panel--icon-background--size--must-zoomout: 66.67% !default;
|
||||
|
||||
$ibo-panel--title--font-size--is-sticking: $ibo-font-size-150 !default;
|
||||
$ibo-panel--title--color: $ibo-color-grey-900 !default;
|
||||
@@ -182,29 +179,24 @@ $ibo-panel--is-selectable--body--after--font-size: $ibo-font-size-700 !default;
|
||||
min-height: $ibo-panel--icon--size;
|
||||
}
|
||||
|
||||
.ibo-panel--icon-img, .ibo-panel--icon-background { // second class is deprecated, remove it when dealing with N°9317
|
||||
.ibo-panel--icon-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-position: center;
|
||||
object-fit: $ibo-panel--icon-img--size--must-contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: $ibo-panel--icon-img--size--must-contain;
|
||||
background-size: $ibo-panel--icon-background--size--must-contain;
|
||||
}
|
||||
|
||||
.ibo-panel--icon-img--must-contain, .ibo-panel--icon-background--must-contain { // second class is deprecated, remove it when dealing with N°9317
|
||||
object-fit: $ibo-panel--icon-img--size--must-contain;
|
||||
background-size: $ibo-panel--icon-img--size--must-contain;
|
||||
.ibo-panel--icon-background--must-contain {
|
||||
background-size: $ibo-panel--icon-background--size--must-contain;
|
||||
}
|
||||
|
||||
.ibo-panel--icon-img--must-cover, .ibo-panel--icon-background--must-cover { // second class is deprecated, remove it when dealing with N°9317
|
||||
object-fit: $ibo-panel--icon-img--size--must-cover;
|
||||
background-size: $ibo-panel--icon-img--size--must-cover;
|
||||
.ibo-panel--icon-background--must-cover {
|
||||
background-size: $ibo-panel--icon-background--size--must-cover;
|
||||
}
|
||||
|
||||
.ibo-panel--icon-img--must-zoomout, .ibo-panel--icon-background--must-zoomout { // second class is deprecated, remove it when dealing with N°9317
|
||||
width: $ibo-panel--icon-img--size--must-zoomout;
|
||||
height: $ibo-panel--icon-img--size--must-zoomout;
|
||||
.ibo-panel--icon-background--must-zoomout {
|
||||
background-size: $ibo-panel--icon-background--size--must-zoomout;
|
||||
}
|
||||
|
||||
.ibo-panel--title {
|
||||
|
||||
@@ -247,8 +247,7 @@ $ibo-quick-create--compartment--placeholder-hint--text-color: $ibo-color-grey-70
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: $ibo-hyperlink-color--on-hover;
|
||||
text-decoration: $ibo-hyperlink-text-decoration--on-hover;
|
||||
@extend a;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
|
||||
@@ -21,8 +21,6 @@ $ibo-search-form-panel--more-criteria--color: $ibo-color-blue-grey-800 !default;
|
||||
$ibo-search-form-panel--more-criteria--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-search-form-panel--more-criteria--icon--color: $ibo-color-primary-600 !default;
|
||||
$ibo-search-form-panel--more-criteria--border-color: $ibo-search-form-panel--criteria--border-color !default;
|
||||
// calc is redundant but avoid SCSS min() from being used instead of CSS min()
|
||||
$ibo-search-form-panel--criteria--max-height: calc(min(#{$ibo-size-750}, 50vh)) !default;
|
||||
|
||||
$ibo-search-form-panel--items--hover--color: $ibo-color-grey-200 !default;
|
||||
|
||||
@@ -280,10 +278,9 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
}
|
||||
|
||||
.sfc_form_group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: -1px;
|
||||
z-index: -1;
|
||||
display: block;
|
||||
margin-top: -1px;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,15 +346,11 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
display: none;
|
||||
max-width: 450px;
|
||||
width: max-content;
|
||||
max-height: $ibo-search-form-panel--criteria--max-height;
|
||||
max-height: 520px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
.sfc_fg_operators {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
font-size: 12px;
|
||||
|
||||
.sfc_fg_operator {
|
||||
@@ -394,9 +387,6 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
}
|
||||
|
||||
.sfc_opc_multichoices {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
label > input {
|
||||
vertical-align: text-top;
|
||||
margin-left: $ibo-spacing-0;
|
||||
@@ -408,6 +398,7 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
}
|
||||
|
||||
.sfc_opc_mc_items_wrapper {
|
||||
max-height: 415px; /* Must be less than .sfc_form_group:max-height - .sfc_opc_mc_toggler:height - .sfc_opc_mc_filter:height */
|
||||
overflow-y: auto;
|
||||
margin: $ibo-spacing-0 -8px; /* Compensate .sfc_opc_multichoices side padding so the hover style can take the full with */
|
||||
|
||||
@@ -569,14 +560,8 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
&.search_form_criteria_enum {
|
||||
.sfc_form_group {
|
||||
.sfc_fg_operator_in {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
> label {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
line-height: initial;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -11,12 +11,9 @@ $ibo-title--icon--size: 90px !default;
|
||||
$ibo-title--icon--size-2: 80px !default;
|
||||
$ibo-title--icon--size-3: 70px !default;
|
||||
|
||||
$ibo-title--icon-background--size--must-contain: contain !default; // deprecated, to be removed in favor of $ibo-title--icon-img--size--must-contain
|
||||
$ibo-title--icon-background--size--must-cover: cover !default; // deprecated, to be removed in favor of $ibo-title--icon-img--size--must-cover
|
||||
$ibo-title--icon-background--size--must-zoomout: 66.67% !default; // deprecated, to be removed in favor of $ibo-title--icon-img--size--must-zoomout
|
||||
$ibo-title--icon-img--size--must-contain: $ibo-title--icon-background--size--must-contain !default; // TODO remove when dealing with N°9317
|
||||
$ibo-title--icon-img--size--must-cover: $ibo-title--icon-background--size--must-cover !default; // TODO remove when dealing with N°9317
|
||||
$ibo-title--icon-img--size--must-zoomout: $ibo-title--icon-background--size--must-zoomout !default; // TODO remove when dealing with N°9317
|
||||
$ibo-title--icon-background--size--must-contain: contain !default;
|
||||
$ibo-title--icon-background--size--must-cover: cover !default;
|
||||
$ibo-title--icon-background--size--must-zoomout: 66.67% !default;
|
||||
|
||||
|
||||
.ibo-title {
|
||||
@@ -47,27 +44,24 @@ $ibo-title--icon-img--size--must-zoomout: $ibo-title--icon-background--size--mus
|
||||
min-height: $ibo-title--icon--size-3;
|
||||
}
|
||||
|
||||
.ibo-title--icon-img, .ibo-title--icon-background { // second class is deprecated, remove it when dealing with N°9317
|
||||
.ibo-title--icon-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-position: center;
|
||||
object-fit: $ibo-title--icon-img--size--must-contain;
|
||||
background-size: $ibo-title--icon-img--size--must-contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: $ibo-title--icon-background--size--must-contain;
|
||||
}
|
||||
|
||||
.ibo-title--icon-img--must-contain, .ibo-title--icon-background--must-contain { // second class is deprecated, remove it when dealing with N°9317
|
||||
object-fit: $ibo-title--icon-img--size--must-contain;
|
||||
background-size: $ibo-title--icon-img--size--must-contain;
|
||||
.ibo-title--icon-background--must-contain {
|
||||
background-size: $ibo-title--icon-background--size--must-contain;
|
||||
}
|
||||
|
||||
.ibo-title--icon-img--must-cover, .ibo-title--icon-background--must-cover { // second class is deprecated, remove it when dealing with N°9317
|
||||
object-fit: $ibo-title--icon-img--size--must-cover;
|
||||
background-size: $ibo-title--icon-img--size--must-cover;
|
||||
.ibo-title--icon-background--must-cover {
|
||||
background-size: $ibo-title--icon-background--size--must-cover;
|
||||
}
|
||||
|
||||
.ibo-title--icon-img--must-zoomout, .ibo-title--icon-background--must-zoomout { // second class is deprecated, remove it when dealing with N°9317
|
||||
width: $ibo-title--icon-img--size--must-zoomout;
|
||||
height: $ibo-title--icon-img--size--must-zoomout;
|
||||
.ibo-title--icon-background--must-zoomout {
|
||||
background-size: $ibo-title--icon-background--size--must-zoomout;
|
||||
}
|
||||
|
||||
.ibo-title--for-object-details {
|
||||
|
||||
@@ -203,9 +203,8 @@ $ibo-input-select--autocomplete-item-image--border: 1px solid $ibo-color-grey-60
|
||||
}
|
||||
|
||||
// N°7982 Default selectize stylesheet override
|
||||
// N°9468 Dropdown content needs to be a few pixel shorter than the dropdown itself to avoid double scrollbar
|
||||
.selectize-dropdown-content{
|
||||
max-height: calc(#{$ibo-input-select-selectize--dropdown--max-height} - 4px);
|
||||
max-height: $ibo-input-select-selectize--dropdown--max-height;
|
||||
}
|
||||
|
||||
.selectize-dropdown.ui-menu .ui-state-active {
|
||||
|
||||
@@ -8,7 +8,6 @@ $ibo-toggler--wrapper--height: 20px !default;
|
||||
|
||||
$ibo-toggler--slider--border-radius: $ibo-border-radius-900 !default;
|
||||
$ibo-toggler--slider--background-color: $ibo-color-secondary-600 !default;
|
||||
$ibo-toggler--slider--disabled--background-color: $ibo-color-secondary-200 !default;
|
||||
|
||||
$ibo-toggler--slider--before--left: 3px !default;
|
||||
$ibo-toggler--slider--before--bottom: 3px !default;
|
||||
@@ -18,7 +17,6 @@ $ibo-toggler--slider--before--border-radius: $ibo-border-radius-full !default;
|
||||
$ibo-toggler--slider--before--background-color: $ibo-color-grey-100 !default;
|
||||
|
||||
$ibo-toggler--slider--checked--background-color: $ibo-color-primary-600 !default;
|
||||
$ibo-toggler--slider--checked-disabled--background-color: $ibo-color-primary-200 !default;
|
||||
$ibo-toggler--slider--focus--box-shadow: 0 0 1px $ibo-color-primary-600 !default;
|
||||
|
||||
$ibo-toggler--label--margin-left: 4px !default;
|
||||
@@ -63,13 +61,6 @@ $ibo-toggler--label--margin-left: 4px !default;
|
||||
background-color: $ibo-toggler--slider--checked--background-color;
|
||||
}
|
||||
|
||||
.ibo-toggler--wrapper input:disabled + .ibo-toggler--slider {
|
||||
background-color: $ibo-toggler--slider--disabled--background-color;
|
||||
}
|
||||
.ibo-toggler--wrapper input:checked:disabled + .ibo-toggler--slider {
|
||||
background-color: $ibo-toggler--slider--checked-disabled--background-color;
|
||||
}
|
||||
|
||||
input:focus + .ibo-toggler--slider {
|
||||
box-shadow: $ibo-toggler--slider--focus--box-shadow;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,3 @@
|
||||
@import "wizard-container/wizard-container";
|
||||
@import "object/all";
|
||||
@import "activity-panel/all";
|
||||
@import "extension/all";
|
||||
@@ -1 +0,0 @@
|
||||
@import "extension-details";
|
||||
@@ -1,76 +0,0 @@
|
||||
$ibo-extension-details--information--metadata--padding: $ibo-spacing-200 !default;
|
||||
$ibo-extension-details--information--metadata--delimiter: "-" !default;
|
||||
$ibo-extension-details--information--metadata--color: $ibo-color-grey-700 !default;
|
||||
$ibo-extension-details--actions--button--padding-y: 3px !default;
|
||||
$ibo-extension-details--actions--button--padding-x: $ibo-button--padding-x !default;
|
||||
|
||||
.ibo-extension-details {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ibo-extension-details--actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--label {
|
||||
@extend %ibo-font-ral-med-150;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--metadata {
|
||||
@extend %ibo-font-ral-med-100;
|
||||
color: $ibo-extension-details--information--metadata--color;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--description {
|
||||
@extend %ibo-font-ral-med-100;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--metadata span + span:before {
|
||||
content: $ibo-extension-details--information--metadata--delimiter;
|
||||
padding-left: $ibo-extension-details--information--metadata--padding;
|
||||
padding-right: $ibo-extension-details--information--metadata--padding;
|
||||
}
|
||||
|
||||
//ibo-extension-details can have other ibo-extension-details inside its ibo-extension-details--information--description in the setup. We need to only affect direct children
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions input:is([type="checkbox"], [type="radio"]):checked){
|
||||
&>.ibo-extension-details--information>.ibo-extension-details--information--label .ibo-badge.unchecked {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
//Merging the two lines below with :is([type="checkbox"], [type="radio"]) will generate a warning in scss compiler
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions input[type="checkbox"]:not(:checked)),
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions input[type="radio"]:not(:checked)) {
|
||||
&>.ibo-extension-details--information>.ibo-extension-details--information--label .ibo-badge.checked {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ibo-extension-details--actions > button {
|
||||
padding: $ibo-extension-details--actions--button--padding-y $ibo-extension-details--actions--button--padding-x;
|
||||
}
|
||||
|
||||
.ibo-extension-details--actions:has(.toggler-install:not(:disabled)) .ibo-popover-menu--section a[data-resource-id="force_uninstall"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ibo-extension-details .ibo-popover-menu ~ .ibo-button{
|
||||
visibility: hidden;
|
||||
}
|
||||
.ibo-extension-details .ibo-popover-menu:has(.ibo-popover-menu--item) ~ .ibo-button{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.ibo-extension-details .ibo-toggler--wrapper:has(.ibo-toggler.ibo-is-hidden){
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -3,29 +3,9 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-datamodel-viewer--viewer-empty--width: max(20%, #{$ibo-size-700}) !default;
|
||||
|
||||
$ibo-datamodel-viewer--parent--spacer--padding-y: $ibo-spacing-0 !default;
|
||||
$ibo-datamodel-viewer--parent--spacer--padding-x: $ibo-spacing-300 !default;
|
||||
|
||||
$ibo-datamodel-viewer--breadcrumb--margin: $ibo-spacing-200 0 !default;
|
||||
|
||||
$ibo-datamodel-viewer--classname--font-family: monospace !default;
|
||||
$ibo-datamodel-viewer--classname--color: $ibo-color-blue-grey-600 !default;
|
||||
|
||||
$ibo-datamodel-viewer--tag--category--color: $ibo-color-blue-grey-800 !default;
|
||||
$ibo-datamodel-viewer--tag--category--icon--color: $ibo-color-blue-grey-600 !default;
|
||||
|
||||
$ibo-datamodel-viewer--abstract-class-icon--margin-right: $ibo-spacing-300 !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--content: 'A' !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--width: 15px !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--height: 15px !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--line-height: 14px !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--border-radius: 50% !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--border: 1px solid $ibo-color-cyan-500 !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--color: $ibo-color-white-100 !default;
|
||||
$ibo-datamodel-viewer--abstract-class-icon--after--background-color: $ibo-color-cyan-500 !default;
|
||||
|
||||
$ibo-datamodel-viewer--classes-list--selectize-input--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-datamodel-viewer--classes-list--selectize-input--color: $ibo-color-grey-800 !default;
|
||||
$ibo-datamodel-viewer--classes-list--selectize-input--border-color: $ibo-color-grey-500 !default;
|
||||
@@ -36,9 +16,8 @@ $ibo-datamodel-viewer--origin-cell--diameter: 8px !default;
|
||||
$ibo-datamodel-viewer--origin-cell--border-radius: $ibo-border-radius-full !default;
|
||||
|
||||
$ibo-datamodel-viewer--classes-list--height: 100% !default;
|
||||
$ibo-datamodel-viewer--classes-list--width: $ibo-size-700 !default;
|
||||
$ibo-datamodel-viewer--classes-list--width: 350px !default;
|
||||
$ibo-datamodel-viewer--classes-list--padding-left: $ibo-spacing-600 !default;
|
||||
$ibo-datamodel-viewer--classes-list--padding-top: $ibo-spacing-300 !default;
|
||||
|
||||
$ibo-datamodel-viewer--lifecycle--code--color: $ibo-color-grey-700 !default;
|
||||
$ibo-datamodel-viewer--lifecycle--stimuli--color: $ibo-color-blue-900 !default;
|
||||
@@ -66,56 +45,6 @@ $ibo-datamodel-viewer--lifecycle-image--margin-bottom: $ibo-spacing-500 !default
|
||||
.ibo-panel--subtitle{
|
||||
@extend %ibo-font-ral-nor-150;
|
||||
}
|
||||
.ibo-datamodel-viewer--empty{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
svg{
|
||||
width: $ibo-datamodel-viewer--viewer-empty--width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-datamodel-viewer--empty--text {
|
||||
@extend %ibo-font-ral-med-400
|
||||
}
|
||||
|
||||
.ibo-datamodel-viewer--breadcrumb{
|
||||
@extend %ibo-font-ral-med-150;
|
||||
margin: $ibo-datamodel-viewer--breadcrumb--margin;
|
||||
.ibo-button{
|
||||
text-transform: none; // unset uppercase
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-datamodel-viewer--classname{
|
||||
color: $ibo-datamodel-viewer--classname--color;
|
||||
@extend %ibo-font-code-100;
|
||||
}
|
||||
|
||||
.ibo-datamodel-viewer--tag--category{
|
||||
color: $ibo-datamodel-viewer--tag--category--color;
|
||||
.ibo-object-details--tag-icon{
|
||||
color:$ibo-datamodel-viewer--tag--category--icon--color;
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-datamodel-viewer--icon--abstract{
|
||||
&:after{
|
||||
content: $ibo-datamodel-viewer--abstract-class-icon--after--content;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: $ibo-datamodel-viewer--abstract-class-icon--after--width;
|
||||
height: $ibo-datamodel-viewer--abstract-class-icon--after--height;
|
||||
line-height: $ibo-datamodel-viewer--abstract-class-icon--after--line-height;
|
||||
border-radius: $ibo-datamodel-viewer--abstract-class-icon--after--border-radius;
|
||||
border: $ibo-datamodel-viewer--abstract-class-icon--after--border;
|
||||
color: $ibo-datamodel-viewer--abstract-class-icon--after--color;
|
||||
background-color: $ibo-datamodel-viewer--abstract-class-icon--after--background-color;
|
||||
|
||||
@extend %ibo-font-ral-bol-50;
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-datamodel-viewer--parent--spacer{
|
||||
@@ -151,7 +80,6 @@ $ibo-datamodel-viewer--lifecycle-image--margin-bottom: $ibo-spacing-500 !default
|
||||
height: $ibo-datamodel-viewer--classes-list--height;
|
||||
width: $ibo-datamodel-viewer--classes-list--width;
|
||||
padding-left: $ibo-datamodel-viewer--classes-list--padding-left;
|
||||
padding-top: $ibo-datamodel-viewer--classes-list--padding-top;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ $text-strong: inherit !default;
|
||||
* See https://bulma.io/documentation/elements/content/
|
||||
*/
|
||||
$content-block-margin-bottom: 0 !default;
|
||||
$content-blockquote-background-color: $ibo-color-grey-200 !default;
|
||||
|
||||
/* Table: Reset style as much as possible to match rich text editor preview, which is the browser's default stylesheet.
|
||||
* As there is no way to avoid bulma rules, we simply make them invalid by setting an invalid variable value, the rules will then be ignored by the browser.
|
||||
|
||||
2
css/backoffice/vendors/_selectize.scss
vendored
2
css/backoffice/vendors/_selectize.scss
vendored
@@ -29,7 +29,7 @@ $ibo-vendors-selectize--element--active--background: $ibo-color-blue-100 !defaul
|
||||
$ibo-vendors-selectize--element--active--color: $ibo-color-grey-900 !default;
|
||||
|
||||
$ibo-vendors-selectize--dropdown--background-color: $ibo-vendors-selectize-input--background-color !default;
|
||||
$ibo-vendors-selectize--dropdown--color: $ibo-vendors-selectize-input--color !default;
|
||||
$ibo-vendors-selectize--dropdown--color: $ibo-vendors-selectize-input--color!default;
|
||||
|
||||
$ibo-vendors-selectize--header--padding-x: 8px !default;
|
||||
$ibo-vendors-selectize--header--padding-y: 5px !default;
|
||||
|
||||
@@ -28,15 +28,6 @@ $ibo-vendors-ckeditor--ck-mentions--item--padding-x: $ibo-spacing-300 !default;
|
||||
$ibo-vendors-ckeditor--ck-mentions--item--padding-y: $ibo-spacing-200 !default;
|
||||
|
||||
/* CSS3 variables */
|
||||
:root {
|
||||
--ck-content-font-family: inherit;
|
||||
--ck-content-font-color: inherit;
|
||||
--ck-content-font-size: inherit;
|
||||
--ck-content-line-height: inherit;
|
||||
--ck-content-word-break: inherit;
|
||||
--ck-content-overflow-wrap: inherit;
|
||||
}
|
||||
|
||||
.ck {
|
||||
--ck-color-list-button-on-background: #{$ibo-vendors-ckeditor--ck-color-list-button-on-background};
|
||||
--ck-color-list-button-on-background-focus: #{$ibo-vendors-ckeditor--ck-color-list-button-on-background-focus};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -316,34 +316,29 @@ fieldset {
|
||||
background-color: #F7FAFC;
|
||||
padding: 10px;
|
||||
.wiz-choice{
|
||||
&:not(:checked) ~ label .checked{
|
||||
&:checked ~ .description {
|
||||
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
|
||||
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
|
||||
~ .description::after {
|
||||
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: $legacy-portal-removal-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(:checked) ~ label .setup-extension-tag.checked{
|
||||
display:none;
|
||||
}
|
||||
&:checked ~ label .unchecked{
|
||||
&:checked ~ label .setup-extension-tag.unchecked{
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions>input:checked) {
|
||||
.ibo-extension-details:has(#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked), #itop-ticket-mgmt-itil-enhanced-portal:not(:checked)) {
|
||||
.ibo-extension-details--information--description::after {
|
||||
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: $legacy-portal-removal-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--metadata{
|
||||
color: $ibo-color-grey-800;
|
||||
}
|
||||
|
||||
.choice-disabled {
|
||||
color: $ibo-color-grey-700;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 1.17rem;
|
||||
@@ -527,12 +522,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-setup-summary-title, .ibo-setup-summary-title:visited, .ibo-setup-summary-title:hover {
|
||||
font-size: $ibo-font-size-150;
|
||||
color: inherit;
|
||||
.ibo-setup-summary-title {
|
||||
font-size: $ibo-font-size-150;
|
||||
}
|
||||
|
||||
|
||||
#ibo-setup-licenses--components-list {
|
||||
background-color: $ibo-color-white-200;
|
||||
padding: 12px;
|
||||
@@ -612,7 +605,6 @@ body {
|
||||
color:#a00000;
|
||||
}
|
||||
.setup-extension-tag {
|
||||
display: inline-flex;
|
||||
background-color: grey;
|
||||
border-radius: 8px;
|
||||
padding-left: 3px;
|
||||
@@ -638,21 +630,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.ibo-extension-details--actions input{
|
||||
margin:0.2em 0.5em;
|
||||
width: 12px;
|
||||
}
|
||||
:not(.ibo-badge) ~ .ibo-badge{
|
||||
margin-left:0.5em;
|
||||
}
|
||||
.ibo-extension-details--information--label i{
|
||||
font-size : 0.9em;
|
||||
margin-left:0.3em;
|
||||
}
|
||||
|
||||
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
@@ -699,10 +676,14 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
#progress_content *:not(.message) + .message {
|
||||
margin-top: 1.5rem;
|
||||
#progress_content {
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
}
|
||||
#installation_progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#fresh_content{
|
||||
border: 0;
|
||||
min-height: 300px;
|
||||
|
||||
@@ -97,19 +97,18 @@
|
||||
$bDebug = isset($aServerParams['debug']) ? $aServerParams['debug'] : false;
|
||||
}
|
||||
|
||||
if (array_key_exists(LDAP_OPT_DEBUG_LEVEL, $aOptions))
|
||||
{
|
||||
// Set debug level before trying to connect, so that debug info appear in the PHP error log if ldap_connect goes wrong
|
||||
$bRet = ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, $aOptions[LDAP_OPT_DEBUG_LEVEL]);
|
||||
$this->LogInfo($bDebug, "ldap_set_option('LDAP_OPT_DEBUG_LEVEL', '{$aOptions[LDAP_OPT_DEBUG_LEVEL]}') returned ".($bRet ? 'true' : 'false'));
|
||||
}
|
||||
|
||||
$hDS = @ldap_connect($sURI);
|
||||
if ($hDS === false)
|
||||
{
|
||||
$this->LogIssue($bDebug, "ldap_authentication: can not connect to the LDAP server '$sURI'. Check the configuration file config-itop.php.");
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists(LDAP_OPT_DEBUG_LEVEL, $aOptions))
|
||||
{
|
||||
// Set debug level before trying to connect, so that debug info appear in the PHP error log if ldap_connect goes wrong
|
||||
$bRet = ldap_set_option($hDS, LDAP_OPT_DEBUG_LEVEL, $aOptions[LDAP_OPT_DEBUG_LEVEL]);
|
||||
$this->LogInfo($bDebug, "ldap_set_option('LDAP_OPT_DEBUG_LEVEL', '{$aOptions[LDAP_OPT_DEBUG_LEVEL]}') returned ".($bRet ? 'true' : 'false'));
|
||||
}
|
||||
foreach($aOptions as $name => $value)
|
||||
{
|
||||
$bRet = ldap_set_option($hDS, $name, $value);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
|
||||
<module_parameters>
|
||||
<parameters id="authent-local" _delta="define">
|
||||
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{12,}$</password_validation.pattern>
|
||||
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$</password_validation.pattern>
|
||||
<password_validation.message type="hash"/>
|
||||
</parameters>
|
||||
</module_parameters>
|
||||
|
||||
@@ -29,7 +29,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Heslo nemůže uživatel změnit.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Heslo bylo obnoveno',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Termín, kdy bylo heslo změneno',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Heslo musí obsahovat minimálně 12 znaků a musí obsahovat minimálně jedno velké písmeno, jedno malé písmeno, jedno číslo a speciální znak.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Heslo musí obsahovat minimálně 8 znaků a musí obsahovat minimálně jedno velké písmeno, jedno malé písmeno, jedno číslo a speciální znak.',
|
||||
'UserLocal:password:expiration' => 'Níže uvedená pole vyžadují rozšíření',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Nastavení exspirace "Jednorázového hesla" nelze u vlastního účtu uživatele.',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort muss mindestens 12 Zeichen lang sein und Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort entspricht nicht dem in den Konfigurationsregeln hinterlegten RegEx-Ausdruck',
|
||||
'UserLocal:password:expiration' => 'Die folgenden Felder benötigen eine '.ITOP_APPLICATION_SHORT.' Erweiterung',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Das setzen des Passwortablaufs auf "Einmalpasswort" ist für den eigenen Benutzer nicht erlaubt.',
|
||||
]);
|
||||
|
||||
@@ -55,7 +55,7 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
|
||||
]);
|
||||
|
||||
@@ -55,7 +55,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
|
||||
]);
|
||||
|
||||
@@ -25,7 +25,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'El usuario no puede cambiar la contraseña.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Renovación de contraseña',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Cuando fue el último cambio de contraseña',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 12 caracteres e incluir mayúsculas, minúsculas, números y caracteres especiales.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 8 caracteres e incluír mayúsculas, minúsculas, números y caracteres especiales.',
|
||||
'UserLocal:password:expiration' => 'El siguiente campo requiere una extensión',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Configurar expiración de contraseña para "ontraseña de un solo uso" no está permitido para su propio Usuario',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Mot de passe changé le',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Dernière date à laquelle le mot de passe a été changé',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 12 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 8 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
|
||||
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impossible de mettre "Usage unique" comme validité du mot de passe pour son propre utilisateur.',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'A felhasználó nem változtathat jelszót.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Jelszó megújítás ideje',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'A jelszó legutóbbi módosításának időpontja',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 12 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 8 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
|
||||
'UserLocal:password:expiration' => 'Az alábbi mezőkhöz egy bővítmény szükséges',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'A jelszó lejárati idejének beállítása "Egyszeri jelszóra" nem engedélyezett a saját Felhasználó számára.',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'La password non può essere cambiata dall\'utente.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Rinnovo della password',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Quando è stata cambiata l\'ultima volta la password',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La password deve essere di almeno 12 caratteri e includere lettere maiuscole, minuscole, numeri e caratteri speciali.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La password deve essere di almeno 8 caratteri e includere lettere maiuscole, minuscole, numeri e caratteri speciali.',
|
||||
'UserLocal:password:expiration' => 'I campi sottostanti richiedono un\'estensione',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impostare la scadenza della password su "Password monouso" non è consentito per il proprio utente',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'De gebruiker kan dit wachtwoord niet veranderen.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Wachtwoord laatst aangepast',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Tijdstip waarop het wachtwoord het laatst aangepast werd.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 12 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 8 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
|
||||
'UserLocal:password:expiration' => 'De velden hieronder vereisen een extensie.',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Je kan geen eenmalig wachtwoord instellen voor je eigen gebruiker.',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Hasło nie może być zmienione przez użytkownika.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Odnowienie hasła',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Kiedy ostatnio zmieniano hasło',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 12 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 8 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
|
||||
'UserLocal:password:expiration' => 'Poniższe pola wymagają rozszerzenia',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Ustawienie wygaśnięcia hasła "Hasło jednorazowe" nie jest dozwolone dla własnego użytkownika',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Дата изменения пароля',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Когда пароль был изменен в последний раз',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 12 символов и включать прописные, строчные, числовые и специальные символы.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 8 символов и включать прописные, строчные, числовые и специальные символы.',
|
||||
'UserLocal:password:expiration' => 'Поля требуют наличия доп. расширения',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -28,7 +28,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
]);
|
||||
|
||||
@@ -51,7 +51,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '用户不允许修改密码.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => '上次修改密码的时间',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少12个字符, 包含大小写, 数字和特殊字符.',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少8个字符, 包含大小写, 数字和特殊字符.',
|
||||
'UserLocal:password:expiration' => '下面的区域需要插件扩展',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => '不允许用户为自己设置 "一次性密码" 的失效期限',
|
||||
]);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user