mirror of
https://github.com/Combodo/iTop.git
synced 2026-09-29 21:09:10 +02:00
Compare commits
254 Commits
feature/63
...
support/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67c6a0732c | ||
|
|
a2a00cb582 | ||
|
|
4e8d34bb23 | ||
|
|
4d699f79e8 | ||
|
|
4e70886d2a | ||
|
|
c2929ecd33 | ||
|
|
873cea1a76 | ||
|
|
b7432b9e60 | ||
|
|
adc2596b4a | ||
|
|
16d68fe993 | ||
|
|
4e1bf80b9f | ||
|
|
2ac4229ee0 | ||
|
|
e6d485855e | ||
|
|
ab9d270b62 | ||
|
|
4a6af15237 | ||
|
|
05e585ced3 | ||
|
|
9e4f2a3053 | ||
|
|
a8b7622110 | ||
|
|
c241c257a5 | ||
|
|
ffa2a6dfd4 | ||
|
|
5d99042d4c | ||
|
|
8ff57dabd0 | ||
|
|
b9a7923a40 | ||
|
|
6c1206658e | ||
|
|
1a395f4b50 | ||
|
|
0ae3bc2821 | ||
|
|
98a007ff9c | ||
|
|
ee17738e8e | ||
|
|
da62814e67 | ||
|
|
aef80d60a8 | ||
|
|
5597040a8a | ||
|
|
7d89a05d8b | ||
|
|
ddc3fd8fd1 | ||
|
|
59af2e8b96 | ||
|
|
7d8a350394 | ||
|
|
4ae3a8ec1d | ||
|
|
34c394718d | ||
|
|
1fac630fdb | ||
|
|
cab1710498 | ||
|
|
8d1711bef5 | ||
|
|
f35af2b8b0 | ||
|
|
91e997a4e0 | ||
|
|
a8eb2a3fff | ||
|
|
dc5384f8aa | ||
|
|
2540d7039d | ||
|
|
373942f922 | ||
|
|
7d4fc98ecc | ||
|
|
cfb04561ec | ||
|
|
f3bad3538c | ||
|
|
9c6baaeef4 | ||
|
|
e769e53653 | ||
|
|
42380f7a7a | ||
|
|
8b384498e3 | ||
|
|
04f1eaff1f | ||
|
|
175b88de30 | ||
|
|
0a5b0b403e | ||
|
|
a346b71e9a | ||
|
|
8065343ea6 | ||
|
|
9c6087fd54 | ||
|
|
66073ba390 | ||
|
|
4756e2474c | ||
|
|
7dbbf742c4 | ||
|
|
2cd4fd1e0e | ||
|
|
fffff76c82 | ||
|
|
06d593631d | ||
|
|
54a0dfd06f | ||
|
|
e31492fe27 | ||
|
|
dfd7f4cd0a | ||
|
|
69624116db | ||
|
|
f3b69ad12e | ||
|
|
edb33e31da | ||
|
|
5542d9882f | ||
|
|
629f9ec210 | ||
|
|
4bd5c22d36 | ||
|
|
2c53b740fc | ||
|
|
18b7ee4d30 | ||
|
|
98913a2a86 | ||
|
|
62ea1c1fd6 | ||
|
|
b919adf4d8 | ||
|
|
4ebd03eebb | ||
|
|
4f891a9170 | ||
|
|
7583ede3b3 | ||
|
|
95cb21cff7 | ||
|
|
cdd7bbedcb | ||
|
|
ea1ed03d0c | ||
|
|
2073e8c10c | ||
|
|
1a41349d7b | ||
|
|
fdc1ef40e6 | ||
|
|
4722551a16 | ||
|
|
a2934ef05b | ||
|
|
1cdc2c5648 | ||
|
|
31eddad029 | ||
|
|
b43d71f324 | ||
|
|
97364af71d | ||
|
|
26033774b8 | ||
|
|
27c0fe191b | ||
|
|
fac132f707 | ||
|
|
6f9a4cb55f | ||
|
|
c07317dc71 | ||
|
|
a3fcc6fdf0 | ||
|
|
b13065f23f | ||
|
|
2eaca40632 | ||
|
|
7f3a1811d7 | ||
|
|
87b48c638c | ||
|
|
54a1221e4a | ||
|
|
97c589c440 | ||
|
|
68e799c0fd | ||
|
|
658825b308 | ||
|
|
af08824866 | ||
|
|
8693951aa9 | ||
|
|
a28e481ee4 | ||
|
|
6032628a70 | ||
|
|
2892949e33 | ||
|
|
ba643ba2ab | ||
|
|
3711e8a5a1 | ||
|
|
a38fef5bbf | ||
|
|
23e4ce7263 | ||
|
|
1bba486ee8 | ||
|
|
801090fddb | ||
|
|
1d77b9faa2 | ||
|
|
5282913791 | ||
|
|
36fae27355 | ||
|
|
812f0b573d | ||
|
|
c7c16f091b | ||
|
|
3739067473 | ||
|
|
fd553b86dd | ||
|
|
d7625d9650 | ||
|
|
893a1d7f71 | ||
|
|
fda1d3dd16 | ||
|
|
61464fc819 | ||
|
|
c281677768 | ||
|
|
e280251623 | ||
|
|
f657308137 | ||
|
|
4596404666 | ||
|
|
64394099f7 | ||
|
|
67e2899a60 | ||
|
|
8d75531d95 | ||
|
|
1e023e5431 | ||
|
|
e2a1c3e79e | ||
|
|
c7570d62c1 | ||
|
|
a30ed63a62 | ||
|
|
dd77124b07 | ||
|
|
9bfe0430f5 | ||
|
|
3fe91ed0b4 | ||
|
|
ccb9633226 | ||
|
|
82b50dc6cd | ||
|
|
d3451f8c30 | ||
|
|
b5c8a98695 | ||
|
|
1403914572 | ||
|
|
b976471a0d | ||
|
|
fa66a09104 | ||
|
|
47981f78e3 | ||
|
|
476ec75a2e | ||
|
|
10c000ddfe | ||
|
|
7e55b9ccfc | ||
|
|
5bb288ffca | ||
|
|
56d7e40f4c | ||
|
|
e0dc1fa571 | ||
|
|
d6466c0bd6 | ||
|
|
eb16e6cbc8 | ||
|
|
42007ffd95 | ||
|
|
6329c157e8 | ||
|
|
9ee0548767 | ||
|
|
620a6b9a51 | ||
|
|
968c56745a | ||
|
|
04a5e5463a | ||
|
|
006dd3d6c2 | ||
|
|
a808e89c30 | ||
|
|
c6e50a0ed7 | ||
|
|
d88687527e | ||
|
|
2cc071b07a | ||
|
|
76a410e00d | ||
|
|
c68887c2bc | ||
|
|
837b433622 | ||
|
|
52fca750b0 | ||
|
|
a5c14e4870 | ||
|
|
0e72b090ee | ||
|
|
d1aedee245 | ||
|
|
1b70b4bf70 | ||
|
|
41f51a2c0c | ||
|
|
73ede13443 | ||
|
|
40dd72ef82 | ||
|
|
c8aea56171 | ||
|
|
041437cbeb | ||
|
|
85d50f31bd | ||
|
|
5715e0484f | ||
|
|
109fdfa06b | ||
|
|
3c301affb3 | ||
|
|
147fae7f8c | ||
|
|
a5aa8c279f | ||
|
|
13fad9efc1 | ||
|
|
9fbc53cf00 | ||
|
|
04f91ab4a2 | ||
|
|
918f110dc9 | ||
|
|
8e0246601a | ||
|
|
bd9cc9c15e | ||
|
|
60ce21509f | ||
|
|
e6cdcd0a5a | ||
|
|
2ab3081926 | ||
|
|
5cabac63e7 | ||
|
|
3918dcf830 | ||
|
|
489300552a | ||
|
|
8507c606ba | ||
|
|
128989450b | ||
|
|
1bd404af6f | ||
|
|
6c770d3e16 | ||
|
|
a74dbb3a73 | ||
|
|
b365aa0971 | ||
|
|
56aa50c9b1 | ||
|
|
eacb7f3401 | ||
|
|
b14045af29 | ||
|
|
7321eeb23f | ||
|
|
05b8c529fe | ||
|
|
a68c017225 | ||
|
|
82b52b95a1 | ||
|
|
556f612f7e | ||
|
|
edc69bebba | ||
|
|
dce1994b85 | ||
|
|
fde6527057 | ||
|
|
0de0592c18 | ||
|
|
c9d512cc31 | ||
|
|
a3ef049063 | ||
|
|
87ec702c5e | ||
|
|
127be1933d | ||
|
|
2c14030fc5 | ||
|
|
7825c08069 | ||
|
|
4ddf17233f | ||
|
|
9493bb03d5 | ||
|
|
8f87c759b5 | ||
|
|
348c90145d | ||
|
|
c37143176a | ||
|
|
0c8229a68e | ||
|
|
fda5fe5ded | ||
|
|
6b396adeea | ||
|
|
c448f8411c | ||
|
|
4b4fe55060 | ||
|
|
dedcd4af19 | ||
|
|
0556a03839 | ||
|
|
64df429715 | ||
|
|
9ae813db2b | ||
|
|
768423a6aa | ||
|
|
921e7c88fe | ||
|
|
d6ef4fb7bb | ||
|
|
a335e1004b | ||
|
|
e882df96d9 | ||
|
|
3a7439e970 | ||
|
|
7fca6424ab | ||
|
|
f09a73c45a | ||
|
|
5a174dd8fa | ||
|
|
01f32086ec | ||
|
|
5c59fdebaf | ||
|
|
363d0767d5 | ||
|
|
5202adb902 | ||
|
|
1d9e7f9b55 |
@@ -2,13 +2,17 @@
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
|
||||
'git0': 'lawngreen',
|
||||
'git3': 'dodgerblue',
|
||||
'git4': 'grey',
|
||||
'git5': 'grey',
|
||||
'git6': 'grey',
|
||||
'git7': 'grey',
|
||||
'git8': 'grey'
|
||||
'git0': '#7ccf00',
|
||||
'git1': '#7ccf00',
|
||||
'git2': '#99a1af',
|
||||
'git3': '#99a1af',
|
||||
'git4': '#99a1af',
|
||||
'git5': '#99a1af',
|
||||
'git6': '#99a1af',
|
||||
'git7': '#99a1af',
|
||||
'git8': '#99a1af',
|
||||
'git9': '#99a1af',
|
||||
'git10': '#99a1af'
|
||||
}, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
|
||||
gitGraph
|
||||
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
|
||||
@@ -106,6 +110,11 @@ gitGraph
|
||||
commit id: "2025-09-25" tag: "2.7.13"
|
||||
checkout support/3.2
|
||||
commit id: "2026-04-27 " tag: "3.2.3"
|
||||
commit id: "2026-05-25 " tag: "3.2.3-1"
|
||||
commit id: "2026-07-17 " tag: "3.2.3-2"
|
||||
checkout develop
|
||||
commit id: "2026-07-23" tag: "3.3.0-beta1"
|
||||
commit id: "2026-09-15" tag: "3.3.0"
|
||||
```
|
||||
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
2
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@@ -8,6 +8,8 @@ body:
|
||||
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.
|
||||
- The more users are affected and the greater is the impact, the more likely this improvement will be accepted. If you identified a bug and have an improvement proposition, please use the 'Bug report' template instead."
|
||||
Please note we're not willing to accept code improvement (e.g. refactoring) that won't have a "functional" change for the user.
|
||||
|
||||
- type: textarea
|
||||
id: enhancement_details
|
||||
|
||||
20
.github/pull_request_template.md
vendored
20
.github/pull_request_template.md
vendored
@@ -1,23 +1,22 @@
|
||||
<!--
|
||||
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.
|
||||
PR that will benefit the most users and has a greater impact on the product are more likely to be accepted, so please explain your use case and why you think it would be useful for the community.
|
||||
We're not willing to receive PR concerning codes improvements (e.g. code refactoring), except if it's part of a code that brings/corrects a feature for users.
|
||||
Please note that we're working with sprints, so we reserve the right to decline a PR if we do not plan to work on that subject in the 6 coming months.
|
||||
Thank you for your comprehension 😊
|
||||
|
||||
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. 🙏
|
||||
Once you've created the issue, 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 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 |
|
||||
| Question | Answer |
|
||||
|---------------------------------------------------------------------------------|---------------------------------------|
|
||||
| Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket? | <!-- Put the URL or ticket number --> |
|
||||
| Type of change? | Bug fix / Enhancement / Translations |
|
||||
|
||||
## Symptom (bug) / Objective (enhancement)
|
||||
|
||||
@@ -75,6 +74,7 @@ Explain in details how you are proposing to solve this:
|
||||
Don't remove these lines, check them once done.
|
||||
-->
|
||||
|
||||
- [ ] If my PR is a bug fix or an enhancement, I opened an issue that got reviewed and approved by Combodo
|
||||
- [ ] 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
|
||||
|
||||
@@ -61,7 +61,7 @@ foreach ($aTcpdfFontsDirContent as $sTcpdfFontResourceName) {
|
||||
* 2) Then adding the DroidSansFallback font (useful for CJK data for example)
|
||||
*/
|
||||
echo $sCurrentScriptFileName.": ---2) Copying font files to TCPDF ($sTcPdfFontsFolder)...\n";
|
||||
$aFontFilesToCopy = glob(__DIR__.'\droidsansfallback.*');
|
||||
$aFontFilesToCopy = glob(__DIR__.DIRECTORY_SEPARATOR.'droidsansfallback.*');
|
||||
foreach ($aFontFilesToCopy as $sFontFileToCopy) {
|
||||
$sFontFileName = basename($sFontFileToCopy);
|
||||
echo $sCurrentScriptFileName.': copying '.$sFontFileName."\n";
|
||||
|
||||
0
.make/license/gen-community-license.sh
Normal file → Executable file
0
.make/license/gen-community-license.sh
Normal file → Executable file
0
.make/license/sortLicenceXml.php
Normal file → Executable file
0
.make/license/sortLicenceXml.php
Normal file → Executable file
0
.make/license/updateLicenses.php
Normal file → Executable file
0
.make/license/updateLicenses.php
Normal file → Executable file
0
.make/release/changelog.php
Normal file → Executable file
0
.make/release/changelog.php
Normal file → Executable file
0
.make/release/update-versions.php
Normal file → Executable file
0
.make/release/update-versions.php
Normal file → Executable file
0
.make/release/update-xml.php
Normal file → Executable file
0
.make/release/update-xml.php
Normal file → Executable file
0
.make/release/update.classes.inc.php
Normal file → Executable file
0
.make/release/update.classes.inc.php
Normal file → Executable file
@@ -95,7 +95,7 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- Casteleyn, Thomas (a.k.a [@Hipska](https://www.github.com/Hipska))
|
||||
- Castro, Randall Badilla
|
||||
- Colantoni, Maria Laura
|
||||
- Couronné, Guy
|
||||
- Couronné, Guy [@GurneyHallack](https://github.com/GurneyHallack)
|
||||
- Dejin, Bie (a.k.a [@bdejin](https://github.com/bdejin))
|
||||
- Delicado, Elodie
|
||||
- Dvořák, Lukáš
|
||||
@@ -110,7 +110,7 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- Khamit, Shamil
|
||||
- Kincel, Martin
|
||||
- Konečný, Kamil
|
||||
- Kunin, Vladimir
|
||||
- Kunin, Vladimir (a.k.a [@vbkunin](https://github.com/vbkunin)
|
||||
- Lassiter, Denis (a.k.a [@delassiter](https://github.com/delassiter))
|
||||
- Lazcano, Federico
|
||||
- Lucas, Jonathan
|
||||
@@ -128,6 +128,7 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- Šafránek, Jaroslav (a.k.a [jkcinik](https://sourceforge.net/u/jkcinik/profile/) on SourceForge)
|
||||
- Seki, Shoji
|
||||
- Shilov, Vladimir
|
||||
- Srivastava, Vaibhav (a.k.a [@vaibhav8a](https://github.com/vaibhav8a))
|
||||
- Stetina, Pavel (a.k.a [@Stetinac](https://github.com/Stetinac))
|
||||
- Stukalov, Ilya (a.k.a [@ilya](https://www.github.com/ilya-stukalov))
|
||||
- Tarjányi, Csaba (a.k.a [@tacsaby](https://github.com/tacsaby))
|
||||
|
||||
@@ -582,16 +582,30 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
$aRet = [];
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oSearch->NoContextParameters();
|
||||
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
|
||||
$oProfiles = new DBObjectSet($oSearch);
|
||||
while ($oUserProfile = $oProfiles->Fetch()) {
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
if (!array_key_exists('profile_list', $oUser->ListChanges())) {
|
||||
// Profiles list is not modified on the $oUser object, we can use DBObjectSearch with `all data` capabilities
|
||||
// Note: This is the default behavior
|
||||
$aRet = [];
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oSearch->NoContextParameters();
|
||||
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
|
||||
$oProfiles = new DBObjectSet($oSearch);
|
||||
while ($oUserProfile = $oProfiles->Fetch()) {
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
|
||||
return $aRet;
|
||||
} else {
|
||||
// Profiles list must be computed with memory changes.
|
||||
// Note: this is a bit tricky because the user object may have been modified in memory (e.g. a profile added or removed) and we need to take that into account
|
||||
$aRet = [];
|
||||
$oProfilesSet = $oUser->Get('profile_list');
|
||||
foreach ($oProfilesSet as $oUserProfile) {
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetSelectFilter($oUser, $sClass, $aSettings = [])
|
||||
@@ -705,26 +719,23 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
protected function GetUserActionGrant($oUser, $sClass, $iActionCode)
|
||||
{
|
||||
$this->LoadCache();
|
||||
|
||||
// load and cache permissions for the current user on the given class
|
||||
//
|
||||
$iUser = $oUser->GetKey();
|
||||
if (isset($this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode])) {
|
||||
$aTest = $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode];
|
||||
if (is_array($aTest)) {
|
||||
return $aTest;
|
||||
if (count($oUser->ListChanges()) === 0) {
|
||||
// load and cache permissions for the current user on the given class
|
||||
if (isset($this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode])) {
|
||||
$aTest = $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode];
|
||||
if (is_array($aTest)) {
|
||||
return $aTest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sAction = self::$m_aActionCodes[$iActionCode];
|
||||
|
||||
$bStatus = null;
|
||||
// Cache user's profiles
|
||||
if (false === array_key_exists($iUser, $this->aUsersProfilesList)) {
|
||||
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
|
||||
}
|
||||
|
||||
$aProfileList = $this->GetProfileList($oUser);
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach ($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) {
|
||||
foreach ($aProfileList as $iProfile => $oProfile) {
|
||||
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
|
||||
if (!is_null($bGrant)) {
|
||||
if ($bGrant) {
|
||||
@@ -742,7 +753,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$aRes = [
|
||||
'permission' => $iPermission,
|
||||
];
|
||||
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
|
||||
if (count($oUser->ListChanges()) === 0) {
|
||||
$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode] = $aRes;
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
@@ -824,18 +837,14 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
{
|
||||
$this->LoadCache();
|
||||
// Note: this code is VERY close to the code of IsActionAllowed()
|
||||
$iUser = $oUser->GetKey();
|
||||
|
||||
// Cache user's profiles
|
||||
if (false === array_key_exists($iUser, $this->aUsersProfilesList)) {
|
||||
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
|
||||
}
|
||||
$aProfileList = $this->GetProfileList($oUser);
|
||||
|
||||
// Note: The object set is ignored because it was interesting to optimize for huge data sets
|
||||
// and acceptable to consider only the root class of the object set
|
||||
$bStatus = null;
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach ($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) {
|
||||
foreach ($aProfileList as $iProfile => $oProfile) {
|
||||
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
|
||||
if (!is_null($bGrant)) {
|
||||
if ($bGrant) {
|
||||
@@ -893,6 +902,25 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
}
|
||||
return $bHasSharing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \User $oUser
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetProfileList(User $oUser): array
|
||||
{
|
||||
if (count($oUser->ListChanges()) === 0) { // if user is already in db and not changed
|
||||
$iUser = $oUser->GetKey();
|
||||
if (false === array_key_exists($iUser, $this->aUsersProfilesList)) {
|
||||
$aProfiles = UserRights::ListProfiles($oUser);
|
||||
$this->aUsersProfilesList[$iUser] = $aProfiles;
|
||||
}
|
||||
return $this->aUsersProfilesList[$iUser];
|
||||
}
|
||||
return UserRights::ListProfiles($oUser);
|
||||
}
|
||||
}
|
||||
|
||||
UserRights::SelectModule('UserRightsProfile');
|
||||
|
||||
@@ -30,6 +30,15 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php');
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @since 2.7.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'application/applicationextension/common/ApplicationPopupMenuItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/common/iPopupMenuExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/common/JSButtonItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/common/JSPopupMenuItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/common/SeparatorPopupMenuItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/common/URLButtonItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/common/URLPopupMenuItem.php');
|
||||
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iApplicationUIExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php');
|
||||
@@ -43,19 +52,12 @@ require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeScr
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeStyleExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iFieldRendererMappingsExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iPageUIBlockExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iPopupMenuExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iPreferencesExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/iWelcomePopupExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/AbstractApplicationUIExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/AbstractPageUIBlockExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/AbstractPreferencesExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/AbstractWelcomePopupExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/ApplicationPopupMenuItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/JSButtonItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/JSPopupMenuItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/SeparatorPopupMenuItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/URLButtonItem.php');
|
||||
require_once(APPROOT.'application/applicationextension/backoffice/URLPopupMenuItem.php');
|
||||
|
||||
require_once(APPROOT.'application/applicationextension/iBackupExtraFilesExtension.php');
|
||||
require_once(APPROOT.'application/applicationextension/iKPILoggerExtension.php');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implement this interface to add sass file (SCSS) to the backoffice pages.
|
||||
* Implement this interface to add sass files (SCSS) to the backoffice pages.
|
||||
* example: return "css/setup.scss"
|
||||
*
|
||||
* @api
|
||||
@@ -11,9 +11,9 @@
|
||||
interface iBackofficeSassExtension
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
* @see \iTopWebPage::$a_styles
|
||||
* @return array An array of relative paths (from loaded import paths) to the files to compile and include
|
||||
* @see \iTopWebPage::$a_linked_stylesheets
|
||||
* @api
|
||||
*/
|
||||
public function GetSass(): string;
|
||||
public function GetSassRelPaths(): array;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Common\Metadata\AriaAttributes;
|
||||
use Combodo\iTop\Application\UI\Base\Common\Metadata\DataAttributes;
|
||||
use Combodo\iTop\Application\UI\Base\Common\Metadata\Grouping;
|
||||
|
||||
/**
|
||||
* Base class for the various types of custom menus
|
||||
*
|
||||
@@ -7,7 +11,7 @@
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
abstract class ApplicationPopupMenuItem
|
||||
abstract class ApplicationPopupMenuItem implements iGroupingProvider, iDataAttributesProvider, iAriaAttributesProvider
|
||||
{
|
||||
/** @ignore */
|
||||
protected $sUID;
|
||||
@@ -19,6 +23,12 @@ abstract class ApplicationPopupMenuItem
|
||||
protected $sIconClass;
|
||||
/** @ignore */
|
||||
protected $aCssClasses;
|
||||
/** @since 3.3.0 */
|
||||
protected ?AriaAttributes $oAriaAttributes = null;
|
||||
/** @since 3.3.0 */
|
||||
protected ?DataAttributes $oDataAttributes = null;
|
||||
/** @since 3.3.0 */
|
||||
protected ?Grouping $oGrouping = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -58,6 +68,17 @@ abstract class ApplicationPopupMenuItem
|
||||
return $this->sLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sLabel
|
||||
*
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function SetLabel(string $sLabel): void
|
||||
{
|
||||
$this->sLabel = $sLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS classes
|
||||
*
|
||||
@@ -141,9 +162,76 @@ abstract class ApplicationPopupMenuItem
|
||||
*/
|
||||
abstract public function GetMenuItem();
|
||||
|
||||
/**
|
||||
* Get additional data, child classes can use this without having to worry about this class interfaces
|
||||
* @return array<string, mixed>
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected function GetMenuItemAdditionalData(): array
|
||||
{
|
||||
$aAdditionalData = [];
|
||||
|
||||
$aDataAttributes = $this->GetDataAttributes()->GetAttributes();
|
||||
if (!empty($aDataAttributes)) {
|
||||
$aAdditionalData['data_attributes'] = $aDataAttributes;
|
||||
}
|
||||
|
||||
$aAriaAttributes = $this->GetAriaAttributes()->GetAttributes();
|
||||
if (!empty($aAriaAttributes)) {
|
||||
$aAdditionalData['aria_attributes'] = $aAriaAttributes;
|
||||
}
|
||||
|
||||
$oGrouping = $this->GetGrouping();
|
||||
if ($oGrouping !== null) {
|
||||
$aAdditionalData['grouping_uid'] = $oGrouping->GetUID();
|
||||
}
|
||||
|
||||
return $aAdditionalData;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function GetLinkedScripts()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function GetAriaAttributes(): AriaAttributes
|
||||
{
|
||||
return $this->oAriaAttributes ??= new AriaAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function GetDataAttributes(): DataAttributes
|
||||
{
|
||||
return $this->oDataAttributes ??= new DataAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function GetGrouping(): ?Grouping
|
||||
{
|
||||
return $this->oGrouping;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Grouping|null $oGrouping
|
||||
* @return static
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function SetGrouping(?Grouping $oGrouping): static
|
||||
{
|
||||
$this->oGrouping = $oGrouping;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Class for adding an item into a popup menu that triggers some Javascript code
|
||||
*
|
||||
* Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal
|
||||
* Note: This works only in the backoffice, {@see \common\JSButtonItem} for the end-user portal
|
||||
*
|
||||
* @api
|
||||
* @package UIExtensibilityAPI
|
||||
@@ -40,7 +40,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
public function GetMenuItem()
|
||||
{
|
||||
// Note: the semicolumn is a must here!
|
||||
return [
|
||||
$aData = [
|
||||
'label' => $this->GetLabel(),
|
||||
'onclick' => $this->GetJsCode().'; return false;',
|
||||
'url' => $this->GetUrl(),
|
||||
@@ -48,6 +48,8 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
'icon_class' => $this->sIconClass,
|
||||
'tooltip' => $this->sTooltip,
|
||||
];
|
||||
|
||||
return array_merge($aData, $this->GetMenuItemAdditionalData());
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@@ -24,6 +24,12 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return ['label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses];
|
||||
$aData = [
|
||||
'label' => '<hr class="menu-separator">',
|
||||
'url' => '',
|
||||
'css_classes' => $this->aCssClasses,
|
||||
];
|
||||
|
||||
return array_merge($aData, $this->GetMenuItemAdditionalData());
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Class for adding an item into a popup menu that browses to the given URL
|
||||
*
|
||||
* Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal
|
||||
* Note: This works only in the backoffice, {@see \common\URLButtonItem} for the end-user portal
|
||||
*
|
||||
* @api
|
||||
* @package UIExtensibilityAPI
|
||||
@@ -35,13 +35,15 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return ['label' => $this->GetLabel(),
|
||||
$aData = ['label' => $this->GetLabel(),
|
||||
'url' => $this->GetUrl(),
|
||||
'target' => $this->GetTarget(),
|
||||
'css_classes' => $this->aCssClasses,
|
||||
'icon_class' => $this->sIconClass,
|
||||
'tooltip' => $this->sTooltip,
|
||||
];
|
||||
|
||||
return array_merge($aData, $this->GetMenuItemAdditionalData());
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Common\Metadata\AriaAttributes;
|
||||
|
||||
/**
|
||||
* Allows to provide ARIA attributes to classes that implement this interface.
|
||||
* The ARIA attributes will be used to improve accessibility of the UI components
|
||||
*
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
interface iAriaAttributesProvider
|
||||
{
|
||||
public function GetAriaAttributes(): AriaAttributes;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Common\Metadata\DataAttributes;
|
||||
|
||||
/**
|
||||
* Allows to provide data attributes to classes that implement this interface.
|
||||
* The data attributes will be used to provide additional information to the UI components
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
interface iDataAttributesProvider
|
||||
{
|
||||
public function GetDataAttributes(): DataAttributes;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Common\Metadata\Grouping;
|
||||
|
||||
/**
|
||||
* Allows to provide grouping information to classes that implement this interface.
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
interface iGroupingProvider
|
||||
{
|
||||
public function GetGrouping(): ?Grouping;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implemented by metadata value objects that flatten to HTML attributes.
|
||||
* Ease the implementation of classes that need to provide HTML attributes to the UI components
|
||||
*
|
||||
* @api
|
||||
* @since 3.3.0
|
||||
*/
|
||||
interface iHtmlAttributesSource
|
||||
{
|
||||
/** @return array<string, string> Attribute name => value, ready to be escaped and printed */
|
||||
public function GetHtmlAttributes(): array;
|
||||
}
|
||||
@@ -95,7 +95,24 @@ interface iPopupMenuExtension
|
||||
* @todo
|
||||
*/
|
||||
public const PORTAL_MENU_ACTIONS = 10;
|
||||
|
||||
/**
|
||||
* Get the list of items to be added to the backoffice top-bar items
|
||||
* $param is an array: [] for now, in a distant future we want to give a complete context
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public const MENU_TOPBAR_ACTIONS = 11;
|
||||
/**
|
||||
* Get the list of items to be added to the actions on a given form field (i.e. the attribute of an object)
|
||||
* $param is an array: ['object' => DBObject, att_code => attribute code , mode => edit/read]
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public const MENU_OBJDETAILS_FIELD_ACTIONS = 12;
|
||||
/**
|
||||
* Get the list of items to be added to the actions in the activity panel (read-only mode)
|
||||
* $param is an array: ['object' => DBObject, caselog_att_code => caselog attribute code or 'activity']
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public const MENU_OBJDETAILS_ACTIVITY_PANEL_ACTIONS = 13;
|
||||
/**
|
||||
* Get the list of items to be added to a menu.
|
||||
*
|
||||
@@ -49,6 +49,7 @@ use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
|
||||
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
|
||||
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
|
||||
use Combodo\iTop\Service\Links\LinkSetModel;
|
||||
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
|
||||
@@ -1046,7 +1047,7 @@ HTML
|
||||
// Add extra data for markup generation
|
||||
// - Attribute code and AttributeDef. class
|
||||
$val['attcode'] = $sAttCode;
|
||||
$val['atttype'] = $oAttDef->GetType();
|
||||
$val['atttype'] = $oAttDef->GetTypeShortClassName();
|
||||
$val['attlabel'] = $sAttLabel;
|
||||
$val['attflags'] = ($bEditMode) ? $this->GetFormAttributeFlags($sAttCode) : OPT_ATT_READONLY;
|
||||
|
||||
@@ -1065,6 +1066,14 @@ HTML
|
||||
}
|
||||
}
|
||||
$val['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : '';
|
||||
$val['actions'] = [];
|
||||
|
||||
/** @var \iPopupMenuExtension $oExtensionInstance */
|
||||
foreach (InterfaceDiscovery::GetInstance()->FindItopClasses('iPopupMenuExtension') as $oExtensionInstance) {
|
||||
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_OBJDETAILS_FIELD_ACTIONS, ['object' => $this, 'att_code' => $sAttCode, 'mode' => $bEditMode ? 'edit' : 'read']) as $oMenuItem) {
|
||||
$val['actions'][] = $oMenuItem;
|
||||
}
|
||||
}
|
||||
|
||||
// The field is visible, add it to the current column
|
||||
$oField = FieldUIBlockFactory::MakeFromParams($val);
|
||||
@@ -1535,190 +1544,6 @@ HTML
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param \CMDBObjectSet $oSet
|
||||
* @param array $aParams
|
||||
*
|
||||
* @throws \Exception
|
||||
* only used in old and deprecated export.php
|
||||
*
|
||||
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
|
||||
*/
|
||||
public static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = [])
|
||||
{
|
||||
$oPage->add(self::GetSetAsHTMLSpreadsheet($oSet, $aParams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Spreadsheet output: designed for end users doing some reporting
|
||||
* Then the ids are excluded and replaced by the corresponding friendlyname
|
||||
*
|
||||
* @param \DBObjectSet $oSet
|
||||
* @param array $aParams
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \Exception
|
||||
*
|
||||
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
|
||||
*/
|
||||
public static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = [])
|
||||
{
|
||||
$aFields = null;
|
||||
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0)) {
|
||||
$aFields = explode(',', $aParams['fields']);
|
||||
}
|
||||
|
||||
$bFieldsAdvanced = false;
|
||||
if (isset($aParams['fields_advanced'])) {
|
||||
$bFieldsAdvanced = (bool)$aParams['fields_advanced'];
|
||||
}
|
||||
|
||||
$bLocalize = true;
|
||||
if (isset($aParams['localize_values'])) {
|
||||
$bLocalize = (bool)$aParams['localize_values'];
|
||||
}
|
||||
|
||||
$aList = [];
|
||||
|
||||
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
|
||||
$aAuthorizedClasses = [];
|
||||
foreach ($aClasses as $sAlias => $sClassName) {
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
$aHeader = [];
|
||||
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
|
||||
$aList[$sAlias] = [];
|
||||
|
||||
foreach (MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) {
|
||||
if (is_null($aFields) || (count($aFields) == 0)) {
|
||||
// Standard list of attributes (no link sets)
|
||||
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) {
|
||||
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
|
||||
|
||||
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
|
||||
|
||||
if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) {
|
||||
$sRemoteClass = $oAttDef->GetTargetClass();
|
||||
foreach (MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) {
|
||||
$aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef(
|
||||
$sRemoteClass,
|
||||
$sRemoteAttCode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User defined list of attributes
|
||||
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) {
|
||||
$aList[$sAlias][$sAttCode] = $oAttDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Replace external key by the corresponding friendly name (if not already in the list)
|
||||
foreach ($aList[$sAlias] as $sAttCode => $oAttDef) {
|
||||
if ($oAttDef->IsExternalKey()) {
|
||||
unset($aList[$sAlias][$sAttCode]);
|
||||
$sFriendlyNameAttCode = $sAttCode.'_friendlyname';
|
||||
if (!array_key_exists(
|
||||
$sFriendlyNameAttCode,
|
||||
$aList[$sAlias]
|
||||
) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) {
|
||||
$oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode);
|
||||
$aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
|
||||
$sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx;
|
||||
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime') {
|
||||
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')';
|
||||
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')';
|
||||
} else {
|
||||
$aHeader[] = $sColLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sHtml = "<table border=\"1\">\n";
|
||||
$sHtml .= "<tr>\n";
|
||||
$sHtml .= "<td>".implode("</td><td>", $aHeader)."</td>\n";
|
||||
$sHtml .= "</tr>\n";
|
||||
$oSet->Seek(0);
|
||||
while ($aObjects = $oSet->FetchAssoc()) {
|
||||
$aRow = [];
|
||||
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
|
||||
$oObj = $aObjects[$sAlias];
|
||||
foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
|
||||
if (is_null($oObj)) {
|
||||
$aRow[] = '<td></td>';
|
||||
} else {
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime') {
|
||||
$sDate = $oObj->Get($sAttCodeEx);
|
||||
if ($sDate === null) {
|
||||
$aRow[] = '<td></td>';
|
||||
$aRow[] = '<td></td>';
|
||||
} else {
|
||||
$iDate = AttributeDateTime::GetAsUnixSeconds($sDate);
|
||||
$aRow[] = '<td>'.date(
|
||||
'Y-m-d',
|
||||
$iDate
|
||||
).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
|
||||
$aRow[] = '<td>'.date(
|
||||
'H:i:s',
|
||||
$iDate
|
||||
).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
|
||||
}
|
||||
} else {
|
||||
if ($oAttDef instanceof AttributeCaseLog) {
|
||||
$rawValue = $oObj->Get($sAttCodeEx);
|
||||
$outputValue = str_replace(
|
||||
"\n",
|
||||
"<br/>",
|
||||
utils::EscapeHtml($rawValue->__toString())
|
||||
);
|
||||
// Trick for Excel: treat the content as text even if it begins with an equal sign
|
||||
$aRow[] = '<td x:str>'.$outputValue.'</td>';
|
||||
} else {
|
||||
$rawValue = $oObj->Get($sAttCodeEx);
|
||||
// Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings
|
||||
// let's fix this and make sure we render an empty string if the key == 0
|
||||
if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) {
|
||||
$sKeyAttCode = $oAttDef->GetKeyAttCode();
|
||||
if ($oObj->Get($sKeyAttCode) == 0) {
|
||||
$rawValue = '';
|
||||
}
|
||||
}
|
||||
if ($bLocalize) {
|
||||
$outputValue = utils::EscapeHtml($oFinalAttDef->GetEditValue($rawValue));
|
||||
} else {
|
||||
$outputValue = utils::EscapeHtml($rawValue);
|
||||
}
|
||||
$aRow[] = '<td>'.$outputValue.'</td>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$sHtml .= implode("\n", $aRow);
|
||||
$sHtml .= "</tr>\n";
|
||||
}
|
||||
$sHtml .= "</table>\n";
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param \CMDBObjectSet $oSet
|
||||
@@ -3250,8 +3075,12 @@ EOF
|
||||
}
|
||||
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
|
||||
if (is_array($aAllowedValues) && count($aAllowedValues) == 1) {
|
||||
$aValues = array_keys($aAllowedValues);
|
||||
$this->Set($sAttCode, $aValues[0]);
|
||||
// linkset are ignored
|
||||
// N°9622 - Error in the user story life cycle
|
||||
if (!$oAttDef->IsLinkSet()) {
|
||||
$aValues = array_keys($aAllowedValues);
|
||||
$this->Set($sAttCode, $aValues[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4167,6 +3996,27 @@ HTML;
|
||||
$sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
|
||||
if ($sTagSetJson !== null) { // bulk modify, direct linked set not handled
|
||||
$value = json_decode($sTagSetJson, true);
|
||||
if ($this->IsNew()) {
|
||||
if (is_array($value['orig_value'])) {
|
||||
foreach ($value['orig_value'] as $val) {
|
||||
if (!in_array($val, $value['removed'])) {
|
||||
$value['added'][] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$aCurrentValues = $this->Get($sAttCode)->GetValues();
|
||||
foreach ($value['orig_value'] as $val) {
|
||||
if (!in_array($val, $aCurrentValues) && !in_array($val, $value['removed']) && !in_array($val, $value['added'])) {
|
||||
$value['added'][] = $val;
|
||||
}
|
||||
}
|
||||
foreach ($aCurrentValues as $val) {
|
||||
if (!in_array($val, $value['orig_value']) && !in_array($val, $value['removed']) && !in_array($val, $value['added'])) {
|
||||
$value['removed'][] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4507,7 +4357,7 @@ HTML;
|
||||
|
||||
$oDivField = FieldUIBlockFactory::MakeLarge("");
|
||||
// UIContentBlockUIBlockFactory::MakeStandard(null,["field_container field_large"]);
|
||||
$oDivField->AddDataAttribute("attribute-type", $oAttDef->GetType());
|
||||
$oDivField->AddDataAttribute("attribute-type", $oAttDef->GetTypeShortClassName());
|
||||
$oDivField->AddDataAttribute("attribute-label", $sAttMetaDataLabel);
|
||||
$oDivField->AddDataAttribute("attribute-flag-hidden", false);
|
||||
$oDivField->AddDataAttribute("attribute-flag-read-only", false);
|
||||
@@ -4810,7 +4660,7 @@ EOF
|
||||
// Not in preview mode, do the update for real
|
||||
$sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id');
|
||||
if (!utils::IsTransactionValid($sTransactionId, false)) {
|
||||
throw new Exception(Dict::S('UI:Error:ObjectAlreadyUpdated'));
|
||||
throw new Exception(Dict::S('UI:Error:InvalidToken'));
|
||||
}
|
||||
utils::RemoveTransaction($sTransactionId);
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ EOF
|
||||
*/
|
||||
public function Render($oPage, $bEditMode = false, $aExtraParams = [], $bCanEdit = true)
|
||||
{
|
||||
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? $this->GetId(), $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
|
||||
/** @var \DashboardLayoutMultiCol $oLayout */
|
||||
$oLayout = new $this->sLayoutClass();
|
||||
@@ -586,7 +586,7 @@ JS
|
||||
/** @var \Dashlet $oDashlet */
|
||||
foreach ($aCell as $oDashlet) {
|
||||
if ($oDashlet->IsVisible()) {
|
||||
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" style="display:none">');
|
||||
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" data-role="ibo-dashlet-properties" data-dashlet-class="'.$oDashlet->GetDashletType().'" style="display:none">');
|
||||
$oForm = $oDashlet->GetForm();
|
||||
$this->SetFormParams($oForm, $aExtraParams);
|
||||
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
|
||||
@@ -1203,7 +1203,7 @@ EOF
|
||||
}
|
||||
$aRenderParams['dashboard_div_id'] = $aExtraParams['dashboard_div_id'];
|
||||
$sJSExtraParams = json_encode($aExtraParams);
|
||||
$oPage->add('<div id="dashboard_editor" class="ibo-dashboard-editor" data-role="ibo-dashboard-editor">');
|
||||
$oPage->add('<div id="dashboard_editor" class="ibo-dashboard-editor" data-role="ibo-dashboard-editor" data-dashboard-id="'.$this->sId.'">');
|
||||
$oPage->add('<div class="ui-layout-center">');
|
||||
$this->SetCustomFlag(true);
|
||||
$this->Render($oPage, true, $aRenderParams);
|
||||
|
||||
@@ -25,6 +25,20 @@ use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
use Combodo\iTop\Application\UI\Base\UIBlock;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeDateTime;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeDecimal;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeDuration;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeEncryptedString;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeEnum;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeExternalField;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeFriendlyName;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeInteger;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeLinkedSet;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeOneWayPassword;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributePassword;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributePercentage;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeSubItem;
|
||||
use Combodo\iTop\Core\AttributeDefinition\iAttributeNoGroupBy;
|
||||
|
||||
require_once(APPROOT.'application/forms.class.inc.php');
|
||||
|
||||
@@ -440,7 +454,7 @@ EOF
|
||||
// For external fields, find the real type of the target
|
||||
$sExtFieldAttCode = $sAttCode;
|
||||
$sTargetClass = $sClass;
|
||||
while (is_a($sAttType, 'AttributeExternalField', true)) {
|
||||
while (is_a($sAttType, AttributeExternalField::class, true)) {
|
||||
$sExtKeyAttCode = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtFieldAttCode, 'extkey_attcode');
|
||||
$sTargetAttCode = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtFieldAttCode, 'target_attcode');
|
||||
$sTargetClass = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtKeyAttCode, 'targetclass');
|
||||
@@ -450,13 +464,13 @@ EOF
|
||||
}
|
||||
|
||||
$aForbidenAttType = [
|
||||
'AttributeLinkedSet',
|
||||
'AttributeFriendlyName',
|
||||
AttributeLinkedSet::class,
|
||||
AttributeFriendlyName::class,
|
||||
|
||||
'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
|
||||
'AttributeOneWayPassword',
|
||||
'AttributeEncryptedString',
|
||||
'AttributePassword',
|
||||
iAttributeNoGroupBy::class, //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
|
||||
AttributeOneWayPassword::class,
|
||||
AttributeEncryptedString::class,
|
||||
AttributePassword::class,
|
||||
];
|
||||
foreach ($aForbidenAttType as $sForbidenAttType) {
|
||||
if (is_a($sAttType, $sForbidenAttType, true)) {
|
||||
@@ -468,7 +482,7 @@ EOF
|
||||
if (!in_array($sLabel, $aGroupBy)) {
|
||||
$aGroupBy[$sAttCode] = $sLabel;
|
||||
|
||||
if (is_a($sAttType, 'AttributeDateTime', true)) {
|
||||
if (is_a($sAttType, AttributeDateTime::class, true)) {
|
||||
$aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
|
||||
$aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel);
|
||||
$aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel);
|
||||
@@ -1102,7 +1116,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$aAttributeTypes = $this->oModelReflection->ListAttributes($this->sClass);
|
||||
if (isset($aAttributeTypes[$this->sGroupByAttCode])) {
|
||||
$sAttributeType = $aAttributeTypes[$this->sGroupByAttCode];
|
||||
if (is_subclass_of($sAttributeType, 'AttributeDateTime') || $sAttributeType == 'AttributeDateTime') {
|
||||
if (is_subclass_of($sAttributeType, AttributeDateTime::class) || $sAttributeType == AttributeDateTime::class) {
|
||||
$this->sOrderDirection = 'asc';
|
||||
} else {
|
||||
$this->sOrderDirection = 'desc';
|
||||
@@ -1258,7 +1272,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
if ($this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode)) {
|
||||
$aAttributeTypes = $this->oModelReflection->ListAttributes($sClass);
|
||||
$sAttributeType = $aAttributeTypes[$this->sGroupByAttCode];
|
||||
if (is_subclass_of($sAttributeType, 'AttributeDateTime') || $sAttributeType == 'AttributeDateTime') {
|
||||
if (is_subclass_of($sAttributeType, AttributeDateTime::class) || $sAttributeType == AttributeDateTime::class) {
|
||||
// Note: an alternative to this somewhat hardcoded way of doing things would be to implement...
|
||||
//$oExpr = Expression::FromOQL($this->sGroupByExpr);
|
||||
//$aTranslationData = array($oQuery->GetClassAlias() => array($this->sGroupByAttCode => new ScalarExpression(date('Y-m-d H:i:s', $iTime))));
|
||||
@@ -1289,7 +1303,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
foreach ($aValues as $sValue) {
|
||||
$aDisplayValues[] = ['label' => $sValue, 'value' => (int)rand(1, 15)];
|
||||
}
|
||||
} elseif (is_subclass_of($sAttributeType, 'AttributeEnum') || $sAttributeType == 'AttributeEnum') {
|
||||
} elseif (is_subclass_of($sAttributeType, AttributeEnum::class) || $sAttributeType == AttributeEnum::class) {
|
||||
$aAllowed = $this->oModelReflection->GetAllowedValues_att($sClass, $this->sGroupByAttCode);
|
||||
if ($aAllowed) { // null for non enums
|
||||
foreach ($aAllowed as $sValue => $sValueLabel) {
|
||||
@@ -1458,11 +1472,11 @@ abstract class DashletGroupBy extends Dashlet
|
||||
}
|
||||
foreach ($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType) {
|
||||
switch ($sAttType) {
|
||||
case 'AttributeDecimal':
|
||||
case 'AttributeDuration':
|
||||
case 'AttributeInteger':
|
||||
case 'AttributePercentage':
|
||||
case 'AttributeSubItem': // TODO: Known limitation: no unit displayed (values in sec)
|
||||
case AttributeDecimal::class:
|
||||
case AttributeDuration::class:
|
||||
case AttributeInteger::class:
|
||||
case AttributePercentage::class:
|
||||
case AttributeSubItem::class: // TODO: Known limitation: no unit displayed (values in sec)
|
||||
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
|
||||
$aFunctionAttributes[$sAttCode] = $sLabel;
|
||||
break;
|
||||
|
||||
@@ -1707,7 +1707,7 @@ JS
|
||||
$oBlock->bAdvancedMode = utils::ReadParam('advanced', false);
|
||||
|
||||
$oBlock->sCsvFile = strtolower($this->m_oFilter->GetClass()).'.csv';
|
||||
$oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile);
|
||||
$oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile);
|
||||
$oBlock->sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search'.$oAppContext->GetForLink(true).'&filter='.rawurlencode($this->m_oFilter->serialize()).'&format=csv';
|
||||
// Pass the parameters via POST, since expression may be very long
|
||||
$aParamsToPost = [
|
||||
@@ -1724,7 +1724,7 @@ JS
|
||||
$oBlock->sLinkToToggle = $oBlock->sLinkToToggle.'&advanced=1';
|
||||
$oBlock->sChecked = '';
|
||||
}
|
||||
$oBlock->sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
|
||||
$oBlock->sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php';
|
||||
|
||||
$oBlock->sCharsetNotice = false;
|
||||
$oBlock->sJsonParams = json_encode($aParamsToPost);
|
||||
@@ -2292,6 +2292,17 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
// ResourceId should not be sanitized
|
||||
$oActionButton->AddDataAttribute('resource-id', $sActionId);
|
||||
if (isset($aAction['data_attributes']) && is_array($aAction['data_attributes'])) {
|
||||
foreach ($aAction['data_attributes'] as $sName => $sValue) {
|
||||
$sDataAttributeName = $sName;
|
||||
$oActionButton->AddDataAttribute($sDataAttributeName, (string) $sValue);
|
||||
}
|
||||
}
|
||||
if (isset($aAction['aria_attributes']) && is_array($aAction['aria_attributes'])) {
|
||||
foreach ($aAction['aria_attributes'] as $sName => $sValue) {
|
||||
$oActionButton->AddAriaAttribute((string) $sName, (string) $sValue);
|
||||
}
|
||||
}
|
||||
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
|
||||
if (empty($sLabel)) {
|
||||
if (empty($aAction['tooltip'])) {
|
||||
|
||||
@@ -220,10 +220,19 @@ class DesignerForm
|
||||
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><div class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-undo"></i></div></span></td>'
|
||||
.$this->EndRow();
|
||||
|
||||
$aPropertyFieldData = [
|
||||
'code' => $oField->GetCode(),
|
||||
'type' => $oField->GetInputType(),
|
||||
'label' => $aRow['label'],
|
||||
'raw-value' => $oField->GetRawValue(),
|
||||
];
|
||||
if (is_null($aRow['label'])) {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value" colspan="2">'.$aRow['value'];
|
||||
$sReturn .= $this->StartRow($sFieldId, $aPropertyFieldData).
|
||||
'<td class="prop_value" data-role="ibo-property-field-value" colspan="2">'.$aRow['value'];
|
||||
} else {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
|
||||
$sReturn .= $this->StartRow($sFieldId, $aPropertyFieldData).
|
||||
'<td class="prop_label" data-role="ibo-property-field-label">'.$aRow['label'].
|
||||
'</td><td class="prop_value" data-role="ibo-property-field-value">'.$aRow['value'];
|
||||
}
|
||||
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField)) {
|
||||
$sReturn .= $sValidationFields;
|
||||
@@ -294,12 +303,21 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
public function StartRow($sFieldId = null)
|
||||
public function StartRow($sFieldId = null, $aPropertyFieldData = [])
|
||||
{
|
||||
if ($sFieldId != null) {
|
||||
return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
|
||||
$sPropertyFieldAttributes = '';
|
||||
foreach ($aPropertyFieldData as $sAttribute => $value) {
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value);
|
||||
} elseif (is_null($value)) {
|
||||
$value = '';
|
||||
}
|
||||
$sPropertyFieldAttributes .= ' data-property-field-'.$sAttribute.'="'.utils::EscapeHtml((string)$value).'"';
|
||||
}
|
||||
return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
|
||||
if ($sFieldId != null) {
|
||||
return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'"'.$sPropertyFieldAttributes.'>';
|
||||
}
|
||||
return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'"'.$sPropertyFieldAttributes.'>';
|
||||
}
|
||||
|
||||
public function EndRow()
|
||||
@@ -679,6 +697,14 @@ class DesignerFormField
|
||||
return $this->sCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetRawValue()
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DesignerForm $oForm
|
||||
*/
|
||||
@@ -1629,6 +1655,11 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
return 'selector_property_field';
|
||||
}
|
||||
|
||||
public function GetRawValue()
|
||||
{
|
||||
return $this->defaultRealValue;
|
||||
}
|
||||
|
||||
public function AddSubForm($oSubForm, $sLabel, $sValue)
|
||||
{
|
||||
$this->aSubForms[] = ['form' => $oSubForm, 'label' => $sLabel, 'value' => $sValue];
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Application\WebPage\ErrorPage;
|
||||
use Combodo\iTop\Application\WebPage\NiceWebPage;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\Session\Session;
|
||||
|
||||
/**
|
||||
* Web page used for displaying the login form
|
||||
@@ -369,16 +369,7 @@ class LoginWebPage extends NiceWebPage
|
||||
public static function ResetSession()
|
||||
{
|
||||
// Unset all of the session variables.
|
||||
Session::Unset('auth_user');
|
||||
Session::Unset('login_state');
|
||||
Session::Unset('can_logoff');
|
||||
Session::Unset('archive_mode');
|
||||
Session::Unset('impersonate_user');
|
||||
Session::Unset('PluginProperties');
|
||||
Session::Unset('UrlMakerClass');
|
||||
Session::Unset('itop_env');
|
||||
Session::Unset('obj_messages');
|
||||
Session::Unset('profile_list');
|
||||
Session::UnsetAll();
|
||||
UserRights::_ResetSessionCache();
|
||||
// If it's desired to kill the session, also delete the session cookie.
|
||||
// Note: This will destroy the session, and not just the session data!
|
||||
|
||||
@@ -113,17 +113,43 @@ abstract class Query extends cmdbAbstractObject
|
||||
]
|
||||
));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey(
|
||||
"owner_id",
|
||||
[
|
||||
"targetclass" => 'Contact',
|
||||
"allowed_values" => null,
|
||||
"sql" => 'owner_id',
|
||||
"is_null_allowed" => true,
|
||||
"depends_on" => [],
|
||||
"display_style" => 'select',
|
||||
"always_load_in_tables" => false,
|
||||
"on_target_delete" => DEL_AUTO,
|
||||
]
|
||||
));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeEnumSet(
|
||||
"usages",
|
||||
[
|
||||
"allowed_values" => null,
|
||||
"possible_values" => new ValueSetEnumPadded("export,reference,notif,draft,dashlet"),
|
||||
"sql" => "usages",
|
||||
"depends_on" => [],
|
||||
"is_null_allowed" => true,
|
||||
"max_items" => 12,
|
||||
]
|
||||
));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems(
|
||||
'details',
|
||||
['name', 'is_template', 'description']
|
||||
['name', 'is_template', 'owner_id', 'usages', 'description']
|
||||
); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['description']); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('list', ['description', 'usages']); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', ['name', 'description', 'is_template']); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems(
|
||||
'default_search',
|
||||
['name', 'description', 'is_template']
|
||||
['name', 'description', 'is_template', 'owner_id', 'usages']
|
||||
); // Criteria of the default search form
|
||||
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
}
|
||||
@@ -143,6 +169,15 @@ abstract class Query extends cmdbAbstractObject
|
||||
return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
|
||||
}
|
||||
|
||||
public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = [])
|
||||
{
|
||||
// read only attribute
|
||||
if (in_array($sAttCode, ['export_count', 'export_last_date', 'export_last_user_id'])) {
|
||||
return OPT_ATT_READONLY;
|
||||
}
|
||||
return parent::GetInitialStateAttributeFlags($sAttCode, $aReasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return export url.
|
||||
*
|
||||
@@ -214,15 +249,15 @@ class QueryOQL extends Query
|
||||
MetaModel::Init_SetZListItems(
|
||||
'details',
|
||||
[
|
||||
'col:col1' => ['fieldset:Query:baseinfo' => ['name', 'is_template', 'description', 'oql', 'fields']],
|
||||
'col:col1' => ['fieldset:Query:baseinfo' => ['name', 'is_template', 'owner_id', 'usages', 'description', 'oql', 'fields']],
|
||||
'col:col2' => ['fieldset:Query:exportInfo' => ['export_count', 'export_last_date', 'export_last_user_id', 'export_last_user_contact']],
|
||||
]
|
||||
); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['description']); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('list', ['description', 'usages']); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems(
|
||||
'standard_search',
|
||||
['name', 'description', 'is_template', 'fields', 'oql']
|
||||
['name', 'description', 'is_template', 'owner_id', 'usages', 'oql']
|
||||
); // Criteria of the std search form
|
||||
}
|
||||
|
||||
|
||||
@@ -109,10 +109,18 @@ $('#shortcut_rename_dlg').dialog({
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
{ text: "$sOkButtonLabel", click: ShortcutRenameOK},
|
||||
{ text: "$sCancelButtonLabel", click: function() {
|
||||
$(this).dialog( "close" ); $(this).remove();
|
||||
} },
|
||||
{
|
||||
text: "$sCancelButtonLabel",
|
||||
click: function() {
|
||||
$(this).dialog( "close" ); $(this).remove();
|
||||
},
|
||||
'class': 'ibo-button ibo-is-alternative ibo-is-neutral action cancel'
|
||||
},
|
||||
{
|
||||
text: "$sOkButtonLabel",
|
||||
click: ShortcutRenameOK,
|
||||
'class': 'ibo-is-regular ibo-is-primary'
|
||||
},
|
||||
],
|
||||
close: function() { $(this).remove(); }
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ class privUITransactionSession
|
||||
// Strictly speaking, the two lines below should be grouped together
|
||||
// by a critical section
|
||||
// sem_acquire($rSemIdentified);
|
||||
$id = static::GetUserPrefix().str_replace(['.', ' '], '', microtime());
|
||||
$id = static::GetUserPrefix().str_replace(['.', ' '], '', microtime()).'-'.bin2hex(random_bytes(24));
|
||||
Session::Set(['transactions', $id], true);
|
||||
// sem_release($rSemIdentified);
|
||||
|
||||
@@ -236,7 +236,7 @@ class privUITransactionFile
|
||||
|
||||
self::CleanupOldTransactions();
|
||||
|
||||
$sTransactionIdFullPath = tempnam(utils::GetDataPath().'transactions', static::GetUserPrefix());
|
||||
$sTransactionIdFullPath = static::CreateUniqueTransactionFilePath(utils::GetDataPath().'transactions', static::GetUserPrefix());
|
||||
file_put_contents($sTransactionIdFullPath, $iCurrentUserId, LOCK_EX);
|
||||
|
||||
$sTransactionIdFileName = basename($sTransactionIdFullPath);
|
||||
@@ -245,6 +245,33 @@ class privUITransactionFile
|
||||
return $sTransactionIdFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction file path with a longer random suffix and create it atomically.
|
||||
*
|
||||
* @param string $sDir
|
||||
* @param string $sPrefix
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws CoreException
|
||||
*/
|
||||
private static function CreateUniqueTransactionFilePath(string $sDir, string $sPrefix): string
|
||||
{
|
||||
$iMaxAttempts = 10;
|
||||
for ($i = 0; $i < $iMaxAttempts; $i++) {
|
||||
$sFileName = $sPrefix.bin2hex(random_bytes(24)); // 48 random hex chars
|
||||
$sFullPath = $sDir.'/'.$sFileName;
|
||||
|
||||
$hFile = @fopen($sFullPath, 'x');
|
||||
if ($hFile !== false) {
|
||||
fclose($hFile);
|
||||
return $sFullPath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CoreException('Failed to allocate a unique transaction file name after multiple attempts.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
|
||||
* the session so that another call to IsTransactionValid for the same transaction id
|
||||
@@ -281,6 +308,18 @@ class privUITransactionFile
|
||||
return false;
|
||||
}
|
||||
|
||||
$iTime = filemtime($sFilepath);
|
||||
$iLifetime = (int) MetaModel::GetConfig()->Get('transactions_file_lifetime');
|
||||
$iLimit = time() - $iLifetime;
|
||||
if ($iTime < $iLimit) {
|
||||
self::Info("IsTransactionValid: Transaction '$id' expired.");
|
||||
if ($iLifetime < 3600) {
|
||||
IssueLog::Warning("IsTransactionValid: Transaction '$id' expired after only $iLifetime seconds. Consider increasing the 'transactions_file_lifetime' configuration parameter.");
|
||||
}
|
||||
@unlink($sFilepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($bRemoveTransaction) {
|
||||
$bResult = @unlink($sFilepath);
|
||||
if (!$bResult) {
|
||||
@@ -314,7 +353,7 @@ class privUITransactionFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old transactions which have been pending since more than 24 hours
|
||||
* Cleanup old transactions which have been pending since more than the lifetime defined in the configuration parameter 'transactions_file_lifetime'.
|
||||
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
|
||||
*/
|
||||
protected static function CleanupOldTransactions($sTransactionDir = null)
|
||||
@@ -327,7 +366,7 @@ class privUITransactionFile
|
||||
}
|
||||
|
||||
clearstatcache();
|
||||
$iLimit = time() - 24 * 3600;
|
||||
$iLimit = time() - (int) MetaModel::GetConfig()->Get('transactions_file_lifetime');
|
||||
$sPattern = $sTransactionDir ? "$sTransactionDir/*" : utils::GetDataPath().'transactions/*';
|
||||
$aTransactions = glob($sPattern);
|
||||
foreach ($aTransactions as $sFileName) {
|
||||
|
||||
@@ -1833,10 +1833,15 @@ SQL;
|
||||
/**
|
||||
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
|
||||
* @param string $sId The ID to sanitize
|
||||
* @param bool $bForSelector Whether to apply strict CSS/jQuery selector-safe sanitization
|
||||
* @return string The sanitized ID
|
||||
*/
|
||||
public static function GetSafeId($sId)
|
||||
public static function GetSafeId($sId, bool $bForSelector = false)
|
||||
{
|
||||
if ($bForSelector) {
|
||||
return preg_replace('/[^a-zA-Z0-9_]/', '_', $sId);
|
||||
}
|
||||
|
||||
return str_replace([':', '[', ']', '+', '-', ' '], '_', $sId);
|
||||
}
|
||||
|
||||
@@ -1886,7 +1891,7 @@ SQL;
|
||||
CURLOPT_HEADER => false, // don't return the headers in the output
|
||||
CURLOPT_FOLLOWLOCATION => true, // follow redirects
|
||||
CURLOPT_ENCODING => "", // handle all encodings
|
||||
CURLOPT_USERAGENT => "spider", // who am i
|
||||
CURLOPT_USERAGENT => static::GetConfig()->Get('http.request.user_agent'), // who am i
|
||||
CURLOPT_AUTOREFERER => true, // set referer on redirect
|
||||
CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
|
||||
CURLOPT_TIMEOUT => 120, // timeout on response
|
||||
@@ -3022,7 +3027,6 @@ TXT
|
||||
* Note: Only works for backoffice URLs for now
|
||||
*
|
||||
* @param string $sText Text containing the mentioned objects to be found
|
||||
* @param string $sFormat {@uses static::ENUM_TEXT_FORMAT_HTML, ...}
|
||||
*
|
||||
* @return array Array of object classes / IDs for the ones found in $sText
|
||||
*
|
||||
@@ -3037,13 +3041,45 @@ TXT
|
||||
public static function GetMentionedObjectsFromText(string $sText): array
|
||||
{
|
||||
$aMentionedObjects = [];
|
||||
$aMentionMatches = [];
|
||||
$sText = html_entity_decode($sText);
|
||||
$aMentionAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes');
|
||||
$oDom = new \DOMDocument();
|
||||
$bPreviousUseInternalErrors = libxml_use_internal_errors(true); // to keep processing even in case of "invalid" HTML, cf. testGetMentionedObjectsFromText
|
||||
|
||||
preg_match_all('/<a\s*([^>]*)data-object-class="([^"]*)"\s.*data-object-key="([^"]*)"/Ui', $sText, $aMentionMatches);
|
||||
foreach ($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch) {
|
||||
$sMatchedClass = $aMentionMatches[2][$iMatchIdx];
|
||||
$sMatchedId = $aMentionMatches[3][$iMatchIdx];
|
||||
try {
|
||||
$oDom->loadHTML('<?xml encoding="UTF-8">'.$sText);
|
||||
} finally {
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($bPreviousUseInternalErrors);
|
||||
}
|
||||
$oXpath = new \DOMXPath($oDom);
|
||||
$oNodes = $oXpath->query('//a[@data-object-class and @data-object-key]');
|
||||
|
||||
foreach ($oNodes as $oObjNode) {
|
||||
$sMatchedClass = $oObjNode->getAttribute('data-object-class');
|
||||
$sMatchedId = $oObjNode->getAttribute('data-object-key');
|
||||
$sMatchedName = trim($oObjNode->textContent);
|
||||
|
||||
// Ensure that what we found is actually configured as a mention
|
||||
$sMentionPrefix = array_search($sMatchedClass, $aMentionAllowedClasses);
|
||||
// - No direct configuration for the matched class, let's look for a configuration on parent classes
|
||||
if (false === $sMentionPrefix) {
|
||||
$bHasMentionConfigurationForParentClass = false;
|
||||
foreach (MetaModel::EnumParentClasses($sMatchedClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $aMatchedParentClass) {
|
||||
$sMentionPrefix = array_search($aMatchedParentClass, $aMentionAllowedClasses);
|
||||
if (false !== $sMentionPrefix) {
|
||||
$bHasMentionConfigurationForParentClass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $bHasMentionConfigurationForParentClass) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// - Test if the name starts with $sMentionPrefix (e.g. '@' for 'Contact' class)
|
||||
if (false === str_starts_with($sMatchedName, $sMentionPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepare array for matched class if not already present
|
||||
if (!array_key_exists($sMatchedClass, $aMentionedObjects)) {
|
||||
@@ -3218,4 +3254,48 @@ TXT
|
||||
return (int)$sLimit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.3.0 N°9875
|
||||
* Open archive and raise appropriate exception.
|
||||
* Warning: do not forget to close archive afterwhile
|
||||
* @param string $sArchiveFilePath
|
||||
* @param int|null $flags
|
||||
* @return ZipArchive
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function ZipArchiveOpen(string $sArchiveFilePath, int|null $flags = null): ZipArchive
|
||||
{
|
||||
$oZip = new ZipArchive();
|
||||
if (is_null($flags)) {
|
||||
$code = $oZip->open($sArchiveFilePath);
|
||||
} else {
|
||||
$code = $oZip->open($sArchiveFilePath, $flags);
|
||||
}
|
||||
if (true !== $code) {
|
||||
//ZipArchive::ZIP_ER_NOZIP : 19
|
||||
if ($code === 19) {
|
||||
throw new \Exception(sprintf('Cannot open zip file due to inconsistent or empty content'));
|
||||
}
|
||||
|
||||
throw new \Exception(sprintf('Cannot open zip file due to error code %s', $code));
|
||||
}
|
||||
return $oZip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.3.0 N°9875
|
||||
* @param string $sDirectory
|
||||
* @param string $sPrefix
|
||||
* @return ZipArchive
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function ZipArchiveCreateWithTempNam(string $sDirectory, string $sPrefix): ZipArchive
|
||||
{
|
||||
$sTempnam = tempnam($sDirectory, $sPrefix);
|
||||
unlink($sTempnam);
|
||||
$sArchiveName = $sTempnam.'.zip';
|
||||
|
||||
return self::ZipArchiveOpen($sArchiveName, ZipArchive::CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ define('ITOP_DESIGN_LATEST_VERSION', '3.3');
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '3.3.0');
|
||||
define('ITOP_CORE_VERSION', '3.3.1');
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
||||
14
composer.lock
generated
14
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1cca58a0e26794fb9c546a001f7f7de8",
|
||||
"content-hash": "24e7507794b971955358df5f2b143ac4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "apereo/phpcas",
|
||||
@@ -5570,16 +5570,16 @@
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.24.0",
|
||||
"version": "v3.28.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "a6769aefb305efef849dc25c9fd1653358c148f0"
|
||||
"reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a6769aefb305efef849dc25c9fd1653358c148f0",
|
||||
"reference": "a6769aefb305efef849dc25c9fd1653358c148f0",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b",
|
||||
"reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5634,7 +5634,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.24.0"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.28.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5646,7 +5646,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-17T21:31:11+00:00"
|
||||
"time": "2026-07-03T20:44:34+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
||||
@@ -916,7 +916,9 @@ class CMDBSource
|
||||
$aColumn = [];
|
||||
$aData = self::QueryToArray($sSql);
|
||||
foreach ($aData as $aRow) {
|
||||
@$aColumn[] = $aRow[$col];
|
||||
if ($aRow[$col] !== null) {
|
||||
$aColumn[] = $aRow[$col];
|
||||
}
|
||||
}
|
||||
return $aColumn;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
*
|
||||
* @see ITOP_CORE_VERSION to get iTop core version
|
||||
*/
|
||||
define('ITOP_VERSION', '3.3.0-dev');
|
||||
define('ITOP_VERSION', '3.3.1-dev');
|
||||
|
||||
define('ITOP_VERSION_NAME', 'Fullmoon');
|
||||
define('ITOP_REVISION', 'svn');
|
||||
@@ -607,6 +607,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'attribute.max_actions_items' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of actions to display before it\'s compacted into a single action (0 means no limit)',
|
||||
'default' => 2,
|
||||
'value' => 2,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'email_asynchronous' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode',
|
||||
@@ -1288,6 +1296,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'transactions_file_lifetime' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Value in seconds for the lifetime of a transaction file (after this duration, the transaction will be invalid and the garbage collector could delete it). This only works if the "transaction_storage" parameter is set to "File".',
|
||||
'default' => 3600,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'log_transactions' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to enable the debug log for the transactions.',
|
||||
@@ -1749,8 +1765,8 @@ class Config
|
||||
'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,
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
@@ -1765,8 +1781,8 @@ class Config
|
||||
'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,
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
|
||||
@@ -730,13 +730,8 @@ abstract class DBObject implements iDisplay
|
||||
public function SetTrim($sAttCode, $sValue)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$iMaxSize = $oAttDef->GetMaxSize();
|
||||
$sLength = mb_strlen($sValue);
|
||||
if ($iMaxSize && ($sLength > $iMaxSize)) {
|
||||
$sMessage = " -truncated ($sLength chars)";
|
||||
$sValue = mb_substr($sValue, 0, $iMaxSize - mb_strlen($sMessage)).$sMessage;
|
||||
}
|
||||
$this->Set($sAttCode, $sValue);
|
||||
|
||||
$this->Set($sAttCode, $oAttDef->TrimValue($sValue));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2038,7 +2033,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
if (!is_null($iMaxSize = $oAtt->GetMaxSize())) {
|
||||
$iLen = mb_strlen($toCheck);
|
||||
$iLen = $oAtt->GetSize($toCheck);
|
||||
if ($iLen > $iMaxSize) {
|
||||
return "String too long (found $iLen, limited to $iMaxSize)";
|
||||
}
|
||||
|
||||
@@ -1935,9 +1935,8 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBObjectSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBObjectSearch
|
||||
protected function ApplyDataFilters(): DBSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
@@ -1946,7 +1945,7 @@ class DBObjectSearch extends DBSearch
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
|
||||
// Joined classes filtering can be disabled through the configuration.
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
|
||||
$aClassesToFilter = $this->GetJoinedClasses();
|
||||
}
|
||||
|
||||
@@ -676,15 +676,14 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBUnionSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBUnionSearch
|
||||
protected function ApplyDataFilters(): DBSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
|
||||
// Joined classes filtering can be disabled through the configuration.
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
|
||||
return parent::ApplyDataFilters();
|
||||
}
|
||||
|
||||
@@ -434,7 +434,7 @@ JS
|
||||
* Resize an image so that it fits the maximum width/height defined in the config file
|
||||
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
|
||||
* @return ormDocument The resampled image (or the original one if it already fit)
|
||||
* @deprecated since 3.3.0 Replaced by ormDocument::ResizeImageToFit
|
||||
* @deprecated 3.3.0 Use ormDocument::ResizeImageToFit instead
|
||||
*/
|
||||
public static function ResizeImageToFit(ormDocument $oImage, &$aDimensions = null)
|
||||
{
|
||||
|
||||
@@ -614,6 +614,13 @@ class LogChannels
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public const SECURITY = 'Security';
|
||||
|
||||
/**
|
||||
* For Session parameters
|
||||
*
|
||||
* @Since 3.3.0
|
||||
*/
|
||||
public const SESSION_PARAMETERS = 'SessionParameters';
|
||||
}
|
||||
|
||||
abstract class LogAPI
|
||||
|
||||
@@ -1642,10 +1642,10 @@ abstract class MetaModel
|
||||
if ($sFilterCode == 'id') {
|
||||
return true;
|
||||
}
|
||||
if (array_key_exists($sClass, self::$m_aMagicFields) && array_key_exists($sFilterCode, self::$m_aMagicFields[$sClass])) {
|
||||
if (array_key_exists($sClass, self::$m_aMagicFields) && in_array($sFilterCode, self::$m_aMagicFields[$sClass])) {
|
||||
return true;
|
||||
}
|
||||
if (array_key_exists($sClass, self::$m_aFilterForbiddenAttributes) && array_key_exists($sFilterCode, self::$m_aFilterForbiddenAttributes[$sClass])) {
|
||||
if (array_key_exists($sClass, self::$m_aFilterForbiddenAttributes) && in_array($sFilterCode, self::$m_aFilterForbiddenAttributes[$sClass])) {
|
||||
return false;
|
||||
}
|
||||
return self::IsValidAttCode($sClass, $sFilterCode);
|
||||
@@ -2963,8 +2963,9 @@ abstract class MetaModel
|
||||
self::$m_aMagicFields[] = $sClass;
|
||||
}
|
||||
self::$m_aMagicFields[$sClass][] = $sCode;
|
||||
self::$m_aAttribOrigins[$sClass][$sCode] = self::$m_aAttribOrigins[$sClass][$sAttCode];
|
||||
}
|
||||
if (!$oAttDef->IsSearchable()) {
|
||||
if (!$oAttDef->IsValidFilterAttribute()) {
|
||||
if (!array_key_exists($sClass, self::$m_aFilterForbiddenAttributes)) {
|
||||
self::$m_aFilterForbiddenAttributes[] = $sClass;
|
||||
}
|
||||
@@ -5761,36 +5762,37 @@ abstract class MetaModel
|
||||
|
||||
self::$m_sEnvironment = $sEnvironment;
|
||||
|
||||
try {
|
||||
if (!defined('MODULESROOT')) {
|
||||
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
|
||||
if (!defined('MODULESROOT')) {
|
||||
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
|
||||
|
||||
self::$m_bTraceSourceFiles = $bTraceSourceFiles;
|
||||
self::$m_bTraceSourceFiles = $bTraceSourceFiles;
|
||||
|
||||
// $config can be either a filename, or a Configuration object (volatile!)
|
||||
if ($config instanceof Config) {
|
||||
self::LoadConfig($config, $bAllowCache);
|
||||
} else {
|
||||
self::LoadConfig(new Config($config), $bAllowCache);
|
||||
}
|
||||
|
||||
if ($bModelOnly) {
|
||||
return;
|
||||
}
|
||||
// $config can be either a filename, or a Configuration object (volatile!)
|
||||
if ($config instanceof Config) {
|
||||
self::LoadConfig($config, $bAllowCache);
|
||||
} else {
|
||||
self::LoadConfig(new Config($config), $bAllowCache);
|
||||
}
|
||||
|
||||
CMDBSource::SelectDB(self::$m_sDBName);
|
||||
|
||||
foreach (MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass) {
|
||||
$oPHPClass::OnMetaModelStarted();
|
||||
if ($bModelOnly) {
|
||||
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
|
||||
EventService::InitService();
|
||||
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
|
||||
return;
|
||||
}
|
||||
|
||||
ExpressionCache::Warmup();
|
||||
} finally {
|
||||
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
|
||||
EventService::InitService();
|
||||
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
|
||||
}
|
||||
|
||||
CMDBSource::SelectDB(self::$m_sDBName);
|
||||
|
||||
foreach (MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass) {
|
||||
$oPHPClass::OnMetaModelStarted();
|
||||
}
|
||||
|
||||
ExpressionCache::Warmup();
|
||||
|
||||
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
|
||||
EventService::InitService();
|
||||
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6864,10 +6866,24 @@ abstract class MetaModel
|
||||
require_once(APPROOT.'/core/dict.class.inc.php');
|
||||
Dict::ResetCache($sAppIdentity);
|
||||
|
||||
$iApcDeleteFailureCount = 0;
|
||||
if (function_exists('apc_delete')) {
|
||||
foreach (self::GetCacheEntries($sEnvironmentId) as $sKey => $aAPCInfo) {
|
||||
$sAPCKey = $aAPCInfo['info'];
|
||||
apc_delete($sAPCKey);
|
||||
if (!apc_delete($sAPCKey)) {
|
||||
$iApcDeleteFailureCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($iApcDeleteFailureCount > 0) {
|
||||
SetupLog::Warning("apc_delete failed for $iApcDeleteFailureCount entries for env $sEnvironment");
|
||||
}
|
||||
|
||||
if (function_exists('apc_clear_cache')) {
|
||||
// old style APC
|
||||
if (!apc_clear_cache()) {
|
||||
SetupLog::Warning("apc_clear_cache failed for env $sEnvironment");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6877,7 +6893,9 @@ abstract class MetaModel
|
||||
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
|
||||
if (function_exists('opcache_reset')) {
|
||||
// Zend opcode cache
|
||||
opcache_reset();
|
||||
if (!opcache_reset()) {
|
||||
SetupLog::Warning("opcache_reset failed for env $sEnvironment");
|
||||
}
|
||||
}
|
||||
|
||||
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
||||
|
||||
@@ -60,15 +60,13 @@ class OQLActualClassTreeResolver
|
||||
$aTranslateFields = [];
|
||||
foreach ($aExpectedAttributes as $sAttCode => $oExpression) {
|
||||
// 'id' is managed later
|
||||
if ($sAttCode == 'id') {
|
||||
if ($sAttCode === 'id') {
|
||||
continue;
|
||||
}
|
||||
// Attributes can be stored in attributes list or for magic ones into filter codes list.
|
||||
$sOriginClass = null;
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode)) {
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode) || MetaModel::IsValidFilterCode($sClass, $sAttCode)) {
|
||||
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
|
||||
} elseif ($sAttCode == 'id') {
|
||||
$sOriginClass = $sClass;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ class ormDocument
|
||||
* @param int $iMaxImageHeight Maximum height for the resized image
|
||||
* @param array|null $aFinalDimensions Image dimensions after resizing or null if unable to read the image
|
||||
* @return ormDocument The resampled image
|
||||
*
|
||||
* @since 3.3.0 N°3124
|
||||
*/
|
||||
public function ResizeImageToFit(int $iMaxWidth, int $iMaxHeight, array|null &$aFinalDimensions = null): static
|
||||
{
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeExternalField;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeFriendlyName;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeLinkedSet;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeStopWatch;
|
||||
use Combodo\iTop\Core\AttributeDefinition\AttributeSubItem;
|
||||
|
||||
/**
|
||||
* Bulk export: Tabular export: abstract base class for all "tabular" exports.
|
||||
@@ -91,8 +98,8 @@ abstract class TabularBulkExport extends BulkExport
|
||||
{
|
||||
$aResult = [];
|
||||
switch (get_class($oAttDef)) {
|
||||
case 'AttributeExternalKey':
|
||||
case 'AttributeHierarchicalKey':
|
||||
case AttributeExternalKey::class:
|
||||
case AttributeHierarchicalKey::class:
|
||||
|
||||
$bAddFriendlyName = true;
|
||||
$oKeyAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
@@ -142,7 +149,7 @@ abstract class TabularBulkExport extends BulkExport
|
||||
}
|
||||
break;
|
||||
|
||||
case 'AttributeStopWatch':
|
||||
case AttributeStopWatch::class:
|
||||
foreach (MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef) {
|
||||
if ($oSubAttDef instanceof AttributeSubItem) {
|
||||
if ($oSubAttDef->GetParentAttCode() == $sAttCode) {
|
||||
@@ -318,7 +325,7 @@ EOF
|
||||
$sQueryId = utils::ReadParam('query', null, true);
|
||||
$sFields = utils::ReadParam('fields', null, true, 'raw_data');
|
||||
if ((($sFields === null) || ($sFields === '')) && ($sQueryId === null)) {
|
||||
throw new BulkExportMissingParameterException('fields');
|
||||
$sFields = $this->GetFieldsFromQuery();
|
||||
} else {
|
||||
if (($sQueryId !== null) && ($sQueryId !== null)) {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]);
|
||||
@@ -329,11 +336,11 @@ EOF
|
||||
// No 'fields' parameter supplied, take the fields from the query phrasebook definition
|
||||
$sFields = trim($oQuery->Get('fields'));
|
||||
if ($sFields === '') {
|
||||
throw new BulkExportMissingParameterException('fields');
|
||||
$sFields = $this->GetFieldsFromQuery();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
|
||||
throw new BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,6 +348,39 @@ EOF
|
||||
$this->SetFields($sFields);
|
||||
}
|
||||
|
||||
public function GetFieldsFromQuery(): string
|
||||
{
|
||||
if (is_null($this->oSearch)) {
|
||||
throw new BulkExportMissingParameterException("fields");
|
||||
}
|
||||
|
||||
$aSelectedClasses = $this->oSearch->GetSelectedClasses();
|
||||
$aAuthorizedClasses = [];
|
||||
$aFields = [];
|
||||
foreach ($aSelectedClasses as $sAlias => $sClassName) {
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ) == UR_ALLOWED_YES) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
|
||||
foreach (MetaModel::ListAttributeDefs($sClassName) as $oAttribute) {
|
||||
$sAttCode = $oAttribute->GetCode();
|
||||
if (! $this->IsExportableField($sClassName, $sAttCode, $oAttribute)) {
|
||||
continue;
|
||||
}
|
||||
$sCurrentAlias = utils::IsNullOrEmptyString($sAlias) ? "" : "$sAlias.";
|
||||
$aFields[] = "{$sCurrentAlias}{$sAttCode}";
|
||||
}
|
||||
}
|
||||
|
||||
if (count($aFields) === 0) {
|
||||
IssueLog::Error("User is not allowed to see any field for exported OQL");
|
||||
throw new BulkExportMissingParameterException("fields");
|
||||
}
|
||||
|
||||
return implode(',', $aFields);
|
||||
}
|
||||
|
||||
public function SetFields($sFields)
|
||||
{
|
||||
// Interpret (and check) the list of fields
|
||||
|
||||
@@ -1530,6 +1530,37 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $oUser
|
||||
* @param array $aExcludedProfilesId Administrator by default, but can also be other proofiles depending on needs (e.g. power portal user or REST profile)
|
||||
* @return bool
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws MySQLException
|
||||
*/
|
||||
public static function IsUserReadOnly(User $oUser, array $aExcludedProfilesId = [1]): bool
|
||||
{
|
||||
$oUserProfiles = $oUser->Get('profile_list');
|
||||
$oUserRights = UserRights::GetModuleInstance();
|
||||
while ($oUserProfile = $oUserProfiles->Fetch()) {
|
||||
$iProfileId = $oUserProfile->Get('profileid');
|
||||
if (in_array($iProfileId, $aExcludedProfilesId)) {
|
||||
return false;
|
||||
}
|
||||
foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass) {
|
||||
foreach (['w', 'bw', 'd', 'bd'] as $sWriteActionCode) {
|
||||
$bIsGranted = $oUserRights->GetProfileActionGrant($iProfileId, $sClass, $sWriteActionCode);
|
||||
if ($bIsGranted === true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param int $iActionCode see UR_ACTION_* constants
|
||||
|
||||
@@ -42,7 +42,7 @@ css/backoffice/
|
||||
|– vendors/ # Third-party libs, should be either:
|
||||
| # - Overload of the lib SCSS variables (BEST way, but possible only if the lib exposes them. e.g. Bulma)
|
||||
| # - Overload of the lib necessary CSS classes only (not great as it duplicates some rules in the browser, which add weight and computation. e.g. dataTables)
|
||||
| # - Duplicate the lib CSS completly to insert SCSS variables (not great as it will be outdated when updating the lib itself. e.g. jQuery UI)
|
||||
| # - Duplicate the lib CSS completely to insert SCSS variables (not great as it will be outdated when updating the lib itself. e.g. jQuery UI)
|
||||
| |– _bulma-variables-overload.scss # Bulma CSS framework
|
||||
| |– _jquery-ui.scss # jQuery UI
|
||||
| ... # Etc…
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
$ibo-user-rights--padding-x: $ibo-spacing-400 !default;
|
||||
$ibo-user-rights--padding-y: $ibo-spacing-200 !default;
|
||||
$ibo-user-rights--padding-y: $ibo-spacing-100 !default;
|
||||
$ibo-user-rights--border-radius: $ibo-border-radius-400 !default;
|
||||
|
||||
$ibo-user-rights--is-success--background-color: $ibo-color-success-100 !default;
|
||||
@@ -18,6 +18,8 @@ $ibo-user-rights--is-failure--border-color: $ibo-color-danger-500 !default;
|
||||
$ibo-user-rights--is-failure--border: 1px solid $ibo-user-rights--is-failure--border-color !default;
|
||||
|
||||
.ibo-user-rights {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
padding: $ibo-user-rights--padding-y $ibo-user-rights--padding-x;
|
||||
border-radius: $ibo-user-rights--border-radius;
|
||||
&.ibo-is-success {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
@import "alert";
|
||||
@import "button";
|
||||
@import "button-bar";
|
||||
@import "button-separator";
|
||||
@import "button-group";
|
||||
@import "breadcrumbs";
|
||||
@import "quick-create";
|
||||
|
||||
@@ -19,6 +19,8 @@ $ibo-badge-colors: (
|
||||
'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),
|
||||
'purple': ($ibo-color-purple-100, $ibo-color-purple-900),
|
||||
'yellow': ($ibo-color-yellow-200, $ibo-color-yellow-900),
|
||||
) !default;
|
||||
|
||||
|
||||
|
||||
43
css/backoffice/components/_button-bar.scss
Normal file
43
css/backoffice/components/_button-bar.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-button-bar--gap: 2px !default;
|
||||
$ibo-button-bar--track--gap: 6px !default;
|
||||
|
||||
/* button-bar.css */
|
||||
ibo-button-bar{
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: $ibo-button-bar--gap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Keep content visible even if JS initialization is delayed. */
|
||||
|
||||
ibo-button-bar .ibo-button-bar--track{
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
gap: $ibo-button-bar--track--gap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ibo-button-bar .ibo-button-bar--item{
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
ibo-button-bar .ibo-button-bar--item[hidden]{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
ibo-button-bar .ibo-button-bar--extra{
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
20
css/backoffice/components/_button-separator.scss
Normal file
20
css/backoffice/components/_button-separator.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-button-separator--width: 1px !default;
|
||||
$ibo-button-separator--height: 16px !default;
|
||||
$ibo-button-separator--margin-x: 0.75rem !default;
|
||||
$ibo-button-separator--margin-y: $ibo-spacing-0 !default;
|
||||
$ibo-button-separator--border-size: 1px !default;
|
||||
$ibo-button-separator--border-color: $ibo-color-grey-500 !default;
|
||||
|
||||
.ibo-button-separator {
|
||||
display: inline-flex;
|
||||
border-right: $ibo-button-separator--border-size solid $ibo-button-separator--border-color;
|
||||
width: $ibo-button-separator--width;
|
||||
height: $ibo-button-separator--height;
|
||||
margin: $ibo-button-separator--margin-y $ibo-button-separator--margin-x;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,12 @@ $ibo-field--enable-bulk--border-radius: $ibo-border-radius-500 !default;
|
||||
|
||||
$ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
|
||||
|
||||
$ibo-field--label--action--margin-y: $ibo-spacing-0 !default;
|
||||
$ibo-field--label--action--margin-x: $ibo-spacing-100 !default;
|
||||
$ibo-field--label--action--size: $ibo-field--fullscreen-toggler--size !default;
|
||||
$ibo-field--label--action--padding: $ibo-spacing-0 !default;
|
||||
$ibo-field--label--action--font-size: $ibo-font-size-50 !default;
|
||||
|
||||
/* SCSS rules */
|
||||
.ibo-field {
|
||||
@extend %ibo-font-size-150;
|
||||
@@ -264,4 +270,32 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
|
||||
|
||||
.ibo-input-select--action-buttons a {
|
||||
@extend %ibo-hyperlink-inherited-colors;
|
||||
}
|
||||
|
||||
.ibo-field--label {
|
||||
.ibo-field--action:not(.ibo-button-separator) {
|
||||
display: inline-flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
|
||||
width: $ibo-field--label--action--size;
|
||||
height: $ibo-field--label--action--size;
|
||||
padding: $ibo-field--label--action--padding;
|
||||
border-radius: $ibo-field--fullscreen-toggler--border-radius;
|
||||
font-size: $ibo-field--label--action--font-size;
|
||||
~ .ibo-field--action {
|
||||
margin-left: $ibo-field--label--action--margin-x;
|
||||
}
|
||||
}
|
||||
.ibo-field--action.ibo-button-separator {
|
||||
margin: $ibo-field--label--action--margin-y $ibo-field--label--action--margin-x;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
// Specific rule to ensure that the grouped actions popover of a field is visible in fullscreen mode
|
||||
body.ibo-has-fullscreen-descendant .ibo-popover-menu.ibo-field--grouped-actions-popover {
|
||||
z-index: 1060 !important;
|
||||
}
|
||||
@@ -8,70 +8,74 @@
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.help-text{
|
||||
padding: 1px 5px;
|
||||
background-color: #d7e3f8;
|
||||
border: 1px solid #c6e7f5;
|
||||
border-radius: 5px;
|
||||
margin: 5px 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.form-error ul{
|
||||
padding: 1px 5px;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 5px;
|
||||
margin: 5px 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.subform{
|
||||
background-color: #efefef;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-buttons{
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.form select{
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form select option{
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.turbo-refreshing{
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.ibo-field legend{
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
collection-entry-element {
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
padding: 10px 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.ts-control{
|
||||
height: auto;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.ibo-form-actions > .ibo-button > span{
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ibo-form textarea{
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
// WIP SDK Forms
|
||||
//form[is="itop-form-element"] {
|
||||
//
|
||||
// .help-text{
|
||||
// padding: 1px 5px;
|
||||
// background-color: #d7e3f8;
|
||||
// border: 1px solid #c6e7f5;
|
||||
// border-radius: 5px;
|
||||
// margin: 5px 0;
|
||||
// font-size: 0.9em;
|
||||
// }
|
||||
//
|
||||
// .form-error ul{
|
||||
// padding: 1px 5px;
|
||||
// background-color: #f8d7da;
|
||||
// border: 1px solid #f5c6cb;
|
||||
// border-radius: 5px;
|
||||
// margin: 5px 0;
|
||||
// font-size: 0.9em;
|
||||
// }
|
||||
//
|
||||
// .subform{
|
||||
// background-color: #efefef;
|
||||
// border-radius: 5px;
|
||||
// padding: 10px;
|
||||
// }
|
||||
//
|
||||
// .form-buttons{
|
||||
// margin: 20px 0;
|
||||
// }
|
||||
//
|
||||
// .form select{
|
||||
// padding: 0;
|
||||
// overflow-y: auto;
|
||||
// }
|
||||
//
|
||||
// .form select option{
|
||||
// height: 30px;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// }
|
||||
//
|
||||
// .turbo-refreshing{
|
||||
// opacity: .5;
|
||||
// }
|
||||
//
|
||||
// .ibo-field legend{
|
||||
// margin-top: 24px;
|
||||
// }
|
||||
//
|
||||
// collection-entry-element {
|
||||
// margin-top: 8px;
|
||||
// display: block;
|
||||
// padding: 10px 10px;
|
||||
// background-color: #f5f5f5;
|
||||
// border-radius: 5px;
|
||||
// }
|
||||
// .ts-control{
|
||||
// height: auto;
|
||||
// min-height: 30px;
|
||||
// }
|
||||
//
|
||||
// .ibo-form-actions > .ibo-button > span{
|
||||
// margin-right: 5px;
|
||||
// }
|
||||
//
|
||||
// .ibo-form textarea{
|
||||
// resize: vertical;
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-input-select-icon--menu--icon--max-height: 100% !default;
|
||||
$ibo-input-select-icon--menu--icon--max-width: 100% !default;
|
||||
$ibo-input-select-icon--icon--padding-right: $ibo-spacing-200 !default;
|
||||
$ibo-input-select-icon--max-height: 100% !default;
|
||||
$ibo-input-select-icon--max-width: 100% !default;
|
||||
$ibo-input-select-icon--padding-right: $ibo-spacing-200 !default;
|
||||
|
||||
$ibo-input-select-icon--menu--z-index: 21 !default;
|
||||
$ibo-input-select-icon--menu--max-height: 300px !default;
|
||||
@@ -18,9 +18,9 @@ $ibo-input-select-icon--menu--icon--margin-right: 10px !default;
|
||||
display: inline-flex;
|
||||
text-align: left;
|
||||
>img{
|
||||
max-height: $ibo-input-select-icon--menu--icon--max-height;
|
||||
max-width: $ibo-input-select-icon--menu--icon--max-width;
|
||||
padding-right: $ibo-input-select-icon--icon--padding-right;
|
||||
max-height: $ibo-input-select-icon--max-height;
|
||||
max-width: $ibo-input-select-icon--max-width;
|
||||
padding-right: $ibo-input-select-icon--padding-right;
|
||||
}
|
||||
>span{
|
||||
overflow: hidden;
|
||||
|
||||
@@ -21,6 +21,7 @@ $ibo-input-set--item--padding-x: 6px !default;
|
||||
$ibo-input-set--item--padding-y: $ibo-spacing-200 !default;
|
||||
$ibo-input-set--item--max-width: 360px !default;
|
||||
$ibo-input-set--item--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-input-set--item--color: $ibo-color-grey-900 !default;
|
||||
$ibo-input-set--item--border: none !default;
|
||||
$ibo-input-set--item--border-radius: $ibo-border-radius-300 !default;
|
||||
$ibo-input-set--item--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgba(241, 241, 241, 0.7) !default;
|
||||
@@ -59,7 +60,7 @@ $ibo-input-set--item--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgb
|
||||
border-radius: $ibo-input-set--item--border-radius;
|
||||
box-shadow: $ibo-input-set--item--box-shadow;
|
||||
|
||||
color: $ibo-color-grey-900;
|
||||
color: $ibo-input-set--item--color;
|
||||
text-shadow: none;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
/* - Guaranty that if a set item has html sub-node, they correctly display ellipsis (eg. Link set set items) */
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
@import "navigation-menu";
|
||||
@import "top-bar";
|
||||
@import "content";
|
||||
@import "details";
|
||||
@import "tab-container/tab-container";
|
||||
@@ -15,4 +14,5 @@
|
||||
@import "wizard-container/wizard-container";
|
||||
@import "object/all";
|
||||
@import "activity-panel/all";
|
||||
@import "extension/all";
|
||||
@import "extension/all";
|
||||
@import "top-bar/all";
|
||||
@@ -793,8 +793,9 @@ $ibo-navigation-menu--user-info--height--is-expanded: 100% !default;
|
||||
color: $ibo-navigation-menu--user--text-color;
|
||||
}
|
||||
|
||||
.ibo-navigation-menu--user-notifications--toggler--icon {
|
||||
padding-left: $ibo-navigation-menu--user-notifications--toggler--icon--padding-left;
|
||||
.ibo-navigation-menu--user-notifications--toggler--icon {
|
||||
position: relative;
|
||||
padding-left: $ibo-navigation-menu--user-notifications--toggler--icon--padding-left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
78
css/backoffice/layout/activity-panel/_activity-action.scss
Normal file
78
css/backoffice/layout/activity-panel/_activity-action.scss
Normal file
@@ -0,0 +1,78 @@
|
||||
$ibo-activity-panel--activity-actions--right: $ibo-activity-panel--add-caselog-entry-button--right !default;
|
||||
$ibo-activity-panel--activity-actions--top: 76px + $ibo-activity-panel--activity-actions--right !default;
|
||||
$ibo-activity-panel--activity-actions--gap: $ibo-spacing-300 !default;
|
||||
|
||||
$ibo-activity-panel--activity-action--diameter: $ibo-activity-panel--add-caselog-entry-button--diameter !default;
|
||||
$ibo-activity-panel--activity-action--background-color: $ibo-activity-panel--add-caselog-entry-button--background-color !default;
|
||||
$ibo-activity-panel--activity-action--background-color--on-hover: $ibo-activity-panel--add-caselog-entry-button--background-color--on-hover !default;
|
||||
$ibo-activity-panel--activity-action--background-color--is-active: $ibo-activity-panel--add-caselog-entry-button--background-color--is-active !default;
|
||||
$ibo-activity-panel--activity-action--color: $ibo-activity-panel--add-caselog-entry-button--color !default;
|
||||
$ibo-activity-panel--activity-action--border-radius: $ibo-border-radius-full !default;
|
||||
$ibo-activity-panel--activity-action--box-shadow: $ibo-activity-panel--add-caselog-entry-button--box-shadow !default;
|
||||
$ibo-activity-panel--activity-action--hover--box-shadow: $ibo-activity-panel--add-caselog-entry-button--hover--box-shadow !default;
|
||||
|
||||
$ibo-activity-panel--activity-action--icon--height: $ibo-activity-panel--add-caselog-entry-button--icon--height !default;
|
||||
$ibo-activity-panel--activity-action--icon--width: $ibo-activity-panel--activity-action--icon--height !default;
|
||||
$ibo-activity-panel--activity-action--icon--font-size: $ibo-activity-panel--add-caselog-entry-button--icon--font-size !default;
|
||||
$ibo-activity-panel--activity-action--icon--line-height: $ibo-activity-panel--add-caselog-entry-button--icon--line-height !default;
|
||||
$ibo-activity-panel--activity-action-separator--width: calc(#{$ibo-activity-panel--activity-action--diameter} / 1.5) !default;
|
||||
$ibo-activity-panel--activity-action-separator--height: 2px !default;
|
||||
$ibo-activity-panel--activity-action-separator--background-color: $ibo-color-grey-500 !default;
|
||||
|
||||
|
||||
.ibo-activity-panel--activity-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $ibo-activity-panel--activity-actions--gap;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: $ibo-activity-panel--activity-actions--right;
|
||||
top: $ibo-activity-panel--activity-actions--top;
|
||||
&.ibo-is-hidden{
|
||||
display: none;
|
||||
}
|
||||
&:has(+ .ibo-activity-panel--add-caselog-entry-button) {
|
||||
top: calc(#{$ibo-activity-panel--activity-actions--top} + #{$ibo-activity-panel--activity-action--diameter} + #{$ibo-activity-panel--activity-actions--gap});
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-activity-panel--activity-action {
|
||||
@extend %ibo-baseline-centered-content;
|
||||
|
||||
width: $ibo-activity-panel--activity-action--diameter;
|
||||
height: $ibo-activity-panel--activity-action--diameter;
|
||||
|
||||
background-color: $ibo-activity-panel--activity-action--background-color;
|
||||
color: $ibo-activity-panel--activity-action--color;
|
||||
|
||||
border-radius: $ibo-activity-panel--activity-action--border-radius;
|
||||
box-shadow: $ibo-activity-panel--activity-action--box-shadow;
|
||||
|
||||
> i{
|
||||
text-align: center;
|
||||
height: $ibo-activity-panel--activity-action--icon--height;
|
||||
width: $ibo-activity-panel--activity-action--icon--width;
|
||||
font-size: $ibo-activity-panel--activity-action--icon--font-size;
|
||||
line-height: $ibo-activity-panel--activity-action--icon--line-height;
|
||||
}
|
||||
&:hover {
|
||||
color: $ibo-activity-panel--activity-action--color;
|
||||
background-color: $ibo-activity-panel--activity-action--background-color--on-hover;
|
||||
box-shadow: $ibo-activity-panel--activity-action--hover--box-shadow;
|
||||
}
|
||||
&:active {
|
||||
color: $ibo-activity-panel--activity-action--color;
|
||||
background-color: $ibo-activity-panel--activity-action--background-color--is-active;
|
||||
}
|
||||
&.ibo-is-hidden{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-activity-panel--activity-action-separator {
|
||||
display: block;
|
||||
width: $ibo-activity-panel--activity-action-separator--width;
|
||||
height: $ibo-activity-panel--activity-action-separator--height;
|
||||
margin: auto;
|
||||
background-color: $ibo-activity-panel--activity-action-separator--background-color;
|
||||
}
|
||||
@@ -137,7 +137,8 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
|
||||
|
||||
.ibo-activity-panel--header,
|
||||
.ibo-activity-panel--body,
|
||||
.ibo-activity-panel--add-caselog-entry-button {
|
||||
.ibo-activity-panel--add-caselog-entry-button,
|
||||
.ibo-activity-panel--activity-actions{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,3 +10,4 @@
|
||||
@import "transition-entry";
|
||||
@import "edits-entry";
|
||||
@import "notification-entry";
|
||||
@import "activity-action";
|
||||
|
||||
@@ -59,4 +59,9 @@ $ibo-caselog-entry-form--lock-message--margin-left: 1rem !default;
|
||||
}
|
||||
.ibo-caselog-entry-form--action-buttons--main-actions > .ibo-popover-menu {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ibo-caselog-entry-form--action-buttons--extra-actions{
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
2
css/backoffice/layout/top-bar/_all.scss
Normal file
2
css/backoffice/layout/top-bar/_all.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "top-bar";
|
||||
@import "top-bar-action";
|
||||
40
css/backoffice/layout/top-bar/_top-bar-action.scss
Normal file
40
css/backoffice/layout/top-bar/_top-bar-action.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
$ibo-top-bar-quick-action--head--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-top-bar-quick-action--icon-padding-x: $ibo-spacing-500 !default;
|
||||
$ibo-top-bar-quick-action--icon-padding-y: $ibo-spacing-0 !default;
|
||||
$ibo-top-bar-quick-action--icon--color: $ibo-color-primary-600 !default;
|
||||
$ibo-top-bar-quick-action--icon--color--on-hover: $ibo-color-primary-700 !default;
|
||||
$ibo-top-bar-quick-action--icon--color--on-active: $ibo-color-primary-800 !default;
|
||||
$ibo-top-bar-quick-action-separator--width: 2px !default;
|
||||
$ibo-top-bar-quick-action-separator--background-color: $ibo-color-grey-300 !default;
|
||||
$ibo-top-bar-quick-action-separator--margin-y: $ibo-spacing-0 !default;
|
||||
$ibo-top-bar-quick-action-separator--margin-x: $ibo-spacing-300 !default;
|
||||
|
||||
.ibo-top-bar-quick-action{
|
||||
position: relative;
|
||||
@extend %ibo-full-height-content;
|
||||
}
|
||||
.ibo-top-bar-quick-action--head{
|
||||
@extend %ibo-full-height-content;
|
||||
height: 100%;
|
||||
background-color: $ibo-top-bar-quick-action--head--background-color;
|
||||
}
|
||||
.ibo-top-bar-quick-action--icon{
|
||||
color: $ibo-top-bar-quick-action--icon--color;
|
||||
align-self: center;
|
||||
padding: $ibo-top-bar-quick-action--icon-padding-y $ibo-top-bar-quick-action--icon-padding-x;
|
||||
@extend %ibo-font-ral-nor-400;
|
||||
|
||||
&:hover{
|
||||
color: $ibo-top-bar-quick-action--icon--color--on-hover;
|
||||
}
|
||||
&:active{
|
||||
color: $ibo-top-bar-quick-action--icon--color--on-active;
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-top-bar-quick-action-separator {
|
||||
width: $ibo-top-bar-quick-action-separator--width;
|
||||
background-color: $ibo-top-bar-quick-action-separator--background-color;
|
||||
margin: $ibo-top-bar-quick-action-separator--margin-y $ibo-top-bar-quick-action-separator--margin-x;
|
||||
}
|
||||
@@ -70,7 +70,7 @@ $ibo-vendors-ckeditor--ck-mentions--item--padding-y: $ibo-spacing-200 !default;
|
||||
}
|
||||
}
|
||||
|
||||
.ck-editor__editable_inline:not(.ck-comment__input *) {
|
||||
.ck-editor__editable_inline:not(.ck-comment__input *), .ck-source-editing-area {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
@@ -84,11 +84,6 @@ $ibo-vendors-ckeditor--ck-mentions--item--padding-y: $ibo-spacing-200 !default;
|
||||
}
|
||||
}
|
||||
|
||||
// N°7552 Allow source editing area to be scrollable in full screen
|
||||
.ck-maximize_editor_main .ck-source-editing-area textarea{
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
/* Mentions */
|
||||
.ck-mentions {
|
||||
.ck-button {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
/* SCSS variables */
|
||||
$common-has-description--content: "\1F6C8" !default;
|
||||
$common-has-description--content: "?" !default;
|
||||
$common-has-description--padding-left: $common-spacing-200 !default;
|
||||
$common-has-description--color: $common-color-grey-600 !default;
|
||||
$common-has-description--font-size: 0.7em !default; /* Font size is em on purpose as we want it to be proportional to its context */
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
*
|
||||
* Usage: @extend %common-font-size-XXX;
|
||||
*/
|
||||
%common-font-size-20 {
|
||||
font-size: $common-font-size-20;
|
||||
}
|
||||
%common-font-size-50 {
|
||||
font-size: $common-font-size-50;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -611,32 +611,6 @@ body {
|
||||
.setup-extension--missing .setup-extension--icon{
|
||||
color:#a00000;
|
||||
}
|
||||
.setup-extension-tag {
|
||||
display: inline-flex;
|
||||
background-color: grey;
|
||||
border-radius: 8px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
margin-right: 3px;
|
||||
&.installed{
|
||||
background-color:#9eff9e
|
||||
}
|
||||
&.notinstalled{
|
||||
background-color:#ed9eff
|
||||
}
|
||||
&.tobeinstalled{
|
||||
background-color:#9ef0ff
|
||||
}
|
||||
&.tobeuninstalled{
|
||||
background-color:#ff9e9e
|
||||
}
|
||||
&.notuninstallable{
|
||||
background-color:#ffc98c
|
||||
}
|
||||
&.removed{
|
||||
background-color: #969594
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details {
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'CAS:Error:UserNotAllowed' => 'User not allowed',
|
||||
'CAS:Login:SignIn' => 'Sign in with CAS',
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Spanish Localized data
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @author Miguel Turrubiates <miguel_tf@yahoo.com>
|
||||
* @notas Utilizar codificación UTF-8 para mostrar acentos y otros caracteres especiales
|
||||
*
|
||||
*/
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'CAS:Error:UserNotAllowed' => 'Usuario no permitido',
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'CAS:Error:UserNotAllowed' => '用户被禁止登录',
|
||||
'CAS:Login:SignIn' => '使用CAS登录',
|
||||
'CAS:Login:SignInTooltip' => '点击这里使用CAS服务器认证',
|
||||
'CAS:Login:SignIn' => '使用 CAS 登录',
|
||||
'CAS:Login:SignInTooltip' => '点击这里使用 CAS 服务器认证',
|
||||
]);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-cas/3.3.0',
|
||||
'authent-cas/3.3.1',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,39 +3,13 @@
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
// Dictionnay conventions
|
||||
// Class:<class_name>
|
||||
// Class:<class_name>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>
|
||||
// Class:<class_name>/Attribute:<attribute_code>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
|
||||
//
|
||||
// Class: UserExternal
|
||||
//
|
||||
|
||||
Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'Class:UserExternal' => 'External user',
|
||||
'Class:UserExternal+' => 'User authentified outside of '.ITOP_APPLICATION_SHORT,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Spanish Localized data
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @author Miguel Turrubiates <miguel_tf@yahoo.com>
|
||||
* @notas Utilizar codificación UTF-8 para mostrar acentos y otros caracteres especiales
|
||||
*
|
||||
*/
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:UserExternal' => 'Usuario externo',
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
*/
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:UserExternal' => 'Külső felhasználó',
|
||||
'Class:UserExternal+' => '',
|
||||
'Class:UserExternal+' => 'User authentified outside of '.ITOP_APPLICATION_SHORT.'~~',
|
||||
]);
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
*/
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:UserExternal' => 'Usuário externo',
|
||||
'Class:UserExternal+' => '',
|
||||
'Class:UserExternal+' => 'User authentified outside of '.ITOP_APPLICATION_SHORT.'~~',
|
||||
]);
|
||||
|
||||
@@ -10,31 +10,7 @@
|
||||
/**
|
||||
* @author Robert Deng <denglx@gmail.com>
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
// Dictionnay conventions
|
||||
// Class:<class_name>
|
||||
// Class:<class_name>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>
|
||||
// Class:<class_name>/Attribute:<attribute_code>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
//
|
||||
// Class: UserExternal
|
||||
//
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserExternal' => '外部用户',
|
||||
'Class:UserExternal+' => '用户在'.ITOP_APPLICATION_SHORT.'外部验证身份',
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-external/3.3.0',
|
||||
'authent-external/3.3.1',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -16,13 +16,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:UserLDAP' => 'LDAP uživatel',
|
||||
'Class:UserLDAP+' => 'Uživatel ověřen přes LDAP',
|
||||
'UserLDAP:server' => 'Specifika LDAP',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -15,13 +15,6 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:UserLDAP' => 'LDAP-Bruger',
|
||||
'Class:UserLDAP+' => 'Bruger der godkendes via LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -15,13 +15,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:UserLDAP' => 'LDAP-Benutzer',
|
||||
'Class:UserLDAP+' => 'Benutzer, der via LDAP authentifiziert wird',
|
||||
'UserLDAP:server' => 'LDAP-Einstellungen',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'LDAP-Server',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => 'Optional: LDAP-Server, der zur Authentifizierung verwendet werden soll, falls mehrere LDAP-Server konfiguriert sind.',
|
||||
]);
|
||||
|
||||
@@ -3,41 +3,17 @@
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
// Dictionnay conventions
|
||||
// Class:<class_name>
|
||||
// Class:<class_name>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>
|
||||
// Class:<class_name>/Attribute:<attribute_code>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'Class:UserLDAP' => 'LDAP user',
|
||||
'Class:UserLDAP+' => 'User authenticated by LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics',
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Spanish Localized data
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @author Miguel Turrubiates <miguel_tf@yahoo.com>
|
||||
* @notas Utilizar codificación UTF-8 para mostrar acentos y otros caracteres especiales
|
||||
*
|
||||
*/
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:UserLDAP' => 'Usuario LDAP',
|
||||
'Class:UserLDAP+' => 'Usuario autenticado vía LDAP',
|
||||
'UserLDAP:server' => 'Especificaciones LDAP',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -14,13 +14,6 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:UserLDAP' => 'Utilisateur LDAP',
|
||||
'Class:UserLDAP+' => 'Utilisateur authentifié par un serveur LDAP',
|
||||
'UserLDAP:server' => 'Champs spécifiques pour LDAP',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Serveur LDAP',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => 'Serveur LDAP utilisé pour l\'authentification de cet utilisateur',
|
||||
]);
|
||||
|
||||
@@ -14,13 +14,6 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:UserLDAP' => 'LDAP felhasználó',
|
||||
'Class:UserLDAP+' => 'LDAP vagy AD felhasználó',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -14,13 +14,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:UserLDAP' => 'Utente LDAP',
|
||||
'Class:UserLDAP+' => 'Utente autenticato da LDAP',
|
||||
'UserLDAP:server' => 'Specifiche LDAP',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -15,13 +15,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:UserLDAP' => 'LDAP ユーザー',
|
||||
'Class:UserLDAP+' => 'LDAP認証ユーザー',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -10,18 +10,12 @@
|
||||
/**
|
||||
* @author Thomas Casteleyn <thomas.casteleyn@super-visions.com>
|
||||
* @author Jeffrey Bostoen <info@jeffreybostoen.be> (2018 - 2022)
|
||||
*
|
||||
*/
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:UserLDAP' => 'LDAP-gebruiker',
|
||||
'Class:UserLDAP+' => 'Gebruiker die aanmeldt via LDAP',
|
||||
'UserLDAP:server' => 'LDAP informatie',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -14,13 +14,6 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:UserLDAP' => 'Użytkownik LDAP',
|
||||
'Class:UserLDAP+' => 'Użytkownik uwierzytelniony przez LDAP',
|
||||
'UserLDAP:server' => 'Serwer LDAP',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -12,15 +12,8 @@
|
||||
*/
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:UserLDAP' => 'Usuário externo via LDAP',
|
||||
'Class:UserLDAP+' => '',
|
||||
'Class:UserLDAP+' => 'User authenticated by LDAP~~',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -14,14 +14,7 @@
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLDAP' => 'Пользователь LDAP',
|
||||
'Class:UserLDAP+' => 'Пользователь, аутентифицируемый через LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'UserLDAP:server' => 'Особенности LDAP',
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Сервер LDAP',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -14,13 +14,6 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'Class:UserLDAP' => 'LDAP užívateľ',
|
||||
'Class:UserLDAP+' => 'User authenticated by LDAP~~',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -15,13 +15,6 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:UserLDAP' => 'LDAP kullanıcısı',
|
||||
'Class:UserLDAP+' => 'Yetki kontrolü LDAP tarafından yapılan',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
@@ -5,46 +5,16 @@
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @author Robert Deng <denglx@gmail.com>
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
// Dictionnay conventions
|
||||
// Class:<class_name>
|
||||
// Class:<class_name>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>
|
||||
// Class:<class_name>/Attribute:<attribute_code>+
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLDAP' => 'LDAP用户',
|
||||
'Class:UserLDAP+' => '用户身份由LDAP认证',
|
||||
'UserLDAP:server' => 'LDAP详情',
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP' => 'LDAP 用户',
|
||||
'Class:UserLDAP+' => '用户身份由 LDAP 认证',
|
||||
'UserLDAP:server' => 'LDAP 详情',
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'LDAP 服务器',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
]);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user