mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-05 00:18:46 +02:00
Compare commits
7 Commits
feature/75
...
support/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cac280b83 | ||
|
|
8f2c990065 | ||
|
|
03437c3453 | ||
|
|
33a51b47bf | ||
|
|
e768cc1c0c | ||
|
|
f6741a6306 | ||
|
|
1ec8aca12d |
111
.doc/itop-version-history.md
Normal file
111
.doc/itop-version-history.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# iTop version history
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
|
||||
'git0': 'lawngreen',
|
||||
'git3': 'dodgerblue',
|
||||
'git4': 'grey',
|
||||
'git5': 'grey',
|
||||
'git6': 'grey',
|
||||
'git7': 'grey',
|
||||
'git8': 'grey'
|
||||
}, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
|
||||
gitGraph
|
||||
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
|
||||
branch support/2.3 order: 900
|
||||
commit id: "2016-07-08" tag: "2.3.1"
|
||||
commit id: "2016-12-22" tag: "2.3.3"
|
||||
commit id: "2017-04-14" tag: "2.3.4"
|
||||
checkout develop
|
||||
commit id: "2017-07-12" tag: "2.4.0-beta" type: REVERSE
|
||||
commit id: "2017-11-16" tag: "2.4.0" type: HIGHLIGHT
|
||||
branch support/2.4 order: 890
|
||||
commit id: "2018-02-14" tag: "2.4.1"
|
||||
checkout develop
|
||||
commit id: "2018-04-25" tag: "2.5.0-beta" type: REVERSE
|
||||
checkout support/2.4
|
||||
commit id: "2018-06-14" tag: "2.4.2"
|
||||
checkout develop
|
||||
commit id: "2018-06-27" tag: "2.5.0" type: HIGHLIGHT
|
||||
branch support/2.5 order: 880
|
||||
checkout develop
|
||||
commit id: "2019-01-09" tag: "2.6.0" type: HIGHLIGHT
|
||||
branch support/2.6 order: 870
|
||||
commit id: "2019-03-28" tag: "2.6.1"
|
||||
checkout develop
|
||||
commit id: "2019-12-18" tag: "2.7.0-beta" type: REVERSE
|
||||
checkout support/2.5
|
||||
commit id: "2020-01-22" tag: "2.5.4"
|
||||
checkout support/2.6
|
||||
commit id: "2020-01-23" tag: "2.6.3"
|
||||
checkout develop
|
||||
commit id: "2020-01-29" tag: "2.7.0-beta2" type: REVERSE
|
||||
commit id: "2020-04-01" tag: "2.7.0-1" type: HIGHLIGHT
|
||||
checkout support/2.6
|
||||
commit id: "2020-04-22" tag: "2.6.4"
|
||||
checkout develop
|
||||
branch support/2.7 order: 860
|
||||
commit id: "2020-06-26" tag: "2.7.1"
|
||||
checkout support/2.7
|
||||
commit id: "2020-12-09" tag: "2.7.3"
|
||||
commit id: "2021-03-31" tag: "2.7.4"
|
||||
checkout develop
|
||||
commit id: "2021-04-06" tag: "3.0.0-beta" type: REVERSE
|
||||
checkout support/2.7
|
||||
commit id: "2021-07-05" tag: "2.7.5"
|
||||
checkout develop
|
||||
commit id: "2021-07-05." tag: "3.0.0-beta2" type: REVERSE
|
||||
checkout support/2.7
|
||||
commit id: "2021-12-17" tag: "2.7.6"
|
||||
checkout develop
|
||||
commit id: "2022-01-04" tag: "3.0.0" type: HIGHLIGHT
|
||||
branch support/3.0 order: 850
|
||||
commit id: "2022-04-08" tag: "3.0.1"
|
||||
checkout support/2.7
|
||||
commit id: "2022-07-11" tag: "2.7.7"
|
||||
checkout support/3.0
|
||||
commit id: "2022-09-12" tag: "3.0.2-1"
|
||||
checkout develop
|
||||
checkout support/2.7
|
||||
commit id: "2022-12-28" tag: "2.7.8"
|
||||
checkout support/3.0
|
||||
commit id: "2023-04-12" tag: "3.0.3"
|
||||
checkout develop
|
||||
commit id: "2023-06-19" tag: "3.1.0-beta" type: REVERSE
|
||||
commit id: "2023-07-26" tag: "3.1.0-1" type: HIGHLIGHT
|
||||
branch support/3.1 order: 840
|
||||
checkout support/3.1
|
||||
commit id: "2023-08-09" tag: "3.1.0-2"
|
||||
checkout support/2.7
|
||||
commit id: "2023-08-10" tag: "2.7.9"
|
||||
checkout support/3.1
|
||||
commit id: "2023-12-20" tag: "3.1.1"
|
||||
checkout develop
|
||||
commit id: "2024-01-15" tag: "Start 3.2" type: HIGHLIGHT
|
||||
branch support/3.2 order: 830
|
||||
checkout support/2.7
|
||||
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"
|
||||
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"
|
||||
checkout support/2.7
|
||||
commit id: "2025-09-25" tag: "2.7.13"
|
||||
checkout support/3.2
|
||||
commit id: "2026-04-27 " tag: "3.2.3"
|
||||
```
|
||||
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
59
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
59
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: "Bug report"
|
||||
description: "Report a bug that you identified in iTop, with the steps to reproduce it and the expected vs actual behavior. If you have an improvement proposition, please use the 'Enhancement suggestion' template instead."
|
||||
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, .tar.gz"
|
||||
54
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
Normal file
54
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: "Enhancement suggestion"
|
||||
description: "Suggest an improvement to iTop, with a clear description of the expected behavior and the benefits it would bring. If you identified a bug and have an improvement proposition, please use the 'Bug report' template instead."
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please explain why you're creating this issue :
|
||||
- Are you willing to create a PR for this enhancement ? If so, we'll indicate in the issue if we're interested in it.
|
||||
- Then, please describe what's your improvement proposition.
|
||||
|
||||
- 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: 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: 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, .tar.gz"
|
||||
61
.github/pull_request_template.md
vendored
61
.github/pull_request_template.md
vendored
@@ -1,83 +1,76 @@
|
||||
<!--
|
||||
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.
|
||||
|
||||
IMPORTANT: Please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
|
||||
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. 🙏
|
||||
|
||||
Any PRs not following the guidelines or with missing information will not be considered.
|
||||
|
||||
-->
|
||||
|
||||
## Base information
|
||||
| Question | Answer
|
||||
|---------------------------------------------------------------|--------
|
||||
| Related to a SourceForge thead / 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 |
|
||||
|
||||
## 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 much details as possible.
|
||||
- Describe what is blocking you, what is the objective with as many details as possible.
|
||||
- Add screenshots if it's related to UI.
|
||||
-->
|
||||
|
||||
|
||||
## Reproduction procedure (bug)
|
||||
|
||||
<!--
|
||||
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.
|
||||
|
||||
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 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) -->
|
||||
2. First go there
|
||||
2. Then do that
|
||||
3. ...
|
||||
4. Finally, see that...
|
||||
3. First go there
|
||||
4. Then do that
|
||||
5. ...
|
||||
6. Finally, see that... (what is expected and what is actually happening)
|
||||
|
||||
## 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)
|
||||
|
||||
<!--
|
||||
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
|
||||
|
||||
<!--
|
||||
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 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
|
||||
- ...
|
||||
-->
|
||||
|
||||
- [ ] ...
|
||||
- [ ] ...
|
||||
- [ ] ...
|
||||
- [ ] Is the PR clear and detailed enough so anyone can understand without digging in the code?
|
||||
@@ -4,30 +4,33 @@ 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
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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!
|
||||
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 ticket](https://sourceforge.net/p/itop/tickets/new/) to submit it, but be warned that there are lots of good
|
||||
[create a corresponding issue][itop-issues] 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.
|
||||
@@ -37,48 +40,33 @@ 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
|
||||
[wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension
|
||||
|
||||
[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 :
|
||||
|
||||
- `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.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
|
||||
- `develop` containing future 3.3.0 version
|
||||
- `support/3.2`: 3.2.x maintenance version
|
||||
|
||||
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.
|
||||
And when 3.3.0 will be out:
|
||||
|
||||
- `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
|
||||
|
||||
@@ -92,12 +80,11 @@ 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 based your developments on the develop branch.
|
||||
Most of the time you should base 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).
|
||||
@@ -106,7 +93,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 `test/` directory, containing a PHPUnit config file : `phpunit.xml.dist`.
|
||||
Our tests are located in the `tests/` directory, containing a PHPUnit config file : `phpunit.xml.dist`.
|
||||
|
||||
### Git Commit Messages
|
||||
|
||||
@@ -140,12 +127,12 @@ When your code is working, please:
|
||||
* Pull request description: mind to add all the information useful to understand why you're suggesting this modification and anything necessary to dive into your work. Especially:
|
||||
- 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))
|
||||
|
||||
* 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 :)
|
||||
|
||||
@@ -158,7 +145,3 @@ We have one sticker per contribution type. You might get multiple stickers with
|
||||
* Ambassador: Outstanding community contributors
|
||||
* Beta tester: Test and give feedback on beta releases
|
||||
* Extension developer: Develop and publish an extension
|
||||
|
||||
Here is the design of each stickers for year 2024:
|
||||
|
||||

|
||||
|
||||
@@ -176,7 +176,7 @@ class EventNotificationEmail extends EventNotification
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', ['date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body', 'attachments']); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['date', 'message', 'to', 'subject', 'attachments']); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('list', ['date', 'message', 'to', 'cc', 'bcc', 'subject', 'attachments']); // Attributes to be displayed for a list
|
||||
|
||||
// Search criteria
|
||||
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
|
||||
@@ -36,7 +36,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'UI:ResetPwd-Ready' => 'Le mot de passe a bien été changé.',
|
||||
'UI:ResetPwd-Login' => 'Cliquez ici pour vous connecter...',
|
||||
|
||||
'UI:Login:About' => ITOP_APPLICATION.' Powered by Combodo~~',
|
||||
'UI:Login:About' => ITOP_APPLICATION.' Powered by Combodo',
|
||||
'UI:Login:ChangeYourPassword' => 'Changer de mot de passe',
|
||||
'UI:Login:OldPasswordPrompt' => 'Ancien mot de passe',
|
||||
'UI:Login:NewPasswordPrompt' => 'Nouveau mot de passe',
|
||||
|
||||
@@ -30,6 +30,7 @@ use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mime\Email as SymfonyEmail;
|
||||
use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\MixedPart;
|
||||
@@ -389,20 +390,40 @@ class EMailSymfony extends Email
|
||||
*/
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
{
|
||||
// Some mime types can have options, let's retrieve the different parts so we can extract the primary mime type
|
||||
$aMimeTypeParts = array_map('trim', explode(';', $sMimeType));
|
||||
$sPrimaryMimeType = strtolower(array_shift($aMimeTypeParts));
|
||||
|
||||
$aMimeTypeParams = [];
|
||||
foreach ($aMimeTypeParts as $sPart) {
|
||||
// Parse key=value parameters (eg method=REQUEST, charset=UTF-8) after the semicolon.
|
||||
// Stored in $aMimeTypeParams so they can be added to the Content-Type header.
|
||||
if ($sPart === '') {
|
||||
continue;
|
||||
}
|
||||
$aPair = explode('=', $sPart, 2);
|
||||
$sKey = strtolower(trim($aPair[0]));
|
||||
// Normalize and unquote the parameter value for Content-Type (eg charset=UTF-8).
|
||||
$sValue = isset($aPair[1]) ? trim($aPair[1], " \t\n\r\0\x0B\"") : '';
|
||||
if ($sKey !== '') {
|
||||
$aMimeTypeParams[$sKey] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline CSS if needed
|
||||
if ($sMimeType === 'text/html') {
|
||||
if ($sPrimaryMimeType === 'text/html') {
|
||||
$sBody = static::InlineCssIntoBodyContent($sBody, $sCustomStyles);
|
||||
}
|
||||
|
||||
$this->m_aData['body'] = ['body' => $sBody, 'mimeType' => $sMimeType];
|
||||
|
||||
$oTextPart = new TextPart(strip_tags($sBody), 'utf-8', 'plain', 'base64');
|
||||
|
||||
// Embed inline images and store them in attachments (so BuildSymfonyMessageFromInternal can pick them)
|
||||
if ($sMimeType === 'text/html') {
|
||||
if ($sPrimaryMimeType === 'text/html') {
|
||||
$aAdditionalParts = $this->EmbedInlineImages($sBody);
|
||||
$oTextPart = new TextPart((new DefaultHtmlToTextConverter())->convert($sBody, 'utf-8'), 'utf-8', 'plain', 'base64');
|
||||
$oHtmlPart = new TextPart($sBody, 'utf-8', 'html', 'base64');
|
||||
$oAlternativePart = new AlternativePart($oHtmlPart, $oTextPart);
|
||||
// It's important de order parts from least prefered to most prefered as per RFC 2046 {@see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.4}
|
||||
$oAlternativePart = new AlternativePart($oTextPart, $oHtmlPart);
|
||||
// Default root part is the HTML body
|
||||
$oRootPart = $oAlternativePart;
|
||||
|
||||
@@ -411,8 +432,20 @@ class EMailSymfony extends Email
|
||||
$oRootPart = new RelatedPart(...$aRelatedParts);
|
||||
}
|
||||
} else {
|
||||
// Default root part is the text body
|
||||
$oRootPart = $oTextPart;
|
||||
// Default root part is a TextPart with the right mimetype
|
||||
$sSubtype = 'plain';
|
||||
// Extract subtype for text/* content; default to plain otherwise.
|
||||
if (strpos($sPrimaryMimeType, '/') !== false) {
|
||||
list($sType, $sSubtype) = explode('/', $sPrimaryMimeType, 2);
|
||||
if ($sType !== 'text') {
|
||||
$sSubtype = 'plain';
|
||||
}
|
||||
}
|
||||
$oRootPart = new TextPart($sBody, 'utf-8', $sSubtype, 'base64');
|
||||
// Add parsed Content-Type parameters like method/charset if provided.
|
||||
if (!empty($aMimeTypeParams)) {
|
||||
$oRootPart->getHeaders()->addParameterizedHeader('Content-Type', $sPrimaryMimeType, $aMimeTypeParams);
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_oMessage->setBody($oRootPart);
|
||||
@@ -432,9 +465,13 @@ class EMailSymfony extends Email
|
||||
|
||||
/**
|
||||
* Add a new part to the existing body
|
||||
*
|
||||
* @deprecated 3.2.3 3.3.0 N°9549 This worked with the previous mail lib (Laminas) but it no longer works with SymfonyMailer and needs to be split in specific methods
|
||||
*/
|
||||
public function AddPart($sText, $sMimeType = 'text/html')
|
||||
{
|
||||
\DeprecatedCallsLog::NotifyDeprecatedPhpMethod('No generic alternative yet, if you want to add an attachment use `self::AddAttachment()`');
|
||||
|
||||
$sMimeSubtype = $this->GetMimeSubtype($sMimeType);
|
||||
|
||||
if (!array_key_exists('parts', $this->m_aData)) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Core\Email\EMailSymfony;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
|
||||
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
|
||||
class EmailSymfonyTest extends ItopTestCase
|
||||
{
|
||||
@@ -135,4 +140,162 @@ HTML;
|
||||
|
||||
$this->assertSame($sExpectedBody, $sActualBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parts of the AlternativePart produced by SetBody() for an HTML email.
|
||||
*
|
||||
* Handles both the simple case (AlternativePart at root) and the inline-images case
|
||||
* where the root is a RelatedPart whose first child is the AlternativePart.
|
||||
*
|
||||
* @return AbstractPart[]
|
||||
*/
|
||||
private function GetAlternativePartsFromHtmlEmail(EMailSymfony $oEmail): array
|
||||
{
|
||||
$oSymfonyMessage = $this->GetNonPublicProperty($oEmail, 'm_oMessage');
|
||||
$oBody = $oSymfonyMessage->getBody();
|
||||
|
||||
// With inline images the root is a RelatedPart; the AlternativePart is its first child.
|
||||
if ($oBody instanceof RelatedPart) {
|
||||
$oBody = $oBody->getParts()[0];
|
||||
}
|
||||
|
||||
$this->assertInstanceOf(AlternativePart::class, $oBody, 'Body should be a multipart/alternative for HTML emails');
|
||||
|
||||
return $oBody->getParts();
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 2046 §5.1.4: parts in multipart/alternative must be ordered from least to most preferred.
|
||||
* Email clients display the last part they support, so text/plain must come first and text/html last.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.4
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyAlternativePartOrderForHtmlEmailIsPlainThenHtml(): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
$oEmail->SetBody('<p>Hello there!</p>', 'text/html');
|
||||
|
||||
[$oFirstPart, $oSecondPart] = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
|
||||
$this->assertSame('plain', $oFirstPart->getMediaSubtype(), 'First part must be text/plain (least preferred per RFC 2046)');
|
||||
$this->assertSame('html', $oSecondPart->getMediaSubtype(), 'Last part must be text/html (most preferred per RFC 2046)');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSetBodyPlainTextDoesNotContainCss
|
||||
*
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyPlainTextDoesNotContainCss(string $sHtml, ?string $sCustomStyles): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
$oEmail->SetBody($sHtml, 'text/html', $sCustomStyles);
|
||||
|
||||
// We locate the plain text part by subtype to be order-agnostic and isolate this assertion from the order bug.
|
||||
$aParts = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
$oPlainPart = null;
|
||||
foreach ($aParts as $oPart) {
|
||||
if ($oPart instanceof TextPart && $oPart->getMediaSubtype() === 'plain') {
|
||||
$oPlainPart = $oPart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($oPlainPart, 'No text/plain part found in the message');
|
||||
|
||||
$sPlainText = $oPlainPart->getBody();
|
||||
|
||||
$this->assertStringNotContainsString('<style>', $sPlainText, 'Style tag must not appear in plain text');
|
||||
$this->assertStringNotContainsString('color:', $sPlainText, 'CSS color rule must not appear in plain text');
|
||||
$this->assertStringNotContainsString('font-size:', $sPlainText, 'CSS font-size rule must not appear in plain text');
|
||||
$this->assertStringNotContainsString('@media', $sPlainText, 'CSS @media rule must not appear in plain text');
|
||||
$this->assertStringContainsString('Hello there!', $sPlainText, 'Actual content must be preserved in plain text');
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML part must contain the body content and the CSS inlined by Emogrifier.
|
||||
* This guards against regressions where the wrong body (e.g. the plain-text version)
|
||||
* would end up in the HTML part.
|
||||
*
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyHtmlPartContainsBodyAndInlinedCss(): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
$oEmail->SetBody('<html><body><p>Hello there!</p></body></html>', 'text/html', 'p { color: red; }');
|
||||
|
||||
$aParts = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
|
||||
$oHtmlPart = null;
|
||||
foreach ($aParts as $oPart) {
|
||||
if ($oPart instanceof TextPart && $oPart->getMediaSubtype() === 'html') {
|
||||
$oHtmlPart = $oPart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($oHtmlPart, 'No text/html part found in the message');
|
||||
|
||||
$sHtmlContent = $oHtmlPart->getBody();
|
||||
$this->assertStringContainsString('Hello there!', $sHtmlContent, 'HTML part must preserve the original text content');
|
||||
$this->assertStringContainsString('color: red', $sHtmlContent, 'HTML part must contain the CSS inlined by Emogrifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* With inline images, SetBody() wraps the AlternativePart in a RelatedPart.
|
||||
* The AlternativePart must still be correctly ordered (plain first, HTML last)
|
||||
* and the plain-text part must not contain CSS.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.4
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyWithInlineImagesHasCorrectPartStructure(): void
|
||||
{
|
||||
// Anonymous subclass so we can inject a fake inline image part without a real inline image in DB
|
||||
$oEmail = new class () extends EMailSymfony {
|
||||
protected function EmbedInlineImages(string &$sBody): array
|
||||
{
|
||||
return [new DataPart('fake-image-data', 'image.png', 'image/png')];
|
||||
}
|
||||
};
|
||||
$oEmail->SetBody('<html><head><style>p { color: red; }</style></head><body><p>Hello there!</p></body></html>', 'text/html');
|
||||
|
||||
$oSymfonyMessage = $this->GetNonPublicProperty($oEmail, 'm_oMessage');
|
||||
$oBody = $oSymfonyMessage->getBody();
|
||||
|
||||
// Root must be a RelatedPart when inline images are present
|
||||
$this->assertInstanceOf(RelatedPart::class, $oBody, 'Root part must be multipart/related when inline images are present');
|
||||
|
||||
// The AlternativePart must be the first child of the RelatedPart
|
||||
$aRelatedParts = $oBody->getParts();
|
||||
$this->assertInstanceOf(AlternativePart::class, $aRelatedParts[0], 'First child of RelatedPart must be the AlternativePart');
|
||||
|
||||
// Order and CSS checks are delegated to the shared helper, which now handles RelatedPart
|
||||
[$oFirstPart, $oSecondPart] = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
$this->assertSame('plain', $oFirstPart->getMediaSubtype(), 'First part must be text/plain (least preferred per RFC 2046)');
|
||||
$this->assertSame('html', $oSecondPart->getMediaSubtype(), 'Last part must be text/html (most preferred per RFC 2046)');
|
||||
}
|
||||
|
||||
public function provideSetBodyPlainTextDoesNotContainCss(): array
|
||||
{
|
||||
$sCustomStyles = 'p { color: blue; font-size: 14px; }';
|
||||
|
||||
return [
|
||||
'<style> tag in HTML, no custom styles' => [
|
||||
'<html><head><style>body { color: red; font-size: 12px; } @media print { p { color: black; } }</style></head><body><p>Hello there!</p></body></html>',
|
||||
null,
|
||||
],
|
||||
'<style> tag in HTML with custom styles' => [
|
||||
'<html><head><style>body { color: red; font-size: 12px; } @media print { p { color: black; } }</style></head><body><p>Hello there!</p></body></html>',
|
||||
$sCustomStyles,
|
||||
],
|
||||
'custom styles only, no <style> tag' => [
|
||||
'<html><body><p>Hello there!</p></body></html>',
|
||||
$sCustomStyles,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user