mirror of
https://github.com/Combodo/iTop.git
synced 2026-10-03 15:09:10 +02:00
Compare commits
166 Commits
support/3.
...
faf/Servic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a95f6c605 | ||
|
|
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 | ||
|
|
d88687527e | ||
|
|
2cc071b07a | ||
|
|
a5c14e4870 | ||
|
|
0e72b090ee | ||
|
|
1b70b4bf70 | ||
|
|
41f51a2c0c | ||
|
|
73ede13443 | ||
|
|
40dd72ef82 | ||
|
|
c8aea56171 | ||
|
|
041437cbeb | ||
|
|
5715e0484f | ||
|
|
109fdfa06b | ||
|
|
13fad9efc1 | ||
|
|
9fbc53cf00 | ||
|
|
918f110dc9 | ||
|
|
bd9cc9c15e | ||
|
|
60ce21509f |
@@ -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"
|
||||
checkout support/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"
|
||||
```
|
||||
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
1
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@@ -8,6 +8,7 @@ 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."
|
||||
|
||||
- type: textarea
|
||||
id: enhancement_details
|
||||
|
||||
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@@ -1,6 +1,7 @@
|
||||
<!--
|
||||
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.
|
||||
|
||||
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. 🙏
|
||||
|
||||
@@ -9,10 +10,10 @@ Any PRs not following the guidelines or with missing information will not be con
|
||||
|
||||
## 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 or ticket number --> |
|
||||
| Type of change? | Bug fix / Enhancement / Translations |
|
||||
|
||||
## Symptom (bug) / Objective (enhancement)
|
||||
|
||||
@@ -70,6 +71,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
|
||||
|
||||
@@ -582,7 +582,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
if (count($oUser->ListChanges()) === 0) { // backward compatibility
|
||||
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();
|
||||
@@ -595,6 +597,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -563,6 +563,17 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_LOCATOR_INITIALIZED" _delta="define">
|
||||
<name>Service Locator Initialized</name>
|
||||
<internal>true</internal>
|
||||
<description><![CDATA[Inform the listeners that Service Locator has been (re)initialized]]></description>
|
||||
<event_data>
|
||||
<event_datum id="file_name">
|
||||
<description>The configuration file used to initialize Service Locator</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
</events>
|
||||
<meta>
|
||||
<classes>
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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)";
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\DependencyInjection\ServiceLocator;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
@@ -130,6 +131,8 @@ abstract class MetaModel
|
||||
private static $m_aClassToFile = [];
|
||||
/** @var string */
|
||||
protected static $m_sEnvironment = ITOP_DEFAULT_ENV;
|
||||
/** @var \Combodo\iTop\Service\DependencyInjection\ServiceLocator */
|
||||
private static ?ServiceLocator $oServiceLocator = null;
|
||||
|
||||
/**
|
||||
* Objects currently created/updated.
|
||||
@@ -1642,10 +1645,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 +2966,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;
|
||||
}
|
||||
@@ -5777,6 +5781,7 @@ abstract class MetaModel
|
||||
// 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));
|
||||
self::$oServiceLocator = new ServiceLocator(self::GetConfig());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -5792,6 +5797,8 @@ abstract class MetaModel
|
||||
// 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));
|
||||
self::$oServiceLocator = new ServiceLocator();
|
||||
self::$oServiceLocator->InitFromConfig(self::GetConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6865,10 +6872,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6878,7 +6899,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');
|
||||
@@ -7068,6 +7091,11 @@ abstract class MetaModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetServiceLocator(): ?ServiceLocator
|
||||
{
|
||||
return self::$oServiceLocator;
|
||||
}
|
||||
}
|
||||
|
||||
// Standard attribute lists
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
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;
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,8 +6,9 @@
|
||||
* @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 服务器认证',
|
||||
]);
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
*
|
||||
* @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
|
||||
@@ -23,6 +21,7 @@
|
||||
* 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>+
|
||||
@@ -32,9 +31,11 @@
|
||||
// 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.'外部验证身份',
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLDAP' => 'Пользователь LDAP',
|
||||
'Class:UserLDAP+' => 'Пользователь, аутентифицируемый через LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
'UserLDAP:server' => 'Особенности LDAP',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -22,6 +22,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
//
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Сервер LDAP',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '',
|
||||
]);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* @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
|
||||
@@ -22,6 +21,7 @@
|
||||
* 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>+
|
||||
@@ -31,13 +31,15 @@
|
||||
// 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' => 'LDAP 用户',
|
||||
'Class:UserLDAP+' => '用户身份由 LDAP 认证',
|
||||
'UserLDAP:server' => 'LDAP 详情',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -45,6 +47,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
//
|
||||
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'LDAP 服务器',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => '',
|
||||
]);
|
||||
|
||||
@@ -25,7 +25,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'abgelaufen',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'einmaliges Passwort',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Das Passwort kann vom Benutzer nicht geändert werden.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort muss mindestens 12 Zeichen lang sein und Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.',
|
||||
|
||||
@@ -24,11 +24,11 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'Истёкший',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'One-time Password~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'Одноразовый пароль',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Пароль не может быть изменён пользователем.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Дата изменения пароля',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Когда пароль был изменен в последний раз',
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 12 символов и включать прописные, строчные, числовые и специальные символы.',
|
||||
'UserLocal:password:expiration' => 'Поля требуют наличия доп. расширения',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Установка срока действия пароля "Одноразовый пароль" для своей собственной учётной записи не разрешена',
|
||||
]);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* @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
|
||||
@@ -22,6 +21,7 @@
|
||||
* 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>+
|
||||
@@ -31,16 +31,19 @@
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
|
||||
//
|
||||
// Class: UserLocal
|
||||
//
|
||||
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLocal' => ITOP_APPLICATION_SHORT.' 用户',
|
||||
'Class:UserLocal+' => '用户由'.ITOP_APPLICATION_SHORT.'验证身份',
|
||||
'Class:UserLocal/Attribute:password' => '密码',
|
||||
'Class:UserLocal/Attribute:password+' => '用于验证用户身份的字符串',
|
||||
'Class:UserLocal/Attribute:expiration' => '密码过期',
|
||||
'Class:UserLocal/Attribute:expiration+' => '密码过期状态 (需要一个扩展才能生效)',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => '密码过期时间',
|
||||
'Class:UserLocal/Attribute:expiration+' => '密码过期状态 (需要扩展才能生效)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => '允许过期',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire' => '永不过期',
|
||||
@@ -49,8 +52,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => '一次性密码',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '用户不允许修改密码.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新时间',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => '上次修改密码的时间',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少12个字符, 包含大小写, 数字和特殊字符.',
|
||||
'UserLocal:password:expiration' => '下面的区域需要插件扩展',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => '不允许用户为自己设置 "一次性密码" 的失效期限',
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
*
|
||||
*/
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
'theme:darkmoon' => 'Тёмная луна',
|
||||
]);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'theme:darkmoon' => 'Dark moon',
|
||||
]);
|
||||
|
||||
@@ -149,6 +149,8 @@ $ibo-quick-create--input--text-color: $ibo-color-grey-100 !default;
|
||||
$ibo-quick-create--input--placeholder-color: $ibo-color-grey-200 !default;
|
||||
$ibo-quick-create--compartment-element--background-color--is-active: $ibo-color-grey-500 !default;
|
||||
|
||||
$ibo-top-bar-quick-action--head--background-color: $ibo-top-bar--background-color !default;
|
||||
|
||||
$ibo-datatable--toolbar--text-color: $ibo-color-grey-300 !default;
|
||||
$ibo-vendors-datatables--pagination-button--background-color--on-hover: $ibo-color-grey-700 !default;
|
||||
$ibo-vendors-datatables--pagination-button--text-color--is-active: $ibo-color-grey-200 !default;
|
||||
@@ -200,6 +202,10 @@ $ibo-input-select-selectize--item--active--text-color: $ibo-color-grey-100 !defa
|
||||
$ibo-input-select-selectize--item--active--background-color: $ibo-color-grey-500 !default;
|
||||
$ibo-input-select--autocomplete-item-image--background-color: $ibo-color-grey-800 !default;
|
||||
$ibo-input-date--button--color: $ibo-input-select--action-button--color;
|
||||
$ibo-input-set--item--background-color: $ibo-color-grey-600 !default;
|
||||
$ibo-input-set--item--color: $ibo-color-grey-200 !default;
|
||||
$ibo-input-set--item--box-shadow: none !default;
|
||||
$ibo-input-set--item--border: solid 1px $ibo-color-grey-800 !default;
|
||||
$ibo-vendors-selectize-input--color: $ibo-body-text-color !default;
|
||||
$ibo-vendors-selectize-input--background-color: $ibo-input--background-color !default;
|
||||
$ibo-vendors-selectize--input--border-color: $ibo-input--border-color !default;
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
*/
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'theme:fullmoon-high-contrast' => 'Fullmoon (High contrast)~~',
|
||||
'theme:fullmoon-high-contrast' => 'Fullmoon (Hoher Kontrast)',
|
||||
]);
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
*/
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'theme:fullmoon-high-contrast' => 'Fullmoon (High contrast)~~',
|
||||
'theme:fullmoon-high-contrast' => 'Fullmoon (высокая контрастность)',
|
||||
]);
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
*/
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (Protanopia & Deuteranopia)~~',
|
||||
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (Protanopie & Deuteranopie)',
|
||||
]);
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
*/
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (Protanopia & Deuteranopia)~~',
|
||||
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (протанопия и дейтеранопия)',
|
||||
]);
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
*/
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'theme:fullmoon-tritanopia' => 'Fullmoon (Tritanopia)~~',
|
||||
'theme:fullmoon-tritanopia' => 'Fullmoon (Tritanopie)',
|
||||
]);
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
*/
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'theme:fullmoon-tritanopia' => 'Fullmoon (Tritanopia)~~',
|
||||
'theme:fullmoon-tritanopia' => 'Fullmoon (тританопия)',
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<service_locator>
|
||||
<services>
|
||||
<service id="DataFeatureRemoverExtension">
|
||||
<class id="Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService"/>
|
||||
</service>
|
||||
</services>
|
||||
</service_locator>
|
||||
@@ -1,11 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
|
||||
<classes>
|
||||
<class id="ResourceDataFeatureRemoval" _delta="define">
|
||||
<parent>AbstractResource</parent>
|
||||
<properties>
|
||||
<comment>/* Extension management access control. */</comment>
|
||||
<abstract>true</abstract>
|
||||
<category>grant_by_profile</category>
|
||||
</properties>
|
||||
<presentation/>
|
||||
<methods/>
|
||||
</class>
|
||||
</classes>
|
||||
<menus>
|
||||
<menu id="DataFeatureRemovalMenu" xsi:type="WebPageMenuNode" _delta="define">
|
||||
<rank>30</rank>
|
||||
<parent>SystemTools</parent>
|
||||
<url>$pages/exec.php?exec_module=combodo-data-feature-removal&exec_page=index.php&c[menu]=DataFeatureRemovalMenu</url>
|
||||
<enable_admin_only>1</enable_admin_only>
|
||||
<enable_class>ResourceDataFeatureRemoval</enable_class>
|
||||
<enable_action>UR_ACTION_MODIFY</enable_action>
|
||||
</menu>
|
||||
</menus>
|
||||
<module_parameters>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Menu:DataFeatureRemovalMenu' => 'Extension-Verwaltung',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => 'Extension-Verwaltung',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => 'Extension-Verwaltung',
|
||||
'DataFeatureRemoval:Main:SubTitle' => 'In Ihrem iTop installierte Extensions aktivieren oder deaktivieren',
|
||||
'DataFeatureRemoval:Failure:Title' => 'Fehler beim Testlauf der Extension-Entfernung',
|
||||
'DataFeatureRemoval:Helper:Title' => 'Prüfen Sie, ob Daten oder Abhängigkeiten dem Hinzufügen bzw. Entfernen einer Extension entgegenstehen.',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Extensions',
|
||||
'DataFeatureRemoval:Result:Title' => 'Angeforderte Änderung',
|
||||
'DataFeatureRemoval:NoResult:Title' => 'Keine Änderung angefordert',
|
||||
'DataFeatureRemoval:Execution:Title' => 'Ausführung der Löschvorgänge',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analyseergebnis',
|
||||
'DataFeatureRemoval:Analysis:Subtitle' => 'Prüfen Sie alle Elemente, die Ihre Aufmerksamkeit erfordern',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s Element(e) müssen bereinigt werden, bevor Sie fortfahren können',
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => 'Plan zur Datenlöschung',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s Zeilen müssen bereinigt werden, bevor Sie fortfahren können',
|
||||
'DataFeatureRemoval:DoDeletion:Title' => 'Löschung durchführen',
|
||||
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Alle Einträge aus der Datenbank entfernen',
|
||||
'DataFeatureRemoval:DeletionPlan:Error:Issues' => 'Einige Objekte müssen vor der Bereinigung manuell gelöscht werden',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Zu entfernendes Element',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Name der Extension',
|
||||
'DataFeatureRemoval:Table:Analysis:Module' => 'Modulname',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurrence' => 'Vorkommen',
|
||||
|
||||
'DataFeatureRemoval:CleanupComplete:Title' => 'Alles bereinigt.',
|
||||
'DataFeatureRemoval:CompilComplete' => 'Kompilierung erfolgreich. Keine Bereinigung erforderlich. Sie können mit dem Setup fortfahren.',
|
||||
|
||||
'DataFeatureRemoval:Compile:InProgress' => 'Kompilierung läuft...',
|
||||
'DataFeatureRemoval:Compile:Success' => 'Kompilierung erfolgreich',
|
||||
'DataFeatureRemoval:Compile:Error' => 'Fehler bei der Kompilierung',
|
||||
|
||||
'DataFeatureRemoval:RunAudit:InProgress' => 'Analyse läuft...',
|
||||
'DataFeatureRemoval:RunAudit:Success' => 'Analyse abgeschlossen',
|
||||
'DataFeatureRemoval:RunAudit:Error' => 'Fehler während der Analyse',
|
||||
|
||||
'UI:Button:Analyze' => 'Analysieren',
|
||||
'UI:Button:ModifyChoices' => 'Auswahl ändern',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analysieren und zum Setup',
|
||||
'UI:Button:PlanDeletion' => 'Mit der Löschung fortfahren',
|
||||
'UI:Button:DoDeletion' => 'Mit der Löschung fortfahren',
|
||||
'UI:Button:BackToMain' => 'Auswahl ändern',
|
||||
'UI:Button:Setup' => 'Setup ausführen',
|
||||
|
||||
'UI:Action:ForceUninstall' => 'Deinstallation erzwingen',
|
||||
'UI:Action:MoreInfo' => 'Weitere Informationen',
|
||||
|
||||
'DataFeatureRemoval:Table:Empty' => 'Keine zu entfernenden Daten',
|
||||
|
||||
'DataFeatureRemoval:Column:Class' => 'Klasse',
|
||||
'DataFeatureRemoval:Column:DeleteCount' => 'Zu löschende Einträge',
|
||||
'DataFeatureRemoval:Column:UpdateCount' => 'Zu aktualisierende Einträge',
|
||||
'DataFeatureRemoval:Column:IssueCount' => 'Gefundene Probleme, die eine automatische Bereinigung verhindern',
|
||||
|
||||
'DataFeatureRemoval:Column:DeletedCount' => 'Gelöschte Einträge',
|
||||
'DataFeatureRemoval:Column:UpdatedCount' => 'Aktualisierte Einträge',
|
||||
]);
|
||||
@@ -20,6 +20,7 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Extensions',
|
||||
'DataFeatureRemoval:Result:Title' => 'Modification requested',
|
||||
'DataFeatureRemoval:NoResult:Title' => 'No modification requested',
|
||||
'DataFeatureRemoval:Execution:Title' => 'Deletion Executions',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:Subtitle' => 'Review all elements requiring attention',
|
||||
@@ -39,6 +40,14 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
'DataFeatureRemoval:CleanupComplete:Title' => 'All clear.',
|
||||
'DataFeatureRemoval:CompilComplete' => 'Compilation successful. No Cleanup needed. You can proceed to setup.',
|
||||
|
||||
'DataFeatureRemoval:Compile:InProgress' => 'Compilation in progress...',
|
||||
'DataFeatureRemoval:Compile:Success' => 'Compilation successful',
|
||||
'DataFeatureRemoval:Compile:Error' => 'Compilation error',
|
||||
|
||||
'DataFeatureRemoval:RunAudit:InProgress' => 'Analysis in progress...',
|
||||
'DataFeatureRemoval:RunAudit:Success' => 'Analysis completed',
|
||||
'DataFeatureRemoval:RunAudit:Error' => 'Error during analysis',
|
||||
|
||||
'UI:Button:Analyze' => 'Analyze',
|
||||
'UI:Button:ModifyChoices' => 'Change my selection',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* Only the entries whose spelling differs from EN US are listed here; every
|
||||
* other string falls back to the EN US dictionary.
|
||||
*/
|
||||
|
||||
Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'DataFeatureRemoval:Helper:Title' => 'Analyse if there are any data or dependency preventing you from adding/removing an extension.',
|
||||
'UI:Button:Analyze' => 'Analyse',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyse and go to setup',
|
||||
]);
|
||||
@@ -20,6 +20,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Extensions',
|
||||
'DataFeatureRemoval:Result:Title' => 'Modification demandée',
|
||||
'DataFeatureRemoval:NoResult:Title' => 'Aucune modification demandée',
|
||||
'DataFeatureRemoval:Execution:Title' => 'Suppressions',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Résultat de l’analyse',
|
||||
'DataFeatureRemoval:Analysis:Subtitle' => 'Vérifier les éléments à nettoyer',
|
||||
@@ -39,6 +40,14 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'DataFeatureRemoval:CleanupComplete:Title' => 'All clear.',
|
||||
'DataFeatureRemoval:CompilComplete' => 'Compilation successful. No Cleanup needed. You can proceed to setup.',
|
||||
|
||||
'DataFeatureRemoval:Compile:InProgress' => 'Compilation en cours...',
|
||||
'DataFeatureRemoval:Compile:Success' => 'Compilation terminée',
|
||||
'DataFeatureRemoval:Compile:Error' => 'Erreur lors de la compilation',
|
||||
|
||||
'DataFeatureRemoval:RunAudit:InProgress' => 'Analyse en cours...',
|
||||
'DataFeatureRemoval:RunAudit:Success' => 'Analyse terminée',
|
||||
'DataFeatureRemoval:RunAudit:Error' => 'Erreur lors de l\'analyse',
|
||||
|
||||
'UI:Button:Analyze' => 'Analyser',
|
||||
'UI:Button:ModifyChoices' => 'Modifier la sélection',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyser et ouvrir l’assistant de configuration',
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
/**
|
||||
* @author Vladimir Kunin <v.b.kunin@gmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Menu:DataFeatureRemovalMenu' => 'Управление расширениями',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => 'Управление расширениями',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => 'Управление расширениями',
|
||||
'DataFeatureRemoval:Main:SubTitle' => 'Включение и отключение расширений, установленных в вашем iTop',
|
||||
'DataFeatureRemoval:Failure:Title' => 'Ошибки пробного удаления расширений',
|
||||
'DataFeatureRemoval:Helper:Title' => 'Проверьте, есть ли данные или зависимости, мешающие добавить/удалить расширение.',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Расширения',
|
||||
'DataFeatureRemoval:Result:Title' => 'Запрошено изменение',
|
||||
'DataFeatureRemoval:NoResult:Title' => 'Изменений не запрошено',
|
||||
'DataFeatureRemoval:Execution:Title' => 'Выполнения удаления',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Результат анализа',
|
||||
'DataFeatureRemoval:Analysis:Subtitle' => 'Просмотрите все элементы, требующие внимания',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => 'Элементов для очистки перед продолжением: %1$s',
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => 'План удаления данных',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => 'Строк для очистки перед продолжением: %1$s',
|
||||
'DataFeatureRemoval:DoDeletion:Title' => 'Выполнить удаление',
|
||||
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Удалить все записи из базы данных',
|
||||
'DataFeatureRemoval:DeletionPlan:Error:Issues' => 'Некоторые объекты нужно удалить вручную перед очисткой',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Элемент для удаления',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Название расширения',
|
||||
'DataFeatureRemoval:Table:Analysis:Module' => 'Название модуля',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurrence' => 'Количество',
|
||||
|
||||
'DataFeatureRemoval:CleanupComplete:Title' => 'Всё чисто.',
|
||||
'DataFeatureRemoval:CompilComplete' => 'Компиляция выполнена успешно. Очистка не требуется. Можно переходить к установке.',
|
||||
|
||||
'DataFeatureRemoval:Compile:InProgress' => 'Идёт компиляция...',
|
||||
'DataFeatureRemoval:Compile:Success' => 'Компиляция выполнена успешно',
|
||||
'DataFeatureRemoval:Compile:Error' => 'Ошибка компиляции',
|
||||
|
||||
'DataFeatureRemoval:RunAudit:InProgress' => 'Идёт анализ...',
|
||||
'DataFeatureRemoval:RunAudit:Success' => 'Анализ завершён',
|
||||
'DataFeatureRemoval:RunAudit:Error' => 'Ошибка при анализе',
|
||||
|
||||
'UI:Button:Analyze' => 'Анализировать',
|
||||
'UI:Button:ModifyChoices' => 'Изменить выбор',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Анализировать и перейти к установке',
|
||||
'UI:Button:PlanDeletion' => 'Продолжить удаление',
|
||||
'UI:Button:DoDeletion' => 'Продолжить удаление',
|
||||
'UI:Button:BackToMain' => 'Изменить выбор',
|
||||
'UI:Button:Setup' => 'Запустить установку',
|
||||
|
||||
'UI:Action:ForceUninstall' => 'Принудительно удалить',
|
||||
'UI:Action:MoreInfo' => 'Подробнее',
|
||||
|
||||
'DataFeatureRemoval:Table:Empty' => 'Нет данных для удаления',
|
||||
|
||||
'DataFeatureRemoval:Column:Class' => 'Класс',
|
||||
'DataFeatureRemoval:Column:DeleteCount' => 'Записей к удалению',
|
||||
'DataFeatureRemoval:Column:UpdateCount' => 'Записей к обновлению',
|
||||
'DataFeatureRemoval:Column:IssueCount' => 'Найдено проблем, мешающих автоматической очистке',
|
||||
|
||||
'DataFeatureRemoval:Column:DeletedCount' => 'Удалено записей',
|
||||
'DataFeatureRemoval:Column:UpdatedCount' => 'Обновлено записей',
|
||||
]);
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Menu:DataFeatureRemovalMenu' => '扩展管理',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => '扩展管理',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => '扩展管理',
|
||||
'DataFeatureRemoval:Main:SubTitle' => '切换安装在您的 iTop 上的扩展',
|
||||
'DataFeatureRemoval:Failure:Title' => '扩展预删除错误',
|
||||
'DataFeatureRemoval:Helper:Title' => '分析是否有任何数据或依赖关系阻止您添加/删除扩展。',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => '扩展',
|
||||
'DataFeatureRemoval:Result:Title' => '请求的修改',
|
||||
'DataFeatureRemoval:Execution:Title' => '删除执行',
|
||||
'DataFeatureRemoval:Analysis:Title' => '分析结果',
|
||||
'DataFeatureRemoval:Analysis:Subtitle' => '审查所有需要关注的元素',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s 个元素需要在继续之前清理',
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => '数据删除计划',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s 行需要在继续之前清理',
|
||||
'DataFeatureRemoval:DoDeletion:Title' => '执行删除',
|
||||
'DataFeatureRemoval:DoDeletion:SubTitle' => '从数据库中删除所有条目',
|
||||
'DataFeatureRemoval:DeletionPlan:Error:Issues' => '某些对象必须在清理前手动删除',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => '要删除的元素',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => '扩展名称',
|
||||
'DataFeatureRemoval:Table:Analysis:Module' => '模块名称',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurrence' => '出现次数',
|
||||
|
||||
'DataFeatureRemoval:CleanupComplete:Title' => '全部清除.',
|
||||
'DataFeatureRemoval:CompilComplete' => '编译成功. 无需清理. 您可以继续进行设置.',
|
||||
|
||||
'UI:Button:Analyze' => '分析',
|
||||
'UI:Button:ModifyChoices' => '改变我的选择',
|
||||
'UI:Button:AnalyzeAndSetup' => '分析并进入设置',
|
||||
'UI:Button:PlanDeletion' => '继续删除',
|
||||
'UI:Button:DoDeletion' => '继续删除',
|
||||
'UI:Button:BackToMain' => '改变我的选择',
|
||||
'UI:Button:Setup' => '运行安装向导',
|
||||
|
||||
'UI:Action:ForceUninstall' => '强制卸载',
|
||||
'UI:Action:MoreInfo' => '更多信息',
|
||||
|
||||
'DataFeatureRemoval:Table:Empty' => '没有数据需要删除',
|
||||
|
||||
'DataFeatureRemoval:Column:Class' => '类',
|
||||
'DataFeatureRemoval:Column:DeleteCount' => '待删除的条目',
|
||||
'DataFeatureRemoval:Column:UpdateCount' => '待更新的条目',
|
||||
'DataFeatureRemoval:Column:IssueCount' => '发现阻止自动清理的问题',
|
||||
|
||||
'DataFeatureRemoval:Column:DeletedCount' => '已删除的条目',
|
||||
'DataFeatureRemoval:Column:UpdatedCount' => '已更新的条目',
|
||||
]);
|
||||
@@ -16,7 +16,6 @@ use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
|
||||
use Combodo\iTop\Service\Session\SessionParameters;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
@@ -24,6 +23,7 @@ use ContextTag;
|
||||
use CoreException;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use iTopExtension;
|
||||
use MetaModel;
|
||||
use MFCompiler;
|
||||
use RunTimeEnvironment;
|
||||
@@ -47,7 +47,11 @@ class DataFeatureRemovalController extends Controller
|
||||
$aParams = [];
|
||||
|
||||
SetupUtils::EraseSetupToken();
|
||||
(new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME))->Erase();
|
||||
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
|
||||
$oParameters->Erase();
|
||||
Session::Unset('aDeletionExecutionSummary');
|
||||
Session::Set('bForceCompilation', true);
|
||||
$oParameters->SetParameter('return_application', 'DataFeatureRemoval');
|
||||
|
||||
$this->AddAnalyzeParams();
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
@@ -60,7 +64,6 @@ class DataFeatureRemovalController extends Controller
|
||||
$aParams['sSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup';
|
||||
$aParams['iCount'] = $this->iCount;
|
||||
|
||||
Session::Set('bForceCompilation', true);
|
||||
$this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css');
|
||||
$this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js');
|
||||
$this->DisplayPage($aParams);
|
||||
@@ -73,7 +76,7 @@ class DataFeatureRemovalController extends Controller
|
||||
$this->iCount = 0;
|
||||
foreach ($this->aCountClassesToCleanup as $sClass => $iCount) {
|
||||
$sModuleName = MetaModel::GetModuleName($sClass);
|
||||
$aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName);
|
||||
$aExtensions = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->GetIncludingExtensions($sModuleName);
|
||||
$sExtensions = implode(' ', $aExtensions);
|
||||
$aColumns = ['ClassName','FeatureName','Module','Occurrence'];
|
||||
$aData[] = [$sClass,$sExtensions,$sModuleName,$iCount];
|
||||
@@ -94,7 +97,7 @@ class DataFeatureRemovalController extends Controller
|
||||
}
|
||||
|
||||
// Display changed extensions
|
||||
$aHiddenInputNames = [
|
||||
$aSetupParameterNames = [
|
||||
'selected_extensions' => '[]',
|
||||
'selected_modules' => '[]',
|
||||
'display_choices' => '',
|
||||
@@ -108,95 +111,38 @@ class DataFeatureRemovalController extends Controller
|
||||
'target_env' => ITOP_DEFAULT_ENV,
|
||||
];
|
||||
|
||||
$aHiddenInputs = [];
|
||||
foreach ($aHiddenInputNames as $sInputName => $defaultValue) {
|
||||
$aHiddenInputs[$sInputName] = utils::ReadPostedParam($sInputName, $defaultValue, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
|
||||
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
|
||||
foreach ($aSetupParameterNames as $sInputName => $defaultValue) {
|
||||
$oParameters->SetParameter($sInputName, $oParameters->GetParameter($sInputName, $defaultValue));
|
||||
}
|
||||
$aParams['aHiddenInputs'] = $aHiddenInputs;
|
||||
|
||||
$aAddedExtensions = json_decode($aHiddenInputs['added_extensions'], true);
|
||||
|
||||
$aRemovedExtensions = json_decode($aHiddenInputs['removed_extensions'], true);
|
||||
if ("[]" === $aHiddenInputs['selected_modules']) {
|
||||
$aAddedExtensions = json_decode($oParameters->GetParameter('added_extensions', '[]'), true);
|
||||
$aRemovedExtensions = json_decode($oParameters->GetParameter('removed_extensions', '[]'), true);
|
||||
if ('[]' === $oParameters->GetParameter('selected_modules', '[]')) {
|
||||
//it does not come from setup
|
||||
// we get extensions from 1st screen uiblocks
|
||||
$this->ReadExtensionsDiff();
|
||||
$aHiddenInputs['force-uninstall'] = $this->bForcedUninstallation ? 'on' : '';
|
||||
$oParameters->SetParameter('force-uninstall', $this->bForcedUninstallation ? 'on' : '');
|
||||
$aAddedExtensions = $this->aExtensionsToCheck['to_be_installed'];
|
||||
$aHiddenInputs['added_extensions'] = $this->ConvertIntoSetupFormat($aAddedExtensions);
|
||||
$oParameters->SetParameter('added_extensions', $this->ConvertIntoSetupFormat($aAddedExtensions));
|
||||
|
||||
$aRemovedExtensions = $this->aExtensionsToCheck['to_be_removed'];
|
||||
$aHiddenInputs['removed_extensions'] = $this->ConvertIntoSetupFormat($aRemovedExtensions);
|
||||
$oParameters->SetParameter('removed_extensions', $this->ConvertIntoSetupFormat($aRemovedExtensions));
|
||||
|
||||
$aExtensionsNotUninstallable = $this->aExtensionsToCheck['extensions_not_uninstallable'];
|
||||
$aHiddenInputs['extensions_not_uninstallable'] = $this->ConvertIntoSetupFormat($aExtensionsNotUninstallable);
|
||||
$oParameters->SetParameter('extensions_not_uninstallable', $this->ConvertIntoSetupFormat($aExtensionsNotUninstallable));
|
||||
}
|
||||
|
||||
$aParams['aAddedExtensions'] = $aAddedExtensions;
|
||||
$aParams['aRemovedExtensions'] = $aRemovedExtensions;
|
||||
|
||||
DataFeatureRemovalLog::Debug(__METHOD__.' Extensions given in parameter', null, [
|
||||
'added_extensions' => $aAddedExtensions,
|
||||
'removed_extensions' => $aRemovedExtensions]);
|
||||
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$aParams['iColumnCount'] = $this->iColumnCount;
|
||||
$aParams['aAvailableExtensions'] = $this->SplitArrayIntoColumns($this->GetExtensionsDiff($aAddedExtensions, $aRemovedExtensions), $this->iColumnCount);
|
||||
|
||||
//to make setup redirection work, we need to pass complex data structures to setup wizards (ie extension/module lists)
|
||||
$sSourceEnv = MetaModel::GetEnvironment();
|
||||
$this->oRuntimeEnvironment = new RunTimeEnvironment($sSourceEnv, false);
|
||||
|
||||
if ('[]' === $aHiddenInputs['selected_modules']) {
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$aSelectedExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions));
|
||||
$aHiddenInputs['selected_extensions'] = $this->ConvertIntoSetupFormat($aSelectedExtensions);
|
||||
|
||||
$aSelectedModules = []; // keep it to compile method
|
||||
} else {
|
||||
$aSelectedExtensions = json_decode($aHiddenInputs['selected_extensions'], true);
|
||||
$aSelectedModules = json_decode($aHiddenInputs['selected_modules'], true);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->Compile($aSelectedExtensions, array_keys($aRemovedExtensions), $aSelectedModules);
|
||||
$aHiddenInputs['selected_modules'] = $this->ConvertIntoSetupFormat($aSelectedModules);
|
||||
} catch (CoreException $e) {
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $e->getHtmlDesc();
|
||||
$this->DisplayPage($aParams, 'AnalysisResult');
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $e->getMessage();
|
||||
$this->DisplayPage($aParams, 'AnalysisResult');
|
||||
return;
|
||||
}
|
||||
|
||||
$oSetupAudit = new SetupAudit($sSourceEnv);
|
||||
$aGetRemovedClasses = array_keys($oSetupAudit->RunDataAudit());
|
||||
DataFeatureRemovalLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
|
||||
|
||||
$aParams['aClasses'] = $aGetRemovedClasses;
|
||||
|
||||
new ContextTag(ContextTag::TAG_SETUP);
|
||||
$aParams['sLaunchSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup/wizard.php';
|
||||
$aParams['aSetupParams'] = [
|
||||
"_class" => "WizStepLandingBeforeAudit",
|
||||
"operation" => "next",
|
||||
];
|
||||
|
||||
foreach ($aHiddenInputs as $sInputName => $sInputValue) {
|
||||
$aParams['aSetupParams'][$sInputName] = $sInputValue;
|
||||
}
|
||||
|
||||
[$aParams['aDeletionPlanSummary'], $aParams['iQueryCount'], $aParams['bDeletionPossible']] = $this->GetDeletionPlanSummaryTable($aGetRemovedClasses);
|
||||
[$aParams['aDeletionExecutionSummary'], $aParams['bHasDeletionExecution']] = $this->GetExecutionSummaryTable();
|
||||
$aParams['bDeletionNeeded'] = ($aParams['iQueryCount'] > 0);
|
||||
Session::Set('aDeletionExecutionSummary', serialize($this->aDeletionExecutionSummary));
|
||||
|
||||
if (!$aParams['bDeletionNeeded']) {
|
||||
// Erase session setup parameters
|
||||
SetupUtils::CreateSetupToken();
|
||||
}
|
||||
$aAvailableExtensions = $this->GetExtensionsDiff($aAddedExtensions, $aRemovedExtensions);
|
||||
$aParams['aAvailableExtensionsCount'] = count($aAvailableExtensions);
|
||||
$aParams['aAvailableExtensions'] = $this->SplitArrayIntoColumns($aAvailableExtensions, $this->iColumnCount);
|
||||
$aParams['sAjaxURL'] = utils::GetAbsoluteUrlModulePage(DataFeatureRemovalHelper::MODULE_NAME, 'index.php');
|
||||
|
||||
$this->DisplayPage($aParams, 'AnalysisResult');
|
||||
}
|
||||
@@ -204,55 +150,137 @@ class DataFeatureRemovalController extends Controller
|
||||
private function ConvertIntoSetupFormat(array $aData): string
|
||||
{
|
||||
$aNewData = [];
|
||||
foreach ($aData as $k => $sVal) {
|
||||
$aNewData[] = sprintf('"%s":"%s"', $k, $sVal);
|
||||
foreach ($aData as $sCode => $sLabel) {
|
||||
$aNewData[] = sprintf('"%s":"%s"', $sCode, $sLabel);
|
||||
}
|
||||
|
||||
return "{".implode(',', $aNewData)."}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aSelectedExtensions
|
||||
* @param array $aRemovedExtensions
|
||||
* @param array $aSelectedModules
|
||||
*
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private function Compile(array $aSelectedExtensions, array $aRemovedExtensions, array &$aSelectedModules): void
|
||||
public function OperationAjaxCompile(): void
|
||||
{
|
||||
$aParams = [];
|
||||
//to make setup redirection work, we need to pass complex data structures to setup wizards (ie extension/module lists)
|
||||
$sSourceEnv = MetaModel::GetEnvironment();
|
||||
$sBuildDir = APPROOT."/env-$sSourceEnv-build";
|
||||
if (! is_dir($sBuildDir)) {
|
||||
SetupUtils::builddir($sBuildDir);
|
||||
}
|
||||
$bIsDirEmpty = count(scandir($sBuildDir)) === 2;
|
||||
$bForceCompilation = Session::Get('bForceCompilation', false);
|
||||
$oRuntimeEnvironment = new RunTimeEnvironment($sSourceEnv, false);
|
||||
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
if ($bIsDirEmpty || $bForceCompilation) {
|
||||
Session::Unset('bForceCompilation');
|
||||
$this->oRuntimeEnvironment->CopySetupFiles();
|
||||
if (count($aSelectedModules) === 0) {
|
||||
$aSelectedModules = $this->oRuntimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions);
|
||||
}
|
||||
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
|
||||
$aSelectedModules = json_decode($oParameters->GetParameter('selected_modules', '[]'), true);
|
||||
$aSelectedExtensions = json_decode($oParameters->GetParameter('selected_extensions', '[]'), true);
|
||||
$aAddedExtensions = json_decode($oParameters->GetParameter('added_extensions', '[]'), true);
|
||||
$aRemovedExtensions = json_decode($oParameters->GetParameter('removed_extensions', '[]'), true);
|
||||
|
||||
try {
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
DataFeatureRemovalLog::Debug(
|
||||
__METHOD__,
|
||||
__METHOD__.": extension lists",
|
||||
null,
|
||||
['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")]
|
||||
['removed_extensions' => $aRemovedExtensions,
|
||||
'selected_extensions' => $aSelectedExtensions,
|
||||
'added_extensions' => $aAddedExtensions,
|
||||
]
|
||||
);
|
||||
$this->oRuntimeEnvironment->DoCompile($aSelectedExtensions, $aRemovedExtensions, $aSelectedModules, MFCompiler::CanUseSymbolicLinks());
|
||||
} else {
|
||||
|
||||
if (count($aSelectedModules) === 0) {
|
||||
$aSelectedModules = $this->oRuntimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions);
|
||||
$aSelectedExtensions = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions));
|
||||
$oParameters->SetParameter('selected_extensions', $this->ConvertIntoSetupFormat($aSelectedExtensions));
|
||||
}
|
||||
|
||||
$sBuildDir = APPROOT."/env-$sSourceEnv-build";
|
||||
if (! is_dir($sBuildDir)) {
|
||||
SetupUtils::builddir($sBuildDir);
|
||||
}
|
||||
$bIsDirEmpty = count(scandir($sBuildDir)) === 2;
|
||||
$bForceCompilation = Session::Get('bForceCompilation', false);
|
||||
if ($bIsDirEmpty || $bForceCompilation) {
|
||||
$oRuntimeEnvironment->CopySetupFiles();
|
||||
if (count($aSelectedModules) === 0) {
|
||||
$oExtensionsMap = \iTopExtensionsMap::GetExtensionsMap($oRuntimeEnvironment->GetBuildEnv());
|
||||
// Removed modules are stored as static for FindModules()
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensions);
|
||||
$aSelectedModules = $oRuntimeEnvironment->GetModulesToLoadFromSelectedExtensions($oConfig, $aSelectedExtensions);
|
||||
}
|
||||
|
||||
DataFeatureRemovalLog::Debug(
|
||||
__METHOD__,
|
||||
null,
|
||||
['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")]
|
||||
);
|
||||
$oRuntimeEnvironment->DoCompile($aSelectedExtensions, $aRemovedExtensions, $aSelectedModules, MFCompiler::CanUseSymbolicLinks());
|
||||
Session::Unset('bForceCompilation');
|
||||
} else {
|
||||
if (count($aSelectedModules) === 0) {
|
||||
$aSelectedModules = $oRuntimeEnvironment->GetModulesToLoadFromSelectedExtensions($oConfig, $aSelectedExtensions);
|
||||
}
|
||||
}
|
||||
DataFeatureRemovalLog::Debug(__METHOD__.": modules", null, ['selected_modules' => $aSelectedModules]);
|
||||
} catch (CoreException $e) {
|
||||
$aParams['error_message'] = $e->getHtmlDesc();
|
||||
} catch (Exception $e) {
|
||||
$aParams['error_message'] = $e->getMessage();
|
||||
}
|
||||
|
||||
$aParams['success_message'] = Dict::S('DataFeatureRemoval:Compile:Success');
|
||||
$aParams['transaction_id'] = utils::GetNewTransactionId();
|
||||
|
||||
$oParameters->SetParameter('selected_modules', json_encode($aSelectedModules));
|
||||
$this->DisplayJSONPage($aParams);
|
||||
}
|
||||
|
||||
public function OperationAjaxRunAudit(): void
|
||||
{
|
||||
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
|
||||
$aParams = [];
|
||||
$aPageParams = [];
|
||||
try {
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$sSourceEnv = MetaModel::GetEnvironment();
|
||||
$oSetupAudit = new SetupAudit($sSourceEnv);
|
||||
$aRemovedClasses = array_keys($oSetupAudit->RunDataAudit());
|
||||
$oParameters->SetParameter('classes', $aRemovedClasses);
|
||||
|
||||
new ContextTag(ContextTag::TAG_SETUP);
|
||||
$aPageParams['sLaunchSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup/wizard.php';
|
||||
$aPageParams['aSetupParams'] = [
|
||||
"_class" => "WizStepLandingBeforeAudit",
|
||||
"operation" => "next",
|
||||
];
|
||||
$aPageParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
|
||||
$aDeletionPlanSummaryEntities = $this->GetDeletionPlanSummaryEntities($aRemovedClasses);
|
||||
[$aPageParams['aDeletionPlanSummary'], $aPageParams['iQueryCount'], $aPageParams['bDeletionPossible']] = $this->GetDeletionPlanSummaryTable($aDeletionPlanSummaryEntities);
|
||||
[$aPageParams['aDeletionExecutionSummary'], $aPageParams['bHasDeletionExecution']] = $this->GetExecutionSummaryTable();
|
||||
$aPageParams['bDeletionNeeded'] = ($aPageParams['iQueryCount'] > 0);
|
||||
|
||||
if (!$aPageParams['bDeletionNeeded']) {
|
||||
// Erase session setup parameters
|
||||
SetupUtils::CreateSetupToken();
|
||||
}
|
||||
|
||||
$this->DisplayAjaxPage($aPageParams, 'AjaxRunAudit');
|
||||
return;
|
||||
} catch (CoreException $e) {
|
||||
$aParams['error_message'] = $e->getHtmlDesc();
|
||||
} catch (Exception $e) {
|
||||
$aParams['error_message'] = $e->getMessage();
|
||||
}
|
||||
|
||||
$this->DisplayJSONPage($aParams);
|
||||
}
|
||||
|
||||
private function GetExecutionSummaryTable(): array
|
||||
{
|
||||
$sName = 'ExcutionSummary';
|
||||
$sName = 'ExecutionSummary';
|
||||
|
||||
$this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary') ?? serialize([]));
|
||||
|
||||
$aTableData = [];
|
||||
if (count($this->aDeletionExecutionSummary) === 0) {
|
||||
@@ -278,11 +306,15 @@ class DataFeatureRemovalController extends Controller
|
||||
|
||||
}
|
||||
|
||||
private function GetDeletionPlanSummaryTable(array $aRemovedClasses): array
|
||||
private function GetDeletionPlanSummaryEntities(array $aRemovedClasses): array
|
||||
{
|
||||
$oDataCleanupService = new StaticDeletionPlan();
|
||||
return $oDataCleanupService->GetCleanupSummary($aRemovedClasses);
|
||||
}
|
||||
|
||||
private function GetDeletionPlanSummaryTable(array $aDeletionPlanSummaryEntities): array
|
||||
{
|
||||
$sName = 'DeletionPlanSummary';
|
||||
$oDataCleanupService = new StaticDeletionPlan();
|
||||
$aDeletionPlanSummaryEntities = $oDataCleanupService->GetCleanupSummary($aRemovedClasses);
|
||||
$aColumns = ['Class', 'Delete Count' , 'Update Count', 'Issue Count'];
|
||||
$aRows = [];
|
||||
$iQueryCount = 0;
|
||||
@@ -305,9 +337,10 @@ class DataFeatureRemovalController extends Controller
|
||||
{
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary'));
|
||||
$this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary') ?? serialize([]));
|
||||
Session::Unset('aDeletionExecutionSummary');
|
||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
|
||||
$aClasses = $oParameters->GetParameter('classes', []);
|
||||
|
||||
$oDataCleanupService = new DataCleanupService();
|
||||
$aDeletionExecutionSummary = $oDataCleanupService->ExecuteCleanup($aClasses);
|
||||
@@ -322,6 +355,7 @@ class DataFeatureRemovalController extends Controller
|
||||
$oSummary->iTotalUpdateCount += $oExecutionSummary->iUpdateCount;
|
||||
}
|
||||
|
||||
Session::Set('aDeletionExecutionSummary', serialize($this->aDeletionExecutionSummary));
|
||||
$this->OperationAnalysisResult();
|
||||
}
|
||||
|
||||
@@ -329,9 +363,9 @@ class DataFeatureRemovalController extends Controller
|
||||
{
|
||||
$aExtensionsData = [];
|
||||
if ($bIncludePackageExtensions) {
|
||||
$aExtensionsRef = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetAllExtensionsWithPreviouslyInstalled();
|
||||
$aExtensionsRef = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->GetExtensionMap()->GetAllExtensionsWithPreviouslyInstalled();
|
||||
} else {
|
||||
$aExtensionsRef = DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions();
|
||||
$aExtensionsRef = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->ReadItopExtensions();
|
||||
}
|
||||
|
||||
foreach ($aExtensionsRef as $oExtension) {
|
||||
@@ -415,12 +449,11 @@ class DataFeatureRemovalController extends Controller
|
||||
|
||||
/**
|
||||
* Read extensions selected from posted parameters
|
||||
* @return int Number of extensions to be added or removed
|
||||
*/
|
||||
public function ReadExtensionsDiff(): int
|
||||
public function ReadExtensionsDiff(): void
|
||||
{
|
||||
if (!is_null($this->aExtensionsToCheck)) {
|
||||
return count($this->aExtensionsToCheck['to_be_installed']) + count($this->aExtensionsToCheck['to_be_removed']);
|
||||
return;
|
||||
}
|
||||
|
||||
$aAvailableExtensions = $this->GetAvailableExtensions();
|
||||
@@ -431,13 +464,14 @@ class DataFeatureRemovalController extends Controller
|
||||
'extensions_not_uninstallable' => [],
|
||||
];
|
||||
foreach ($aAvailableExtensions as $sCode => &$aExtensionData) {
|
||||
if (!isset($aSelectedExtensionsFromUI[$sCode])) {
|
||||
$sCodeFromUI = $sCode !== '' ? $sCode : iTopExtension::CODE_NO_CODE;
|
||||
if (!isset($aSelectedExtensionsFromUI[$sCodeFromUI])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] !== 'on') {
|
||||
if ($aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCodeFromUI] !== 'on') {
|
||||
$aExtensionData['extra_flags']['selected'] = false;
|
||||
$sLabel = $aAvailableExtensions[$sCode]['label'];
|
||||
$sLabel = $aExtensionData['label'];
|
||||
$this->aExtensionsToCheck['to_be_removed'][$sCode] = $sLabel;
|
||||
if (! $this->bForcedUninstallation && $aExtensionData['extra_flags']['uninstallable']) {
|
||||
$this->bForcedUninstallation = true;
|
||||
@@ -445,13 +479,12 @@ class DataFeatureRemovalController extends Controller
|
||||
if (false === $aExtensionData['extra_flags']['uninstallable'] || true === $aExtensionData['extra_flags']['remote']) {
|
||||
$this->aExtensionsToCheck['extensions_not_uninstallable'][] = $sCode;
|
||||
}
|
||||
} elseif (!$aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] === 'on') {
|
||||
} elseif (!$aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCodeFromUI] === 'on') {
|
||||
$aExtensionData['extra_flags']['selected'] = true;
|
||||
$sLabel = $aAvailableExtensions[$sCode]['label'];
|
||||
$sLabel = $aExtensionData['label'];
|
||||
$this->aExtensionsToCheck['to_be_installed'][$sCode] = $sLabel;
|
||||
}
|
||||
}
|
||||
return count($this->aExtensionsToCheck['to_be_installed']) + count($this->aExtensionsToCheck['to_be_removed']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,16 +13,11 @@ class DeletionPlanEntity
|
||||
public readonly DeletionPlanItem $oUpdate;
|
||||
public readonly DeletionPlanItem $oIssue;
|
||||
|
||||
/**
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oDelete
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oUpdate
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oIssue
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->oDelete = $oDelete ?? new DeletionPlanItem();
|
||||
$this->oUpdate = $oUpdate ?? new DeletionPlanItem();
|
||||
$this->oIssue = $oIssue ?? new DeletionPlanItem();
|
||||
$this->oDelete = new DeletionPlanItem();
|
||||
$this->oUpdate = new DeletionPlanItem();
|
||||
$this->oIssue = new DeletionPlanItem();
|
||||
}
|
||||
|
||||
public function TotalCount(): int
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
class DataFeatureRemovalConfig
|
||||
{
|
||||
private static DataFeatureRemovalConfig $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DataFeatureRemovalConfig
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new DataFeatureRemovalConfig();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
public function Get(string $sParamName, $default = null)
|
||||
{
|
||||
return MetaModel::GetModuleSetting(DataFeatureRemovalHelper::MODULE_NAME, $sParamName, $default);
|
||||
}
|
||||
|
||||
public function GetBoolean(string $sParamName, $default = null): bool
|
||||
{
|
||||
$res = $this->Get($sParamName, $default);
|
||||
|
||||
return boolval($res);
|
||||
}
|
||||
|
||||
public function IsEnabled(): bool
|
||||
{
|
||||
return $this->GetBoolean('enable', false);
|
||||
}
|
||||
|
||||
public function Set(string $sParamName, $value)
|
||||
{
|
||||
$oConfig = utils::GetConfig();
|
||||
$oConfig->SetModuleSetting(DataFeatureRemovalHelper::MODULE_NAME, $sParamName, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Hook;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\Events\iEventServiceSetup;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
class EventListener implements iEventServiceSetup
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function RegisterEventsAndListeners()
|
||||
{
|
||||
EventService::RegisterListener(
|
||||
sEvent: \EVENT_SERVICE_LOCATOR_INITIALIZED,
|
||||
callback: [$this, 'OnServiceLocatorInitialized']
|
||||
);
|
||||
}
|
||||
|
||||
public function OnServiceLocatorInitialized(EventData $oEventData): void
|
||||
{
|
||||
MetaModel::GetServiceLocator()->AddClassesFromFile(utils::GetAbsoluteModulePath(DataFeatureRemovalHelper::MODULE_NAME).'Config/ServiceLocator.xml');
|
||||
}
|
||||
}
|
||||
@@ -13,34 +13,19 @@ use MetaModel;
|
||||
|
||||
class DataFeatureRemoverExtensionService
|
||||
{
|
||||
private static DataFeatureRemoverExtensionService $oInstance;
|
||||
private ?iTopExtensionsMap $oMap = null;
|
||||
private array $aItopExtensions = [];
|
||||
private array $aIncludingExtensionsByModuleName = [];
|
||||
|
||||
protected function __construct()
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DataFeatureRemoverExtensionService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new DataFeatureRemoverExtensionService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?DataFeatureRemoverExtensionService $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sModuleName
|
||||
*
|
||||
* @return array
|
||||
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetIncludingExtensions(string $sModuleName): array
|
||||
{
|
||||
@@ -62,6 +47,7 @@ class DataFeatureRemoverExtensionService
|
||||
|
||||
/**
|
||||
* @return \iTopExtensionsMap
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetExtensionMap(): iTopExtensionsMap
|
||||
{
|
||||
@@ -74,6 +60,7 @@ class DataFeatureRemoverExtensionService
|
||||
|
||||
/**
|
||||
* @return iTopExtension[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ReadItopExtensions(): array
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% if bDeletionNeeded %}
|
||||
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:DeletionPlan:Title'|dict_s} %}
|
||||
{% UIDataTable ForForm { sRef:'aDeletionPlanSummary', aColumns:aDeletionPlanSummary.Columns, aData:aDeletionPlanSummary.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIFieldSet %}
|
||||
{% if bDeletionPossible %}
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'DoDeletion'} %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
{% else %}
|
||||
{% UIAlert ForFailure { sTitle: '', sContent: 'DataFeatureRemoval:DeletionPlan:Error:Issues'|dict_s } %}{% EndUIAlert %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% UIAlert ForSuccess { sTitle:'DataFeatureRemoval:CleanupComplete:Title'|dict_s, sContent:'DataFeatureRemoval:CompilComplete'|dict_s } %}{% EndUIAlert %}
|
||||
|
||||
{% UIForm Standard {sId:'launch-setup-form', Action:sLaunchSetupUrl, EncType: 'application/x-www-form-urlencoded'} %}
|
||||
{% for sKey, sValue in aSetupParams %}
|
||||
{% UIInput ForHidden { sName:sKey, sValue:sValue } %}
|
||||
{% endfor %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Setup'|dict_s, sName:'btn_setup', sId:'btn_setup', bIsSubmit:true} %}
|
||||
{% EndUIForm %}
|
||||
{% endif %}
|
||||
|
||||
{% if bHasDeletionExecution %}
|
||||
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:Execution:Title'|dict_s} %}
|
||||
{% UIDataTable ForForm { sRef:'aDeletionExecutionSummary', aColumns:aDeletionExecutionSummary.Columns, aData:aDeletionExecutionSummary.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIFieldSet %}
|
||||
{% endif %}
|
||||
@@ -1,92 +1,50 @@
|
||||
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
|
||||
|
||||
{% UIPanel ForInformation { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:Subtitle'|dict_s} %}
|
||||
{% if null != DataFeatureRemovalErrorMessage %}
|
||||
<div id="feature_removal_error_msg_div" style="display:block">
|
||||
{% UIAlert ForFailure { sTitle:'DataFeatureRemoval:Failure:Title'|dict_s, sId: 'feature_removal_error_msg', sContent:DataFeatureRemovalErrorMessage } %}
|
||||
{% EndUIAlert %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% UIAlert ForInformation { sTitle: '', sId: 'ajax_compile_in_progress_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}
|
||||
{{ 'DataFeatureRemoval:Compile:InProgress'|dict_s }}
|
||||
{% UISpinner Standard { } %}
|
||||
{% EndUIAlert %}
|
||||
{% UIAlert ForSuccess { sTitle:'success', sId: 'ajax_compile_success_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
|
||||
{% UIAlert ForFailure { sTitle:'error', sId: 'ajax_compile_error_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
|
||||
|
||||
{% UIAlert ForInformation { sTitle: '', sId: 'ajax_run_audit_in_progress_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}
|
||||
{{ 'DataFeatureRemoval:RunAudit:InProgress'|dict_s }}
|
||||
{% UISpinner Standard { } %}
|
||||
{% EndUIAlert %}
|
||||
{% UIAlert ForSuccess { sTitle:'DataFeatureRemoval:RunAudit:Success'|dict_s, sId: 'ajax_run_audit_success_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
|
||||
{% UIAlert ForFailure { sTitle:'error', sId: 'ajax_run_audit_error_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
|
||||
|
||||
{% if aAvailableExtensionsCount == 0 %}
|
||||
{% UITitle Neutral { sTitle:'DataFeatureRemoval:NoResult:Title'|dict_s, iLevel:2 } %}{% EndUITitle %}
|
||||
{% else %}
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Result:Title'|dict_s, sSubTitle: '' } %}
|
||||
{% UIMultiColumn Standard {} %}
|
||||
{% for iColumnIndex in 0..iColumnCount-1 %}
|
||||
{% UIColumn Standard {} %}
|
||||
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
|
||||
{% if aExtension['installed'] %}
|
||||
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% else %}
|
||||
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% EndUIColumn %}
|
||||
{% endfor %}
|
||||
{% EndUIMultiColumn %}
|
||||
{% EndUIPanel %}
|
||||
{% else %}
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Result:Title'|dict_s, sSubTitle: '' } %}
|
||||
{% UIMultiColumn Standard {} %}
|
||||
{% for iColumnIndex in 0..iColumnCount-1 %}
|
||||
{% UIColumn Standard {} %}
|
||||
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
|
||||
{% if aExtension['installed'] %}
|
||||
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% else %}
|
||||
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% EndUIColumn %}
|
||||
{% endfor %}
|
||||
{% EndUIMultiColumn %}
|
||||
{% EndUIPanel %}
|
||||
|
||||
{% if bDeletionNeeded %}
|
||||
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:DeletionPlan:Title'|dict_s} %}
|
||||
{% UIDataTable ForForm { sRef:'aDeletionPlanSummary', aColumns:aDeletionPlanSummary.Columns, aData:aDeletionPlanSummary.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIFieldSet %}
|
||||
{% if bDeletionPossible %}
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'DoDeletion'} %}
|
||||
{% for sKey, sClass in aClasses %}
|
||||
{% UIInput ForHidden { sName:"classes[" ~ sKey ~ "]", sValue:sClass } %}
|
||||
{% endfor %}
|
||||
{% for sCode, sLabel in aAddedExtensions %}
|
||||
{% UIInput ForHidden { sName:"aAddedExtensions[" ~ sCode ~ "]", sValue:sLabel } %}
|
||||
{% endfor %}
|
||||
{% for sCode, sLabel in aRemovedExtensions %}
|
||||
{% UIInput ForHidden { sName:"aRemovedExtensions[" ~ sCode ~ "]", sValue:sLabel } %}
|
||||
{% endfor %}
|
||||
{% for sInputName, sValue in aHiddenInputs %}
|
||||
{% UIInput ForHidden { sName:sInputName, sValue:sValue } %}
|
||||
{% endfor %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
{% else %}
|
||||
{% UIAlert ForFailure { sContent: 'DataFeatureRemoval:DeletionPlan:Error:Issues'|dict_s } %}{% EndUIAlert %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% UIAlert ForSuccess { sTitle:'DataFeatureRemoval:CleanupComplete:Title'|dict_s, sContent:'DataFeatureRemoval:CompilComplete'|dict_s, sId:value } %}{% EndUIAlert %}
|
||||
|
||||
{% UIForm Standard {'sId':'launch-setup-form', Action:sLaunchSetupUrl, 'EncType': 'application/x-www-form-urlencoded'} %}
|
||||
{% for sKey, sValue in aSetupParams %}
|
||||
{% UIInput ForHidden { sName:sKey, sValue:sValue } %}
|
||||
{% endfor %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Setup'|dict_s, sName:'btn_setup', sId:'btn_setup', bIsSubmit:true} %}
|
||||
{% EndUIForm %}
|
||||
{% endif %}
|
||||
|
||||
{% if bHasDeletionExecution %}
|
||||
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:Execution:Title'|dict_s} %}
|
||||
{% UIDataTable ForForm { sRef:'aDeletionExecutionSummary', aColumns:aDeletionExecutionSummary.Columns, aData:aDeletionExecutionSummary.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIFieldSet %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div id="ajax_run_audit" class="ibo-block"></div>
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'Main'} %}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
function ajax_run_audit(sTransactionId) {
|
||||
$('#ajax_run_audit_in_progress_msg').removeClass('ibo-is-hidden');
|
||||
$.post(
|
||||
'{{ sAjaxURL|raw }}',
|
||||
{ operation: 'ajax_run_audit', transaction_id: sTransactionId },
|
||||
function (data) {
|
||||
$('#ajax_run_audit_in_progress_msg').addClass('ibo-is-hidden');
|
||||
|
||||
if (data.error_message) {
|
||||
$('#ajax_run_audit_error_msg .ibo-alert--title').html(data.error_message);
|
||||
$('#ajax_run_audit_error_msg').removeClass('ibo-is-hidden');
|
||||
} else {
|
||||
$('#ajax_run_audit_success_msg').removeClass('ibo-is-hidden');
|
||||
$('#ajax_run_audit').html(data);
|
||||
}
|
||||
}
|
||||
)
|
||||
.fail(function() {
|
||||
$('#ajax_run_audit_in_progress_msg').addClass('ibo-is-hidden');
|
||||
$('#ajax_run_audit_error_msg .ibo-alert--title').html('{{ 'DataFeatureRemoval:RunAudit:Error'|dict_s }}');
|
||||
$('#ajax_run_audit_error_msg').removeClass('ibo-is-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function ajax_compile() {
|
||||
$('#ajax_compile_in_progress_msg').removeClass('ibo-is-hidden');
|
||||
|
||||
$.post(
|
||||
'{{ sAjaxURL|raw }}',
|
||||
{ operation: 'ajax_compile', transaction_id: '{{ sTransactionId }}' },
|
||||
function (data) {
|
||||
$('#ajax_compile_in_progress_msg').addClass('ibo-is-hidden');
|
||||
|
||||
if (data.error_message) {
|
||||
$('#ajax_compile_error_msg .ibo-alert--title').html(data.error_message);
|
||||
$('#ajax_compile_error_msg').removeClass('ibo-is-hidden');
|
||||
} else {
|
||||
$('#ajax_compile_success_msg .ibo-alert--title').html(data.success_message);
|
||||
$('#ajax_compile_success_msg').removeClass('ibo-is-hidden');
|
||||
|
||||
ajax_run_audit(data.transaction_id);
|
||||
}
|
||||
},
|
||||
'json'
|
||||
)
|
||||
.fail(function() {
|
||||
$('#ajax_compile_in_progress_msg').addClass('ibo-is-hidden');
|
||||
$('#ajax_compile_error_msg .ibo-alert--title').html('{{ 'DataFeatureRemoval:Compile:Error'|dict_s }}');
|
||||
$('#ajax_compile_error_msg').removeClass('ibo-is-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
ajax_compile();
|
||||
@@ -5,7 +5,6 @@
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden {sName:'operation', sValue:'AnalysisResult'} %}
|
||||
{% UIInput ForHidden {sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden {sName:'return_application', sValue:'itop'} %}
|
||||
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Features:Title'|dict_s, sSubTitle: '' } %}
|
||||
{% UIMultiColumn Standard {} %}
|
||||
@@ -13,9 +12,9 @@
|
||||
{% UIColumn Standard {} %}
|
||||
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
|
||||
{% if aExtension['installed'] %}
|
||||
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% else %}
|
||||
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% EndUIColumn %}
|
||||
|
||||
@@ -10,10 +10,10 @@ return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => $baseDir . '/src/Entity/DataCleanupSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanEntity' => $baseDir . '/src/Entity/DeletionPlanEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanItem' => $baseDir . '/src/Entity/DeletionPlanItem.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => $baseDir . '/src/Helper/DataFeatureRemovalConfig.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Hook\\EventListener' => $baseDir . '/src/Hook/EventListener.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataCleanupService' => $baseDir . '/src/Service/DataCleanupService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectService' => $baseDir . '/src/Service/ObjectService.php',
|
||||
|
||||
@@ -25,10 +25,10 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DataCleanupSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanEntity' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanItem' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanItem.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalConfig.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Hook\\EventListener' => __DIR__ . '/../..' . '/src/Hook/EventListener.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataCleanupService' => __DIR__ . '/../..' . '/src/Service/DataCleanupService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectService' => __DIR__ . '/../..' . '/src/Service/ObjectService.php',
|
||||
|
||||
@@ -64,7 +64,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Suche zunächst nach verlorenen / falsch platzierten Attachments mittels einer Analyse der Datenbank',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyseergebnisse:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Toll! Alles scheint am richtigen Ort zu sein.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Manche Attachments scheinen am falschen Ort zu sein. Werfen Sie einen Blick auf die folgende Liste und wählen Sie diejenigen aus, die Sie gerne verschieben möchten.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Manche Attachments (%1$d) scheinen am falschen Ort zu sein. Werfen Sie einen Blick auf die folgende Liste und wählen Sie diejenigen aus, die Sie gerne verschieben möchten.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Dateiname',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Derzeitiger Ort',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Verschieben nach...',
|
||||
|
||||
@@ -16,8 +16,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'DBTools:Class' => 'Класс',
|
||||
'DBTools:Title' => 'Инструменты обслуживания базы данных',
|
||||
'DBTools:ErrorsFound' => 'Найденные ошибки',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Indication' => 'Важно: после исправления ошибок в базе данных нужно будет запустить анализ заново, так как появятся новые несоответствия',
|
||||
'DBTools:Disclaimer' => 'ВНИМАНИЕ: СДЕЛАЙТЕ РЕЗЕРВНУЮ КОПИЮ БАЗЫ ДАННЫХ ПЕРЕД ЗАПУСКОМ ИСПРАВЛЕНИЙ',
|
||||
'DBTools:Error' => 'Ошибка',
|
||||
'DBTools:Count' => 'Количество',
|
||||
'DBTools:SQLquery' => 'SQL-запрос',
|
||||
@@ -28,23 +28,23 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'DBTools:ShowIds' => 'Подробный вид',
|
||||
'DBTools:ShowReport' => 'Отчёт',
|
||||
'DBTools:IntegrityCheck' => 'Проверка целостности',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
'DBTools:FetchCheck' => 'Проверка выборки (долго)',
|
||||
'DBTools:SelectAnalysisType' => 'Выберите тип анализа',
|
||||
'DBTools:Analyze' => 'Анализировать',
|
||||
'DBTools:Details' => 'Показать подробности',
|
||||
'DBTools:ShowAll' => 'Показать все ошибки',
|
||||
'DBTools:Inconsistencies' => 'Несоответствия базы данных',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBTools:DetailedErrorTitle' => 'Ошибок (%2$s) в классе %1$s: %3$s',
|
||||
'DBTools:DetailedErrorLimit' => 'Список ограничен %1$s ошибками',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Сиротская запись в `%1$s`, она должна иметь свой аналог в таблице `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Недопустимый внешний ключ %1$s (столбец: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Отсутствует внешний ключ %1$s (столбец: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-InvalidValue' => 'Недопустимое значение для %1$s (столбец: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-UsersWithoutProfile' => 'Некоторые учетные записи пользователей не имеют профилей',
|
||||
'DBAnalyzer-Integrity-HKInvalid' => 'Broken hierarchical key `%1$s`~~',
|
||||
'DBAnalyzer-Fetch-Count-Error' => 'Fetch count error in `%1$s`, %2$d entries fetched / %3$d counted~~',
|
||||
'DBAnalyzer-Integrity-FinalClass' => 'Field `%2$s`.`%1$s` must have the same value as `%3$s`.`%1$s`~~',
|
||||
'DBAnalyzer-Integrity-RootFinalClass' => 'Field `%2$s`.`%1$s` must contain a valid class~~',
|
||||
'DBAnalyzer-Integrity-HKInvalid' => 'Повреждён иерархический ключ `%1$s`',
|
||||
'DBAnalyzer-Fetch-Count-Error' => 'Ошибка количества выборки в `%1$s`: получено записей %2$d / посчитано %3$d',
|
||||
'DBAnalyzer-Integrity-FinalClass' => 'Поле `%2$s`.`%1$s` должно иметь то же значение, что и `%3$s`.`%1$s`',
|
||||
'DBAnalyzer-Integrity-RootFinalClass' => 'Поле `%2$s`.`%1$s` должно содержать допустимый класс',
|
||||
]);
|
||||
|
||||
// Database Info
|
||||
|
||||
@@ -33,10 +33,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'DBTools:Disclaimer' => '免责申明: 在应用修复之前, 应先备份数据库',
|
||||
'DBTools:Error' => '错误',
|
||||
'DBTools:Count' => '个数',
|
||||
'DBTools:SQLquery' => 'SQL查询',
|
||||
'DBTools:FixitSQLquery' => '修复问题的SQL查询 (指示)',
|
||||
'DBTools:SQLresult' => 'SQL结果',
|
||||
'DBTools:NoError' => '数据库正确',
|
||||
'DBTools:SQLquery' => 'SQL 查询',
|
||||
'DBTools:FixitSQLquery' => '用于修复问题的 SQL 查询(说明)',
|
||||
'DBTools:SQLresult' => 'SQL 结果',
|
||||
'DBTools:NoError' => '数据库 OK',
|
||||
'DBTools:HideIds' => '错误列表',
|
||||
'DBTools:ShowIds' => '详细视图',
|
||||
'DBTools:ShowReport' => '报告',
|
||||
@@ -73,7 +73,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
// Lost attachments
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'DBTools:LostAttachments' => '附件缺失',
|
||||
'DBTools:LostAttachments:Disclaimer' => '可以在此搜索数据库中丢失或错放的附件. 这不是数据恢复工具, 其无法恢复已删除的数据.',
|
||||
'DBTools:LostAttachments:Disclaimer' => '可以在此搜索数据库中丢失或错放的附件. 请注意, 这不是数据恢复工具, 无法恢复已删除的数据.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => '分析',
|
||||
'DBTools:LostAttachments:Button:Restore' => '还原',
|
||||
|
||||
@@ -40,7 +40,7 @@ oder melden Sie dem '.ITOP_APPLICATION_SHORT.' Administrator diesen Fehler, weil
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:Attachment' => 'Attachment',
|
||||
'Class:Attachment+' => '',
|
||||
'Class:Attachment+' => 'Datei – Text oder Bild –, die mit genau einem Objekt verknüpft ist. Sie kann nicht geändert, sondern nur gelöscht werden. Anhänge lassen sich ausschließlich beim Bearbeiten ihres Objekts anlegen.',
|
||||
'Class:Attachment/Attribute:expire' => 'Läuft ab',
|
||||
'Class:Attachment/Attribute:expire+' => '',
|
||||
'Class:Attachment/Attribute:temp_id' => 'Temporäre ID',
|
||||
@@ -84,13 +84,13 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (beim Herunterladen eines Attachment eines Objekts)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger für das Herunterladen des Attachments der angegebenen Klasse oder einer Unterklasse',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (bei Erstellung eines Anhangs)',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger bei Erstellung eines Anhangs an einem Objekt',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Datei an E-Mail anhängen',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'Wenn aktiviert, wird die Datei automatisch an die E-Mail angehängt, sobald eine E-Mail-Aktion ausgelöst wird',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (bei Löschung eines Anhangs)',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger bei Löschung eines Anhangs an einem Objekt',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Gelöschte Datei an E-Mail anhängen',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'Wenn aktiviert, wird die gelöschte Datei automatisch an die E-Mail angehängt, sobald eine E-Mail-Aktion ausgelöst wird',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Ein klassenunabhängiger Trigger ist für die Klasse Attachment nicht zulässig. Bitte verwenden Sie einen spezifischen Trigger',
|
||||
]);
|
||||
|
||||
@@ -26,12 +26,12 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Attachments:NoAttachment' => 'Нет вложений.',
|
||||
'Attachments:PreviewNotAvailable' => 'Предварительный просмотр не доступен для этого типа вложений.',
|
||||
'Attachments:Error:FileTooLarge' => 'Файл слишком велик для загрузки. %1$s',
|
||||
'Attachments:Error:UploadedFileEmpty' => 'The received file is empty and cannot be attached.
|
||||
Either you have pushed an empty file,
|
||||
or ask your '.ITOP_APPLICATION_SHORT.' administrator if the '.ITOP_APPLICATION_SHORT.' server disk is full.~~',
|
||||
'Attachments:Render:Icons' => 'Display as icons~~',
|
||||
'Attachments:Render:Table' => 'Display as list~~',
|
||||
'UI:Attachments:DropYourFileHint' => 'Drop files anywhere in this area~~',
|
||||
'Attachments:Error:UploadedFileEmpty' => 'Полученный файл пуст и не может быть прикреплён.
|
||||
Либо вы загрузили пустой файл,
|
||||
либо обратитесь к администратору '.ITOP_APPLICATION_SHORT.' — возможно, диск сервера '.ITOP_APPLICATION_SHORT.' переполнен.',
|
||||
'Attachments:Render:Icons' => 'Отображать как иконки',
|
||||
'Attachments:Render:Table' => 'Отображать как список',
|
||||
'UI:Attachments:DropYourFileHint' => 'Перетащите файлы в любое место этой области',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -62,7 +62,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Attachments:File:Uploader' => 'Пользователь',
|
||||
'Attachments:File:Size' => 'Размер',
|
||||
'Attachments:File:MimeType' => 'Тип',
|
||||
'Attachments:File:DownloadsCount' => 'Downloads~~',
|
||||
'Attachments:File:DownloadsCount' => 'Скачиваний',
|
||||
]);
|
||||
//
|
||||
// Class: Attachment
|
||||
@@ -82,15 +82,15 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
//
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
'Class:TriggerOnAttachmentDownload' => 'Триггер (на скачивание вложения объекта)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Триггер на скачивание вложения объекта заданного класса (или его дочернего класса)',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Триггер (на создание вложения объекта)',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Триггер на создание вложения объекта',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Добавлять файл в email',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'Если отмечено, файл будет автоматически прикреплён к письму при срабатывании действия email',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Триггер (на удаление вложения объекта)',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Триггер на удаление вложения объекта',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Добавлять удалённый файл в email',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'Если отмечено, удалённый файл будет автоматически прикреплён к письму при срабатывании действия email',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Триггер на объект не допускается для класса Attachment. Используйте специальный триггер',
|
||||
]);
|
||||
|
||||
@@ -20,7 +20,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'bkp-mysqldump-ok' => 'Утилита mysqldump найдена: %1$s',
|
||||
'bkp-mysqldump-notfound' => 'Утилиту mysqldump найти не удалось: %1$s - пожалуйста, убедитесь в том, что она установлена, и путь до директории с бинарными файлами добавлен в PATH, либо измените параметр mysql_bindir в файле конфигурации.',
|
||||
'bkp-mysqldump-issue' => 'Утилита mysqldump на может быть запущена (retcode=%1$d) Пожалуйста, убедитесь в том, что она установлена, и путь до директории с бинарными файлами добавлен в PATH, либо измените параметр mysql_bindir в файле конфигурации.',
|
||||
'bkp-missing-dir' => 'The target directory <code>%1$s</code> could not be found~~',
|
||||
'bkp-missing-dir' => 'Целевой каталог <code>%1$s</code> не найден',
|
||||
'bkp-free-disk-space' => '<b>%1$s свободно</b> в <code>%2$s</code>',
|
||||
'bkp-dir-not-writeable' => '%1$s недоступен для записи',
|
||||
'bkp-wrong-format-spec' => 'Неправильный формат шаблона названия файлов резервных копий (%1$s). Будет использован шаблон по умолчанию: %2$s',
|
||||
@@ -38,7 +38,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'bkp-status-backups-manual' => 'Резервное копирование вручную',
|
||||
'bkp-status-backups-none' => 'Резервных копий ещё нет',
|
||||
'bkp-next-backup' => 'Следующее резервное копирование будет выполняться в <b>%1$s</b> (%2$s) в %3$s',
|
||||
'bkp-next-backup-unknown' => 'The next backup is <b>not scheduled</b> yet.~~',
|
||||
'bkp-next-backup-unknown' => 'Следующее резервное копирование пока <b>не запланировано</b>.',
|
||||
'bkp-button-backup-now' => 'Запустить сейчас!',
|
||||
'bkp-button-restore-now' => 'Восстановить!',
|
||||
'bkp-confirm-backup' => 'Пожалуйста, подтвердите, что вы хотите выполнить резервное копирование прямо сейчас.',
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Spinner\SpinnerUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
@@ -410,8 +409,11 @@ JS
|
||||
|
||||
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
|
||||
|
||||
$oModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitBackup);
|
||||
$sModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oModalSpinner);
|
||||
$oBackupModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitBackup);
|
||||
$sBackupModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oBackupModalSpinner);
|
||||
|
||||
$oRestoreModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitRestore);
|
||||
$sRestoreModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oRestoreModalSpinner);
|
||||
|
||||
$oP->add_script(
|
||||
<<<JS
|
||||
@@ -424,7 +426,7 @@ function LaunchBackupNow()
|
||||
{
|
||||
const oModal = CombodoModal.OpenModal({
|
||||
title: '$sBackUpNow',
|
||||
content: `$sModalSpinnerHtml`
|
||||
content: `$sBackupModalSpinnerHtml`
|
||||
});
|
||||
|
||||
var oParams = {};
|
||||
@@ -450,10 +452,10 @@ function LaunchRestoreNow(sBackupFile, sConfirmationMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const oModal = CombodoModal.OpenModal({
|
||||
title: '$sRestore',
|
||||
content: '<i class="ajax-spin fas fa-sync-alt fa-spin"></i> $sPleaseWaitRestore'
|
||||
content: `$sRestoreModalSpinnerHtml`
|
||||
});
|
||||
|
||||
$('#backup_success').addClass('ibo-is-hidden');
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:lnkFunctionalCIToProviderContract' => 'Verknüpfung FunctionalCI/Provider-Vertrag',
|
||||
'Class:lnkFunctionalCIToProviderContract+' => '',
|
||||
'Class:lnkFunctionalCIToProviderContract+' => 'Diese Verknüpfung bildet ab, welche funktionalen CIs über einen Provider-Vertrag von einem externen Unternehmen betreut werden.',
|
||||
'Class:lnkFunctionalCIToProviderContract/Name' => '%1$s / %2$s',
|
||||
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id' => 'Provider-Vertrag',
|
||||
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id+' => '',
|
||||
@@ -31,7 +31,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:lnkFunctionalCIToService' => 'Verknüpfung FunctionalCI/Service',
|
||||
'Class:lnkFunctionalCIToService+' => '',
|
||||
'Class:lnkFunctionalCIToService+' => 'Diese Verknüpfung bildet ab, welche funktionalen CIs zur Erbringung eines Services benötigt werden und deren Ausfall die Qualität des Services beeinträchtigen würde.',
|
||||
'Class:lnkFunctionalCIToService/Name' => '%1$s / %2$s',
|
||||
'Class:lnkFunctionalCIToService/Attribute:service_id' => 'Service',
|
||||
'Class:lnkFunctionalCIToService/Attribute:service_id+' => '',
|
||||
@@ -49,9 +49,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:FunctionalCI/Attribute:providercontracts_list' => 'Provider-Verträge',
|
||||
'Class:FunctionalCI/Attribute:providercontracts_list+' => '',
|
||||
'Class:FunctionalCI/Attribute:providercontracts_list+' => 'Alle Provider-Verträge zu diesem Konfigurationselement',
|
||||
'Class:FunctionalCI/Attribute:services_list' => 'Services',
|
||||
'Class:FunctionalCI/Attribute:services_list+' => '',
|
||||
'Class:FunctionalCI/Attribute:services_list+' => 'Alle Services, die von diesem Konfigurationselement beeinträchtigt werden',
|
||||
]);
|
||||
|
||||
//
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:lnkFunctionalCIToProviderContract' => 'Связь Функциональная КЕ/Договор с поставщиком',
|
||||
'Class:lnkFunctionalCIToProviderContract+' => '',
|
||||
'Class:lnkFunctionalCIToProviderContract/Name' => '%1$s / %2$s~~',
|
||||
'Class:lnkFunctionalCIToProviderContract/Name' => '%1$s / %2$s',
|
||||
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id' => 'Договор с поставщиком',
|
||||
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id+' => '',
|
||||
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_name' => 'Договор с поставщиком',
|
||||
@@ -32,7 +32,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:lnkFunctionalCIToService' => 'Связь Функциональная КЕ/Услуга',
|
||||
'Class:lnkFunctionalCIToService+' => '',
|
||||
'Class:lnkFunctionalCIToService/Name' => '%1$s / %2$s~~',
|
||||
'Class:lnkFunctionalCIToService/Name' => '%1$s / %2$s',
|
||||
'Class:lnkFunctionalCIToService/Attribute:service_id' => 'Услуга',
|
||||
'Class:lnkFunctionalCIToService/Attribute:service_id+' => '',
|
||||
'Class:lnkFunctionalCIToService/Attribute:service_name' => 'Услуга',
|
||||
|
||||
@@ -155,6 +155,28 @@
|
||||
</field>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="UserRequest" _delta="if_exists">
|
||||
<lifecycle>
|
||||
<states>
|
||||
<state id="closed">
|
||||
<flags>
|
||||
<attribute id="functionalcis_list" _delta="define">
|
||||
<read_only/>
|
||||
</attribute>
|
||||
</flags>
|
||||
</state>
|
||||
</states>
|
||||
</lifecycle>
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="functionalcis_list" _delta="define">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
</classes>
|
||||
<menus>
|
||||
</menus>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:lnkFunctionalCIToTicket' => 'Verknüpfung FunctionalCI/Ticket',
|
||||
'Class:lnkFunctionalCIToTicket+' => '',
|
||||
'Class:lnkFunctionalCIToTicket+' => 'Diese Verknüpfung bildet ab, welche funktionalen CIs von einem Ticket betroffen sind (etwa von einer Benutzeranfrage, einem Incident, einem Change ...)',
|
||||
'Class:lnkFunctionalCIToTicket/Name' => '%1$s / %2$s',
|
||||
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id' => 'Ticket',
|
||||
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id+' => '',
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:lnkFunctionalCIToTicket' => 'Связь Функциональная КЕ/Тикет',
|
||||
'Class:lnkFunctionalCIToTicket+' => '',
|
||||
'Class:lnkFunctionalCIToTicket/Name' => '%1$s / %2$s~~',
|
||||
'Class:lnkFunctionalCIToTicket/Name' => '%1$s / %2$s',
|
||||
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id' => 'Тикет',
|
||||
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id+' => '',
|
||||
'Class:lnkFunctionalCIToTicket/Attribute:ticket_ref' => 'Тикет',
|
||||
|
||||
@@ -19,14 +19,14 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Menu:NewChange+' => 'Einen neuen Change erstellen',
|
||||
'Menu:SearchChanges' => 'Suche nach Changes',
|
||||
'Menu:SearchChanges+' => 'Unter den bestehenden Changes suchen',
|
||||
'Menu:Change:Shortcuts' => 'Changes~~',
|
||||
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes~~',
|
||||
'Menu:Change:Shortcuts' => 'Changes',
|
||||
'Menu:Change:Shortcuts+' => 'Verknüpfungen zu vordefinierten Change-Listen',
|
||||
'Menu:WaitingAcceptance' => 'Changes, die auf Annahme warten',
|
||||
'Menu:WaitingAcceptance+' => 'Changes, die auf Annahme warten',
|
||||
'Menu:WaitingApproval' => 'Changes, die auf Genehmigung warten',
|
||||
'Menu:WaitingApproval+' => 'Changes, die auf Genehmigung warten',
|
||||
'Menu:Changes' => 'Offene Changes',
|
||||
'Menu:Changes+' => 'Offene Changes',
|
||||
'Menu:Changes+' => 'Alle Changes, die nicht im Status "geschlossen" sind',
|
||||
'Menu:MyChanges' => 'Changes, die mir zugewiesen sind',
|
||||
'Menu:MyChanges+' => 'Changes, die mir zugewiesen sind',
|
||||
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => 'Changes der letzten sieben Tage nach Kategorie',
|
||||
@@ -53,7 +53,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:Change' => 'Change',
|
||||
'Class:Change+' => '',
|
||||
'Class:Change+' => 'Abstrakte Klasse. Ein Ticket-Typ zur Behandlung von Changes, die sich auf erbrachte Services auswirken.',
|
||||
'Class:Change/Attribute:status' => 'Status',
|
||||
'Class:Change/Attribute:status+' => '',
|
||||
'Class:Change/Attribute:status/Value:new' => 'Neu',
|
||||
@@ -76,7 +76,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:Change/Attribute:status/Value:monitored+' => '',
|
||||
'Class:Change/Attribute:status/Value:closed' => 'Geschlossen',
|
||||
'Class:Change/Attribute:status/Value:closed+' => '',
|
||||
'Class:Change/Attribute:reason' => 'Ursache',
|
||||
'Class:Change/Attribute:reason' => 'Ablehnungsgrund',
|
||||
'Class:Change/Attribute:reason+' => '',
|
||||
'Class:Change/Attribute:requestor_id' => 'Antragsteller',
|
||||
'Class:Change/Attribute:requestor_id+' => '',
|
||||
@@ -92,7 +92,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:Change/Attribute:supervisor_group_name+' => '',
|
||||
'Class:Change/Attribute:supervisor_id' => 'Aufsicht',
|
||||
'Class:Change/Attribute:supervisor_id+' => '',
|
||||
'Class:Change/Attribute:supervisor_email' => 'Aufsicht',
|
||||
'Class:Change/Attribute:supervisor_email' => 'E-Mail der Aufsicht',
|
||||
'Class:Change/Attribute:supervisor_email+' => '',
|
||||
'Class:Change/Attribute:manager_group_id' => 'Manager-Team',
|
||||
'Class:Change/Attribute:manager_group_id+' => '',
|
||||
@@ -100,7 +100,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:Change/Attribute:manager_group_name+' => '',
|
||||
'Class:Change/Attribute:manager_id' => 'Manager',
|
||||
'Class:Change/Attribute:manager_id+' => '',
|
||||
'Class:Change/Attribute:manager_email' => 'Manager',
|
||||
'Class:Change/Attribute:manager_email' => 'E-Mail des Managers',
|
||||
'Class:Change/Attribute:manager_email+' => '',
|
||||
'Class:Change/Attribute:outage' => 'Ausfall',
|
||||
'Class:Change/Attribute:outage+' => '',
|
||||
@@ -156,7 +156,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:RoutineChange' => 'Routine Change',
|
||||
'Class:RoutineChange+' => '',
|
||||
'Class:RoutineChange+' => 'Standard-Change mit geringem Risiko, ohne zusätzliche Genehmigung, für den ein freigegebenes Verfahren existiert, das angewendet werden muss.',
|
||||
'Class:RoutineChange/Stimulus:ev_validate' => 'Validieren',
|
||||
'Class:RoutineChange/Stimulus:ev_validate+' => '',
|
||||
'Class:RoutineChange/Stimulus:ev_reject' => 'Ablehnen',
|
||||
@@ -187,7 +187,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:ApprovedChange' => 'Genehmigte Changes',
|
||||
'Class:ApprovedChange+' => '',
|
||||
'Class:ApprovedChange+' => 'Abstrakte Klasse. Normale und Notfall-Changes, die eine Genehmigung erfordern.',
|
||||
'Class:ApprovedChange/Attribute:approval_date' => 'Datum der Genehmigung',
|
||||
'Class:ApprovedChange/Attribute:approval_date+' => '',
|
||||
'Class:ApprovedChange/Attribute:approval_comment' => 'Kommentar zur Genehmigung',
|
||||
@@ -222,7 +222,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:NormalChange' => 'Normaler Change',
|
||||
'Class:NormalChange+' => '',
|
||||
'Class:NormalChange+' => 'Change-Typ',
|
||||
'Class:NormalChange/Attribute:acceptance_date' => 'Datum der Annahme',
|
||||
'Class:NormalChange/Attribute:acceptance_date+' => '',
|
||||
'Class:NormalChange/Attribute:acceptance_comment' => 'Kommentar zur Annahme',
|
||||
@@ -257,7 +257,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:EmergencyChange' => 'Emergency Change',
|
||||
'Class:EmergencyChange+' => '',
|
||||
'Class:EmergencyChange+' => 'Change-Ticket für Notfallsituationen (Fehlfunktionen, Sicherheit). Der Genehmigungsprozess ist verkürzt, der Notfall-Change wird so schnell wie möglich umgesetzt.',
|
||||
'Class:EmergencyChange/Stimulus:ev_validate' => 'Validieren',
|
||||
'Class:EmergencyChange/Stimulus:ev_validate+' => '',
|
||||
'Class:EmergencyChange/Stimulus:ev_reject' => 'Ablehnen',
|
||||
|
||||
@@ -30,12 +30,12 @@ Dict::Add('EN GB', 'British English', 'British English', [
|
||||
'Menu:NewChange+' => 'Create a new change ticket',
|
||||
'Menu:SearchChanges' => 'Search for changes',
|
||||
'Menu:SearchChanges+' => 'Search for change tickets',
|
||||
'Menu:Change:Shortcuts' => 'Changes~~',
|
||||
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes~~',
|
||||
'Menu:Change:Shortcuts' => 'Changes',
|
||||
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes',
|
||||
'Menu:WaitingAcceptance' => 'Changes awaiting acceptance',
|
||||
'Menu:WaitingAcceptance+' => '',
|
||||
'Menu:WaitingApproval' => 'Changes awaiting approval',
|
||||
'Menu:WaitingApproval+' => 'Changes in planned status~~',
|
||||
'Menu:WaitingApproval+' => 'Changes in planned status',
|
||||
'Menu:Changes' => 'Open changes',
|
||||
'Menu:Changes+' => 'All changes which are not in closed status',
|
||||
'Menu:MyChanges' => 'Changes assigned to me',
|
||||
|
||||
@@ -27,24 +27,24 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Menu:Change:Overview' => '概况',
|
||||
'Menu:Change:Overview+' => '',
|
||||
'Menu:NewChange' => '新建变更',
|
||||
'Menu:NewChange+' => '新建变更',
|
||||
'Menu:NewChange+' => '新建变更工单',
|
||||
'Menu:SearchChanges' => '搜索变更',
|
||||
'Menu:SearchChanges+' => '搜索变更',
|
||||
'Menu:Change:Shortcuts' => '快捷方式',
|
||||
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes~~',
|
||||
'Menu:SearchChanges+' => '搜索变更工单',
|
||||
'Menu:Change:Shortcuts' => '变更',
|
||||
'Menu:Change:Shortcuts+' => '快速访问预定义的变更数据',
|
||||
'Menu:WaitingAcceptance' => '等待审核的变更',
|
||||
'Menu:WaitingAcceptance+' => '',
|
||||
'Menu:WaitingApproval' => '等待批准的变更',
|
||||
'Menu:WaitingApproval+' => 'Changes in planned status~~',
|
||||
'Menu:Changes' => '所有打开的变更',
|
||||
'Menu:Changes+' => '所有打开的变更',
|
||||
'Menu:WaitingApproval+' => '处于计划状态的变更',
|
||||
'Menu:Changes' => '所有待处理的变更',
|
||||
'Menu:Changes+' => '所有待处理的变更',
|
||||
'Menu:MyChanges' => '分配给我的变更',
|
||||
'Menu:MyChanges+' => '分配给我的变更 (作为办理人)',
|
||||
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => '最近一周的变更 (按类型)',
|
||||
'UI-ChangeManagementOverview-Last-7-days' => '最近一周的变更 (按数量)',
|
||||
'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => '最近一周的变更 (按范围)',
|
||||
'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => '最近一周的变更 (按状态)',
|
||||
'Tickets:Related:OpenChanges' => '打开的变更',
|
||||
'Tickets:Related:OpenChanges' => '待处理的变更',
|
||||
'Tickets:Related:RecentChanges' => '最近的变更 (72小时)',
|
||||
]);
|
||||
|
||||
@@ -132,7 +132,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:Change/Attribute:related_incident_list' => '相关事件',
|
||||
'Class:Change/Attribute:related_incident_list+' => '此变更相关的所有事件',
|
||||
'Class:Change/Attribute:child_changes_list' => '子变更',
|
||||
'Class:Change/Attribute:child_changes_list+' => '此变更相关的字变更',
|
||||
'Class:Change/Attribute:child_changes_list+' => '此变更相关的子变更',
|
||||
'Class:Change/Attribute:parent_id_friendlyname' => '父级变更昵称',
|
||||
'Class:Change/Attribute:parent_id_friendlyname+' => '',
|
||||
'Class:Change/Attribute:parent_id_finalclass_recall' => '变更类型',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user