Compare commits

...

27 Commits

Author SHA1 Message Date
odain
b35ffb0fe9 add some error handling to have full stacktrace 2024-09-11 15:36:50 +02:00
odain
1e319f3943 add a test to reproduct the hub extension deploy issue 2024-09-11 15:36:24 +02:00
odain
cd41eda29d adapt hub code to use another env thant production 2024-09-11 15:35:17 +02:00
Eric Espie
4a99afae3b Merge remote-tracking branch 'origin/support/3.1' into support/3.2 2024-08-26 13:55:55 +02:00
Molkobain
119dcf9c97 N°7730 - Rename constant as it is actually for ID and class selectors, not only IDs 2024-08-26 13:55:03 +02:00
Eric Espie
b7d14ca48e N°7730 - code hardening 2024-08-26 13:55:03 +02:00
Romain Quetiez
37cd12fb21 N°7662 Fix the handling of PHP native deprecations (e.g. call strftime on PHP 8.1) 2024-08-23 16:35:52 +02:00
Benjamin Dalsass
ffb61503dc N°7534 - Request to Improve the Display of an Inline Attachment
- reinforce algorithm
2024-08-20 16:52:15 +02:00
Molkobain
fbda0e38e1 💄 Update README.md 2024-08-20 16:34:32 +02:00
Molkobain
bfd8fb1c23 💄 Update README.md file with BEM methodology 2024-08-20 13:54:45 +02:00
Molkobain
c6760371ba 💄 Fix typo in $ibo-depression-100 CSS3 var. counterpart 2024-08-20 13:12:26 +02:00
Molkobain
8c704951e1 N°7730 - Rename constant as it is actually for ID and class selectors, not only IDs 2024-08-19 18:41:32 +02:00
Thomas Casteleyn
018f7808db N°5039 - DataSynchro: TEXT field too small for big linkset (#479) 2024-08-19 18:18:21 +02:00
Eric Espie
24c23628d6 N°7730 - code hardening 2024-08-19 15:15:22 +02:00
Benjamin Dalsass
1d3c71fd10 N°7534 - Request to Improve the Display of an Inline Attachment (#664) 2024-08-19 11:03:06 +02:00
Molkobain
e8d059fa77 N°7756 - Allow AttributeClass to declare "class_category" empty in XML 2024-08-17 23:29:56 +02:00
Molkobain
3b4a9dc368 📝 Update backoffice SCSS README.md 2024-08-13 15:05:32 +02:00
Anne-Cath
244ae33ad6 N°7745 - Portal: Fix js error in firefox when leaving object modal form with ckeditor form 2024-08-12 14:36:32 +02:00
jf-cbd
c1eb605195 Merge remote-tracking branch 'refs/remotes/origin/support/3.2.0' into support/3.2 2024-08-07 17:59:57 +02:00
jf-cbd
0ee1818f12 N°7732 - CSRF protection generating error when cancelling the creation of an object
N°7741 - PDF export on impact analysis not working
2024-08-07 17:56:34 +02:00
Benjamin Dalsass
31758cbe2b Merge remote-tracking branch 'origin/support/3.2.0' into support/3.2 2024-08-07 08:32:59 +02:00
Benjamin Dalsass
a4a1fa4ec9 N°7734 - Date time picker button show html icon 2024-08-07 08:32:06 +02:00
Benjamin Dalsass
c32e186133 Merge remote-tracking branch 'origin/support/3.2.0' into support/3.2 2024-08-07 07:55:50 +02:00
Benjamin Dalsass
3d325caa06 N°7734 - Date time picker button show html icon 2024-08-07 07:54:40 +02:00
Molkobain
22141aba7b Merge remote-tracking branch 'origin/support/3.2.0' into support/3.2 2024-08-05 16:10:49 +02:00
Molkobain
9b066b3b88 N°7410 - Add hyperlinks to news features 2024-08-05 09:17:51 +02:00
Romain Quetiez
7ea7f5a967 N°7728 Stop flooding error.log with low probability issues, and do not confuse the root cause and the fact that the API is deprecated. 2024-08-02 10:33:00 +02:00
43 changed files with 951 additions and 425 deletions

View File

@@ -706,7 +706,7 @@ class DisplayBlock
if ($bDoSearch)
{
// Keep the table_id identifying this table if we're performing a search
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
if ($sTableId != null)
{
$aExtraParams['table_id'] = $sTableId;

View File

@@ -113,6 +113,11 @@ class utils
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
/**
* @var string For XML / HTML node id/class selector
* @since 3.1.2 3.2.1
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR = 'element_selector';
/**
* @var string For variables names
* @since 3.0.0
@@ -497,8 +502,17 @@ class utils
}
break;
// For XML / HTML node identifiers
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
$retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
$retValue = filter_var($retValue, FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[A-Za-z0-9][A-Za-z0-9_-]*$/']]);
break;
// For XML / HTML node id selector
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[#\.][A-Za-z0-9][A-Za-z0-9_-]*$/']]);
break;
case static::ENUM_SANITIZATION_FILTER_VARIABLE_NAME:

View File

@@ -2069,7 +2069,7 @@ class AttributeLinkedSet extends AttributeDefinition
public function GetImportColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'TEXT'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode()] = 'MEDIUMTEXT'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}

View File

@@ -1496,6 +1496,7 @@ class DisplayableGraph extends SimpleGraph
'excluded' => $aExcludedByClass,
'grouping_threshold' => $iGroupingThreshold,
'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')),
'transaction_id' => utils::GetNewTransactionId(),
'export_as_attachment' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsAttachment'), 'obj_class' => $sObjClass, 'obj_key' => $iObjKey),
'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')),
'labels' => array(

View File

@@ -1173,7 +1173,7 @@ class DeprecatedCallsLog extends LogAPI
/**
* This will catch a message for all E_DEPRECATED and E_USER_DEPRECATED errors.
* This handler is set in DeprecatedCallsLog::Enable
* This handler is set in {@see DeprecatedCallsLog::Enable}
*
* @param int $errno
* @param string $errstr
@@ -1193,52 +1193,22 @@ class DeprecatedCallsLog extends LogAPI
return false;
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
if (isset($aStack[2]['function']) && ($aStack[2]['function'] == 'ForwardToTriggerError')) {
// Let the notice bubble up
return false;
}
if (false === static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
// returns true so that nothing is throwned !
// returns true so that nothing is thrown!
return true;
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call (can be either 'trigger_deprecation' or the faulty method), level 3 = In some cases, method containing the 'trigger_deprecation' call
// In case current level is actually a 'trigger_deprecation' call, try to go one level further to get the real deprecated method
if (array_key_exists($iStackDeprecatedMethodLevel, $aStack) && ($aStack[$iStackDeprecatedMethodLevel]['function'] === 'trigger_deprecation') && array_key_exists($iStackDeprecatedMethodLevel + 1, $aStack)) {
$iStackDeprecatedMethodLevel++;
}
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
// We are generating a trigger_error ourselves, we don't want to trace them !
return false;
}
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'] ?? null;
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'] ?? null;
$sMessage .= ' (';
if (!is_null($sCallerObject)) {
$sMessage .= "{$sCallerObject}::{$sCallerMethod}";
} else {
$sCallerMethodFile = $aStack[$iStackCallerMethodLevel]['file'];
$sCallerMethodLine = $aStack[$iStackCallerMethodLevel]['line'];
if (!is_null($sCallerMethod)) {
$sMessage .= "call to {$sCallerMethod}() in {$sCallerMethodFile}#L{$sCallerMethodLine}";
} else {
$sMessage .= "{$sCallerMethodFile}#L{$sCallerMethodLine}";
}
}
$sMessage .= ')';
}
if (!empty($errstr)) {
$sMessage .= ' : '.$errstr;
}
$aStack = static::StripCallStack($aStack);
$sMessage = "$errstr, called from ".static::SummarizeCallStack($aStack);
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_LIBMETHOD);
static::ForwardToTriggerError($sMessage);
return true;
}
@@ -1264,6 +1234,8 @@ class DeprecatedCallsLog extends LogAPI
}
/**
* Call this helper at the beginning of a deprecated file (in its global scope)
*
* @since 3.0.1 3.1.0 N°4725 silently handles ConfigException
* @since 3.0.4 3.1.0 N°4725 remove forgotten throw PHPDoc annotation
*
@@ -1298,9 +1270,12 @@ class DeprecatedCallsLog extends LogAPI
}
static::Warning($sMessage, static::ENUM_CHANNEL_FILE);
static::ForwardToTriggerError($sMessage);
}
/**
* Call this helper when calling a deprecated extension method
*
* @param string $sImplementationClass Class implementing the deprecated API
* @param string $sDeprecatedApi Class name of the deprecated API
* @param string $sDeprecatedMethod Method name of the deprecated API
@@ -1327,9 +1302,12 @@ class DeprecatedCallsLog extends LogAPI
}
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_API);
static::ForwardToTriggerError($sMessage);
}
/**
* Call this helper within deprecated methods
*
* @param string|null $sAdditionalMessage
*
* @link https://www.php.net/debug_backtrace
@@ -1347,52 +1325,24 @@ class DeprecatedCallsLog extends LogAPI
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$sMessage = self::GetMessageFromStack($aStack);
if (!is_null($sAdditionalMessage)) {
$sMessage .= ' : '.$sAdditionalMessage;
if (isset($aStack[1]['class'])) {
$sFunctionDesc = $aStack[1]['class'].$aStack[1]['type'].$aStack[1]['function'];
}
else {
$sFunctionDesc = $aStack[1]['function'];
}
$sMessage = "Function $sFunctionDesc() is deprecated";
if (!is_null($sAdditionalMessage)) {
$sMessage .= ': '.$sAdditionalMessage;
}
$sMessage .= '. Caller: '.self::SummarizeCallStack(array_slice($aStack, 1));
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
}
/**
* @param array $aDebugBacktrace data from {@see debug_backtrace()}
*
* @return string message to print to the log
*/
private static function GetMessageFromStack(array $aDebugBacktrace): string
{
// level 0 = current method
// level 1 = deprecated method, containing the `NotifyDeprecatedPhpMethod` call
$sMessage = 'Call'.self::GetMessageForCurrentStackLevel($aDebugBacktrace[1], " to ");
// level 2 = caller of the deprecated method
if (array_key_exists(2, $aDebugBacktrace)) {
$sMessage .= ' (from ';
$sMessage .= self::GetMessageForCurrentStackLevel($aDebugBacktrace[2]);
$sMessage .= ')';
}
return $sMessage;
}
private static function GetMessageForCurrentStackLevel(array $aCurrentLevelDebugTrace, ?string $sPrefix = ""): string
{
$sMessage = "";
if (array_key_exists('class', $aCurrentLevelDebugTrace)) {
$sDeprecatedObject = $aCurrentLevelDebugTrace['class'];
$sDeprecatedMethod = $aCurrentLevelDebugTrace['function'] ?? "";
$sMessage = "{$sPrefix}{$sDeprecatedObject}::{$sDeprecatedMethod} in ";
}
if (array_key_exists('file', $aCurrentLevelDebugTrace)) {
$sCallerFile = $aCurrentLevelDebugTrace['file'];
$sCallerLine = $aCurrentLevelDebugTrace['line'] ?? "";
$sMessage .= "{$sCallerFile}#L{$sCallerLine}";
}
return $sMessage;
static::ForwardToTriggerError($sMessage);
}
/**
@@ -1422,14 +1372,11 @@ class DeprecatedCallsLog extends LogAPI
}
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_ENDPOINT);
static::ForwardToTriggerError($sMessage);
}
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
{
if (true === utils::IsDevelopmentEnvironment()) {
trigger_error($sMessage, E_USER_DEPRECATED);
}
try {
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
}
@@ -1437,6 +1384,61 @@ class DeprecatedCallsLog extends LogAPI
// nothing much we can do... and we don't want to crash the caller !
}
}
/**
* Strips some elements from the top of the call stack to skip calls that are not relevant to report the deprecated call
* @param array $aCallStack Call stack as returned by {@see debug_backtrace()}
*/
protected static function StripCallStack($aCallStack): array
{
if (!isset($aCallStack[0]['line'])) {
$aCallStack = array_slice($aCallStack, 1);
}
if (isset($aCallStack[1]['function']) && $aCallStack[1]['function'] === 'trigger_deprecation') {
$aCallStack = array_slice($aCallStack, 1);
}
return $aCallStack;
}
protected static function SummarizeCallStack($aCallStack, $bRecurse = true)
{
if (count($aCallStack) == 0) {
return null;
}
$sFileLine = $aCallStack[0]['file'].'#'.$aCallStack[0]['line'];
$sSummary = $sFileLine;
// If possible and meaningful, add the class and method
if (isset($aCallStack[1]['class'])) {
$sSummary = $aCallStack[1]['class'].$aCallStack[1]['type'].$aCallStack[1]['function']." ($sFileLine)";
}
elseif (isset($aCallStack[1]['function'])) {
if (in_array($aCallStack[1]['function'], ['include', 'require', 'include_once', 'require_once'])) {
// No need to show the generic mechanism of inclusion
$bRecurse = false;
}
else {
$sSummary = $aCallStack[1]['function']." ($sFileLine)";
}
}
if ($bRecurse) {
$sUpperSummary = static::SummarizeCallStack(array_slice($aCallStack, 1), false);
if (!is_null($sUpperSummary)) {
$sSummary .= ', itself called from '.$sUpperSummary;
}
}
return $sSummary;
}
private static function ForwardToTriggerError(string $sMessage): void
{
if (true === utils::IsDevelopmentEnvironment()) {
trigger_error($sMessage, E_USER_DEPRECATED);
}
}
}

View File

@@ -1,75 +1,108 @@
## Description
This is a brief description of the SASS 7-1 system and how to use it.
- [File structure](#file-structure)
- [Usage](#usage)
# Description
This is a brief description of the how the backoffice theme is structured using both BEM and SASS 7-1 systems and how to use them.
* [7-1 pattern](#7-1-pattern)
* [File structure](#file-structure)
* [Usage](#usage)
* [BEM methodology](#bem-methodology)
* [Principles](#principles)
* [Examples](#examples)
# 7-1 pattern
## File structure
SCSS files are structured following the [7-1 pattern](https://sass-guidelin.es/#the-7-1-pattern). \
@rveitch made a great summary with the following, which can also be found [here](https://gist.github.com/rveitch/84cea9650092119527bc).
_Note: Folders with an * are customizations we made to the original 7-1 pattern to best fit our needs_
_Note: Folders with an * are customizations we made to the original 7-1 pattern to best fit our needs_
```
css/backoffice/
|
| utils/
| | _variables.scss # Sass Variables
| | _functions.scss # Sass Functions
| | _mixins.scss # Sass Mixins
| | _helpers.scss # Class & placeholders helpers
| | variables/ # Sass Variables used in Functions, Mixins, Helpers, ...
| | |- colors/
| | | |- _base.scss
| | | |- _base-palette.scss # Base colors used everywhere
| | | |- _lifecycle-palette.scss # Colors used for lifecycle of an object (e.g. representing states such as new, frozen, done, ...), based on the base colors
| | | |- _semantic-palette.scss # Colors used for semantic meaning (e.g. red for errors, green for success, ...), based on the base colors
| | | ...
| | |
| | |- _depression.scss
| | |- _elevation.scss
| | |- _size.scss # Base sizes used everywhere (spacings, ...)
| | |- _spacing.scss
| | |- _typography.scss # Typography sizes, weights, families, ...
| | ...
| |
| | functions/ # Sass Functions
| | |- _color.scss # Color manipulation functions
| |
| | mixins/ # Sass Mixins
| | helpers/ # Class & placeholders helpers
|
| vendors/
| | _bootstrap.scss # Bootstrap
| | _jquery-ui.scss # jQuery UI
| ... # Etc…
| 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)
| | _bulma-variables-overload.scss # Bulma CSS framework
| | _jquery-ui.scss # jQuery UI
| ... # Etc…
|
| base/
| | _reset.scss # Reset/normalize
| | _typography.scss # Typography rules
| ... # Etc…
| | _reset.scss # Reset/normalize
| | _typography.scss # Typography fonts imports
| ... # Etc…
|
| components/
| | _buttons.scss # Buttons
| | _carousel.scss # Carousel
| | _cover.scss # Cover
| | _dropdown.scss # Dropdown
| ... # Etc…
| components/ # Components of the UI, each corresponding to a UI block and being usable as a standalone
| | _button.scss
| | _button-group.scss
| | _global-search.scss
| | _quick-create.scss
| ...
|
| layout/
| | _navigation.scss # Navigation
| | _grid.scss # Grid system
| | _header.scss # Header
| | _footer.scss # Footer
| | _sidebar.scss # Sidebar
| | _forms.scss # Forms
| ... # Etc…
| layout/ # Elements of the UI made of several components, making the layout of the app
| | activity-panel/
| | dashboard/
| | object/ # DM object display (details, summary card, ...)
| | tab-container/
| ...
|
|- *application/ # Elements that are not usable as a standalone (like componants and layouts are) and very application (the backoffice) specific
|- *application/ # Elements that are not usable as a standalone (like componants and layouts are) and very application (the backoffice) specific
| |- display-block
| |- tabular-fields
| ...
|
|- *datamodel/ # SCSS / CSS3 variables and CSS classes for PHP classes of the DM that are part of the core (not in a module) and cannot be styled otherwise
|- *datamodel/ # SCSS / CSS3 variables and CSS classes for *PHP* classes of the DM that are part of the core (not in a module) and cannot be styled otherwise
| |- _action.scss
| |- _user.scss
| ...
|
| pages/
| | _home.scss # Home specific styles
| | _contact.scss # Contact specific styles
| ... # Etc…
| pages/ # SCSS / CSS3 variables and CSS classes for HTML elements specific to backoffice pages
| | _base.scss # Base for all backoffice pages
| | _audit.scss # Audit page
| | _csv-import.scss # CSV Import page
| ... # Etc…
|
|- *blocks-integrations # Specific rules for the integration of a block with another one, those kind of rules should never be in the block partial directly
| |- _panel-with-datatable.scss # Changes the negative margins of the datatable so it overlaps the panel's original padding
|- *blocks-integrations # Specific rules for the integration of a UI block with another one, those kind of rules should NEVER be in the block partial directly
| |- alert/
| | |- _alert-with-blocks.scss # How an alert should be displayed when after another block
| |- button/
| | |- _button-with-button.scss # How a button should be displayed when after another button
| | |- _button-with-button-group.scss # How a button should be displayed when before/after a button group
| |- panel/
| | |- _panel-with-blocks.scss # How a panel should be displayed when after another block
| | |- _panel-within-main-content.scss # How a panel becomes sticky when in the main content
| | |- _panel-within-modal.scss # How a panel becomes sticky when in a modal
| |- _tab-container-within-panel.scss # Changes the negative margins of the datatable so it overlaps the panel's original padding
| ...
|
| themes/
| | _theme.scss # Default theme
| | _admin.scss # Admin theme
| ... # Etc…
| | _page-banner.scss # ???
| ... # Etc…
|
|
`- _shame.scss # Shame file, should contain all the ugly hacks (https://sass-guidelin.es/#shame-file)
` main.scss # Main Sass file
|- _fallback.scss # Fallback file, should only contain rules that make standard HTML tags fallback to the style of a custom CSS class
|- _shame.scss # Shame file, should contain all the ugly hacks (https://sass-guidelin.es/#shame-file)
` main.scss # Main Sass file
```
## Usage
@@ -84,7 +117,58 @@ To avoid common errors, files should be imported in the final file in the follow
- Components
- Layout
- \*Application
- \*Datamodel
- Pages
- \*Block integrations
- Themes
- Shame file
- Shame file
# BEM methodology
## Principles
[BEM is a methodology](https://getbem.com/) that helps you to create reusable components and code sharing in frontend development. \
The main idea is to use discriminant classes instead of nested basic selectors for 2 main reasons:
* It's easier to understand the purpose of a specific class when seeing it in the HTML markup of the SCSS file
* It's easier to override a specific class when needed as you don't need to use a selector at least as precise/complex as the one you want to override
In our implementation, we start with the code of the UI block, followed by the sub-element, then the property or modifier. Separation is made of `--` instead of `__`.
## Examples
### Classes and CSS properties example
```scss
// SCSS variables:
// - For CSS properties: CSS class, followed by CSS property
$ibo-button--padding-y: 6px !default;
$ibo-button--padding-x: 9px !default;
$ibo-button--border: 0 !default;
$ibo-button--border-radius: $ibo-border-radius-400 !default;
$ibo-button--box-shadow-bottom: 0px 2px 0px !default;
$ibo-button--box-shadow-top: inset 0px 2px 0px !default;
$ibo-button--label--margin-left: $ibo-spacing-200 !default;
// CSS classes:
.ibo-button {
padding: $ibo-button--padding-y $ibo-button--padding-x;
border: $ibo-button--border;
border-radius: $ibo-button--border-radius;
}
.ibo-button--label {
margin-left: $ibo-button--label--margin-left;
}
```
### States example
```scss
// SCSS variables:
// Same rule as before, but with a `--is-` or `--on--` suffix
$ibo-quick-create--input--padding: $ibo-spacing-0 default;
$ibo-quick-create--input--padding-x--is-opened: $ibo-spacing-300 !default;
$ibo-quick-create--input--padding-y--is-opened: $ibo-spacing-300 !default;
$ibo-quick-create--input--width: $ibo-size-0 !default;
$ibo-quick-create--input--width--is-opened: 245px !default;
$ibo-quick-create--input--background-color: $ibo-color-white-100 !default;
$ibo-quick-create--input--background-color--on-hover: $ibo-color-grey-200 !default;
```

View File

@@ -6,5 +6,5 @@
$ibo-depression-100: inset 0 1px 1px 0 rgba(0, 0, 0, 0.15) !default;
:root{
--ibo-elevation-100: #{$ibo-depression-100};
--ibo-depression-100: #{$ibo-depression-100};
}

View File

@@ -64,6 +64,25 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
return $result;
}
/**
* @param cmdbAbstractObject $oObject
*
* @return bool
* @since 3.2.1 N°7534
*/
public static function IsAttachmentAllowedForObject(cmdbAbstractObject $oObject) : bool
{
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach ($aAllowedClasses as $sAllowedClass)
{
if ($oObject instanceof $sAllowedClass)
{
return true;
}
}
return false;
}
/**
* Returns the max. file upload size allowed as a dictionary entry
*

View File

@@ -36,6 +36,7 @@ require_once(APPROOT.'core/dict.class.inc.php');
require_once(APPROOT.'setup/xmldataloader.class.inc.php');
require_once(__DIR__.'/hubruntimeenvironment.class.inc.php');
/**
* Overload of DBBackup to handle logging
*/
@@ -79,11 +80,11 @@ function DoBackup($sTargetFile)
// Make sure the target directory exists
$sBackupDir = dirname($sTargetFile);
SetupUtils::builddir($sBackupDir);
$oBackup = new DBBackupWithErrorReporting();
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
$sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
$oMutex->Lock();
try
@@ -101,7 +102,7 @@ function DoBackup($sTargetFile)
/**
* Outputs the status of the current ajax execution (as a JSON structure)
*
*
* @param string $sMessage
* @param bool $bSuccess
* @param number $iErrorCode
@@ -123,7 +124,7 @@ function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = arra
/**
* Helper to output the status of a successful execution
*
*
* @param string $sMessage
* @param array $aMoreFields
* Extra fields to pass to the caller, if needed
@@ -135,7 +136,7 @@ function ReportSuccess($sMessage, $aMoreFields = array())
/**
* Helper to output the status of a failed execution
*
*
* @param string $sMessage
* @param number $iErrorCode
* @param array $aMoreFields
@@ -156,21 +157,21 @@ try
SetupUtils::ExitMaintenanceMode(false); // Reset maintenance mode in case of problem
utils::PushArchiveMode(false);
ini_set('max_execution_time', max(3600, ini_get('max_execution_time'))); // Under Windows SQL/backup operations are part of the PHP timeout and require extra time
ini_set('display_errors', 1); // Make sure that fatal errors remain visible from the end-user
// Most of the ajax calls are done without the MetaModel being loaded
// Therefore, the language must be passed as an argument,
// and the dictionnaries be loaded here
$sLanguage = utils::ReadParam('language', '');
if ($sLanguage!='')
{
foreach (glob(APPROOT.'env-production/dictionaries/*.dict.php') as $sFilePath)
foreach (glob(APPROOT.'env-'. utils::GetCurrentEnvironment(). '/dictionaries/*.dict.php') as $sFilePath)
{
require_once ($sFilePath);
}
$aLanguages = Dict::GetLanguages();
if (array_key_exists($sLanguage, $aLanguages))
{
@@ -211,7 +212,7 @@ try
}
}
break;
case 'do_backup':
require_once (APPROOT.'/application/startup.inc.php');
require_once (APPROOT.'/application/loginwebpage.class.inc.php');
@@ -254,8 +255,9 @@ try
ReportError($e->getMessage(), $e->getCode());
}
break;
case 'compile':
SetupLog::Info('Deployment starts...');
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
if (!file_exists(APPROOT.'data/hub/compile_authent') || $sAuthent !== file_get_contents(APPROOT.'data/hub/compile_authent'))
@@ -266,13 +268,13 @@ try
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', array());
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', array());
$oRuntimeEnv = new HubRunTimeEnvironment('production', false); // use a temp environment: production-build
$oRuntimeEnv = new HubRunTimeEnvironment(utils::GetCurrentEnvironment(), false); // use a temp environment: production-build
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
$oConfig = new Config(APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE);
if ($oConfig->Get('demo_mode')) throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
$aSelectModules = $oRuntimeEnv->CompileFrom('production', false); // WARNING symlinks does not seem to be compatible with manual Commit
$aSelectModules = $oRuntimeEnv->CompileFrom(utils::GetCurrentEnvironment(), false); // WARNING symlinks does not seem to be compatible with manual Commit
$oRuntimeEnv->UpdateIncludes($oConfig);
@@ -291,11 +293,11 @@ try
SetupLog::Info('Compilation completed...');
ReportSuccess('Ok'); // No access to Dict::S here
break;
case 'move_to_production':
// Second step: update the schema and the data
// Everything happening below is based on env-production
$oRuntimeEnv = new RunTimeEnvironment('production', true);
$oRuntimeEnv = new RunTimeEnvironment(utils::GetCurrentEnvironment(), true);
try
{
@@ -307,7 +309,7 @@ try
}
unlink(APPROOT.'data/hub/compile_authent');
// Load the "production" config file to clone & update it
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
$oConfig = new Config(APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE);
SetupUtils::EnterReadOnlyMode($oConfig);
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
@@ -375,7 +377,7 @@ try
unlink(APPROOT.'data/hub/compile_authent');
}
// Note: at this point, the dictionnary is not necessarily loaded
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted'), null, [$e->getMessage(), $e->getTraceAsString()]);
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
ReportError($e->getMessage(), $e->getCode());
}
@@ -384,17 +386,17 @@ try
SetupUtils::ExitReadOnlyMode();
}
break;
default:
ReportError("Invalid operation: '$sOperation'", -1);
}
}
catch (Exception $e)
{
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted'), null, [$e->getMessage(), $e->getTraceAsString()]);
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
utils::PopArchiveMode();
ReportError($e->getMessage(), $e->getCode());
}

View File

@@ -1,6 +1,6 @@
<?php
class HubRunTimeEnvironment extends RunTimeEnvironment
{
{
/**
* Constructor
* @param string $sEnvironment
@@ -9,7 +9,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
{
parent::__construct($sEnvironment, $bAutoCommit);
if ($sEnvironment != $this->sTargetEnv)
{
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv))
@@ -23,7 +23,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
SetupUtils::copydir(APPROOT.'/data/'.$sEnvironment.'-modules', APPROOT.'/data/'.$this->sTargetEnv.'-modules');
}
}
/**
* Update the includes for the target environment
* @param Config $oConfig
@@ -32,7 +32,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
{
$oConfig->UpdateIncludes('env-'.$this->sTargetEnv); // TargetEnv != FinalEnv
}
/**
* Move an extension (path to folder of this extension) to the target environment
* @param string $sExtensionDirectory The folder of the extension
@@ -45,7 +45,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
}
$sDestinationPath = APPROOT.'/data/'.$this->sTargetEnv.'-modules/';
// Make sure that the destination directory of the extension does not already exist
if (is_dir($sDestinationPath.basename($sExtensionDirectory)))
{
@@ -54,7 +54,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
}
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
}
/**
* Move the selected extensions located in the given directory in data/<target-env>-modules
* @param string $sDownloadedExtensionsDir The directory to scan

View File

@@ -20,7 +20,7 @@ function DisplayStatus(WebPage $oPage)
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
$oExtensionsMap = new iTopExtensionsMap(utils::GetCurrentEnvironment(), true, $aExtraDirs);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
@@ -156,7 +156,7 @@ function DoInstall(WebPage $oPage)
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
$oExtensionsMap = new iTopExtensionsMap(utils::GetCurrentEnvironment(), true, $aExtraDirs);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {

View File

@@ -97,6 +97,12 @@ p_object_attachment_add:
defaults:
_controller: 'Combodo\iTop\Portal\Controller\ObjectController::AttachmentAction'
p_object_attachment_display:
path: '/object/attachment/display/{sAttachmentId}'
defaults:
_controller: 'Combodo\iTop\Portal\Controller\ObjectController::AttachmentAction'
sOperation: 'display'
p_object_attachment_download:
path: '/object/attachment/download/{sAttachmentId}'
defaults:

View File

@@ -31,7 +31,7 @@ $.fn.modal.Constructor.prototype.enforceFocus = function () {
var $parent = $(e.target.parentNode);
if ($modalElement[0] !== e.target && !$modalElement.has(e.target).length &&
!$parent.hasClass('ck-input')) {
e.target.focus()
$(e.target.activeElement).focus();
}
})
};

View File

@@ -1241,6 +1241,19 @@ class ObjectController extends BrickController
break;
case 'display':
// Preparing redirection
// - Route
$aRouteParams = array(
'sObjectClass' => 'Attachment',
'sObjectId' => $this->oRequestManipulatorHelper->ReadParam('sAttachmentId', null),
'sObjectField' => 'contents',
);
$oResponse = $this->ForwardToRoute('p_object_document_display', $aRouteParams, $oRequest->query->all());
break;
default:
throw new HttpException(Response::HTTP_FORBIDDEN, Dict::S('Error:HTTP:400'));
break;

View File

@@ -94,6 +94,8 @@ class ObjectFormManager extends FormManager
*/
private $oFormHandlerHelper;
/** @var array $aPlugins plugins data */
private array $aPlugins = array();
/**
* @param string|array $formManagerData value of the formmanager_data portal parameter, either JSON or object
@@ -502,6 +504,11 @@ class ObjectFormManager extends FormManager
{
$aFieldsExtraData[$sFieldId]['opened'] = true;
}
// Checking if the field is handled by a plugin
if ($oFieldNode->hasAttribute('data-field-plugin'))
{
$aFieldsExtraData[$sFieldId]['plugin'] = $oFieldNode->getAttribute('data-field-plugin');
}
// Checking if field allows to ignore scope (For linked set)
if ($oFieldNode->hasAttribute('data-field-ignore-scopes') && ($oFieldNode->getAttribute('data-field-ignore-scopes') === 'true'))
{
@@ -651,6 +658,20 @@ class ObjectFormManager extends FormManager
// Building the form
foreach ($aFieldsAtts as $sAttCode => $iFieldFlags)
{
// handle plugins fields
if(array_key_exists($sAttCode, $aFieldsExtraData)
&& array_key_exists('plugin', $aFieldsExtraData[$sAttCode])){
$sPluginName = $aFieldsExtraData[$sAttCode]['plugin'];
switch($sPluginName){
case AttachmentPlugIn::class:
$this->AddAttachmentField($oForm, $sAttCode, $aFieldsExtraData);
break;
default:
throw new Exception('Unknown plugin ' . $sPluginName);
}
continue;
}
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sAttCode);
/** @var Field $oField */
@@ -990,49 +1011,12 @@ class ObjectFormManager extends FormManager
}
}
// Checking if the instance has attachments
if (class_exists('Attachment') && class_exists('AttachmentPlugIn'))
{
// Checking if the object is allowed for attachments
$bClassAllowed = false;
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach ($aAllowedClasses as $sAllowedClass)
{
if ($this->oObject instanceof $sAllowedClass)
{
$bClassAllowed = true;
break;
}
}
// Adding attachment field
if ($bClassAllowed)
{
// set id to a unique key - avoid collisions with another attribute that could exist with the name 'attachments'
$oField = new FileUploadField('attachments_plugin');
$oField->SetLabel(Dict::S('Portal:Attachments'))
->SetUploadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_add'))
->SetDownloadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_download',
array('sAttachmentId' => '-sAttachmentId-')))
->SetTransactionId($oForm->GetTransactionId())
->SetAllowDelete($this->oFormHandlerHelper->getCombodoPortalConf()['properties']['attachments']['allow_delete'])
->SetObject($this->oObject);
// Checking if we can edit attachments in the current state
if (($this->sMode === static::ENUM_MODE_VIEW)
|| AttachmentPlugIn::IsReadonlyState($this->oObject, $this->oObject->GetState(),
AttachmentPlugIn::ENUM_GUI_PORTALS) === true
|| $oForm->GetEditableFieldCount(true) === 0)
{
$oField->SetReadOnly(true);
}
// Adding attachements field in transition only if it is editable
if (!$this->IsTransitionForm() || ($this->IsTransitionForm() && !$oField->GetReadOnly()))
{
$oForm->AddField($oField);
}
}
// fallback Checking if the instance has attachments
// (in case attachment is not explicitly declared in layout)
if (class_exists('Attachment') && class_exists('AttachmentPlugIn')
&& !$this->IsPluginInitialized(AttachmentPlugIn::class)
&& AttachmentPlugIn::IsAttachmentAllowedForObject($this->oObject)){
$this->AddAttachmentField($oForm, 'attachments_plugin', $aFieldsExtraData);
}
$oForm->Finalize();
@@ -1040,6 +1024,78 @@ class ObjectFormManager extends FormManager
$this->oRenderer->SetForm($this->oForm);
}
/**
* IsPluginInitialized.
*
* @param string $sPluginName
*
* @return bool
*/
private function IsPluginInitialized(string $sPluginName) : bool
{
return array_key_exists($sPluginName, $this->aPlugins);
}
/**
* AddAttachmentField.
*
* @param $oForm
* @param $sId
* @param $aFieldsExtraData
*
* @throws \Exception
*/
private function AddAttachmentField($oForm, $sId, $aFieldsExtraData) : void
{
// only one instance allowed
if($this->IsPluginInitialized(AttachmentPlugIn::class)){
throw new Exception("Unable to process field `$sId`, AttachmentPlugIn has already been initialized with field `" . $this->aPlugins[AttachmentPlugIn::class]['field']->GetId() . '`');
}
// not allowed for object class
if(!AttachmentPlugIn::IsAttachmentAllowedForObject($this->oObject)){
throw new Exception("Unable to process field `$sId`, AttachmentPlugIn is not allowed for class `" . $this->oObject::class . '`');
}
// set id to a unique key - avoid collisions with another attribute that could exist with the name 'attachments'
$oField = new FileUploadField($sId);
$oField->SetLabel(Dict::S('Portal:Attachments'))
->SetUploadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_add'))
->SetDownloadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_download',
array('sAttachmentId' => '-sAttachmentId-')))
->SetDisplayEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_display',
array('sAttachmentId' => '-sAttachmentId-')))
->SetTransactionId($oForm->GetTransactionId())
->SetAllowDelete($this->oFormHandlerHelper->getCombodoPortalConf()['properties']['attachments']['allow_delete'])
->SetObject($this->oObject);
// Checking if we can edit attachments in the current state
$oObjectFormManager = $this;
$oField->SetOnFinalizeCallback(function() use ($oObjectFormManager, $oForm, $oField){
if (($oObjectFormManager->sMode === static::ENUM_MODE_VIEW)
|| AttachmentPlugIn::IsReadonlyState($oObjectFormManager->oObject, $oObjectFormManager->oObject->GetState(),
AttachmentPlugIn::ENUM_GUI_PORTALS) === true
|| $oForm->GetEditableFieldCount(true) === 0)
{
$oField->SetReadOnly(true);
}
});
if (array_key_exists($sId, $aFieldsExtraData) && array_key_exists('opened', $aFieldsExtraData[$sId])){
$oField->SetDisplayOpened(true);
}
// Adding attachments field in transition only if it is editable
if (!$this->IsTransitionForm() || !$oField->GetReadOnly()){
$oForm->AddField($oField);
}
// save plugin data
$this->aPlugins[AttachmentPlugIn::class] = [
'field' => $oField,
];
}
/**
* @inheritDoc
*

View File

@@ -24,7 +24,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,16 +24,16 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
<div>Passen Sie Ihre ' . ITOP_APPLICATION_SHORT . ' Präferenzen an, um ein personalisiertes Erlebnis zu erhalten.</div>',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Sagen Sie "Hallo" zum Newsroom',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Sagen Sie Auf Wiedersehen zu überfüllten Posteingängen und Hallo zu personalisierten Benachrichtigungen mit dem <b>Newsroom von '.ITOP_APPLICATION_SHORT.'</b>!</div>
<div>Der Newsroom ermöglicht es Ihnen, Benachrichtigungen innerhalb der Plattform einfach zu verwalten, sodass Sie über wichtige Updates informiert bleiben, ohne ständig Ihre E-Mails überprüfen zu müssen. Mit der Fähigkeit, Nachrichten als gelesen oder ungelesen zu markieren und alte Benachrichtigungen automatisch zu löschen, haben Sie die volle Kontrolle über Ihre Benachrichtigungen.</div>
<div><a href="%1$s" target="_blank">Der Newsroom</a> ermöglicht es Ihnen, Benachrichtigungen innerhalb der Plattform einfach zu verwalten, sodass Sie über wichtige Updates informiert bleiben, ohne ständig Ihre E-Mails überprüfen zu müssen. Mit der Fähigkeit, Nachrichten als gelesen oder ungelesen zu markieren und alte Benachrichtigungen automatisch zu löschen, haben Sie die volle Kontrolle über Ihre Benachrichtigungen.</div>
<br>
<div>Probieren Sie es noch heute aus und optimieren Sie die Benachrichtigungserfahrung von ' . ITOP_APPLICATION_SHORT . '!</div>',
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Title' => 'Benachrichtigungszentrum',
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>Da wir wissen, dass Ihre Informationsaufnahme bereits am Limit ist, können Sie nun leicht wählen, wie Sie Ihre Benachrichtigungen erhalten - per E-Mail, Chat oder sogar über die Newsroom-Funktion.</div>
<div>Sie möchten eine bestimmte Art von Benachrichtigungen nicht erhalten? Nichts leichter als das mit diesen erweiterten Anpassungsmöglichkeiten, die Ihnen die Flexibilität geben, Ihr Erlebnis an Ihre Bedürfnisse anzupassen.</div>
<br>
<div>Zugriff auf Ihr Benachrichtigungszentrum über den Newsroom oder über Ihre Präferenzen und vermeiden Sie Informationsüberflutung auf allen Ihren Kommunikationskanälen!</div>',
<div>Zugriff auf Ihr <a href="%1$s" target="_blank">Benachrichtigungszentrum</a> über den Newsroom oder über Ihre Präferenzen und vermeiden Sie Informationsüberflutung auf allen Ihren Kommunikationskanälen!</div>',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Barrierefreiheit für die Benutzeroberfläche von ' . ITOP_APPLICATION_SHORT . '',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>Um die Barrierefreiheit von ' . ITOP_APPLICATION_SHORT . ' zu gewährleisten, hat unser Team an neuen Back-Office-Themen gearbeitet. WCAG-konform konzentrieren sich diese Benutzeroberflächen darauf, es Benutzern mit Sehbehinderungen zu erleichtern, die Lösung zu verwenden:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>Um die Barrierefreiheit von ' . ITOP_APPLICATION_SHORT . ' zu gewährleisten, hat unser Team an <a href="%1$s" target="_blank">neuen Back-Office-Themen</a> gearbeitet. WCAG-konform konzentrieren sich diese Benutzeroberflächen darauf, es Benutzern mit Sehbehinderungen zu erleichtern, die Lösung zu verwenden:
<ul>
<li><b>Farbenblindes Thema:</b> Entwickelt, um Benutzern mit Farbenblindheit zu helfen, teilt sich dieses Thema tatsächlich in zwei Unterthemen auf, um sich an spezifische Fälle anzupassen:</li>
<ul>

View File

@@ -24,7 +24,7 @@ Dict::Add('EN US', 'English', 'English', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>',
@@ -32,9 +32,9 @@ Dict::Add('EN US', 'English', 'English', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('EN US', 'English', 'English', [
</ul>
</div>',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -25,7 +25,7 @@ Nous espérons que vous apprécierez cette version autant que nous avons pris pl
<br>
<div>Paramétrez vos préférences iTop pour une expérience personnalisée.</div>',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Decouvrez la newsroom',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Dites adieu aux boîtes de réception encombrées et bonjour aux alertes personnalisées grâce à la <b>Newsroom d\''.ITOP_APPLICATION_SHORT.'</b> !</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Dites adieu aux boîtes de réception encombrées et bonjour aux alertes personnalisées grâce à la <a href="%1$s" target="_blank">Newsroom d\''.ITOP_APPLICATION_SHORT.'</a> !</div>
<br>
<div>La Newsroom vous permet de gérer facilement les notifications au sein de la plateforme, afin que vous puissiez rester au courant des mises à jour importantes sans avoir à vérifier constamment vos méls. Avec la possibilité de marquer les messages comme lus ou non lus, et de supprimer automatiquement les anciennes notifications, vous avez un contrôle total sur vos notifications</div>
<br>
@@ -34,9 +34,9 @@ Nous espérons que vous apprécierez cette version autant que nous avons pris pl
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>Comme nous savons que vous êtes déjà saturé d\'informations, vous pouvez désormais choisir facilement la manière dont vous recevez vos notifications - par mél, par chat ou même par la fonction Newsroom.</div>
<div>Vous ne voulez pas recevoir un certain type d\'alerte ? Rien de plus facile avec ces fonctionnalités de personnalisation avancées qui permettent d\'adapter votre expérience utilisateur à vos besoins.</div>
<br>
<div>Accédez à votre centre de notifications via la newsroom ou vos préférences et évitez la surcharge d\'information sur vos canaux de communication !</div>',
<div>Accédez à votre <a href="%1$s" target="_blank">centre de notifications</a> via la newsroom ou vos préférences et évitez la surcharge d\'information sur vos canaux de communication !</div>',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibilité de l\'interface utilisateur d\'' . ITOP_APPLICATION_SHORT,
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>Afin d\'assurer l\'accessibilité d\'' . ITOP_APPLICATION_SHORT . ', notre équipe a travaillé sur de nouveaux thèmes pour le backoffice. Conformes aux normes WCAG, ces interfaces utilisateur visent à faciliter l\'utilisation de la solution par les utilisateurs souffrant de déficiences visuelles :
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>Afin d\'assurer l\'accessibilité d\'' . ITOP_APPLICATION_SHORT . ', notre équipe a travaillé sur de <a href="%1$s" target="_blank">nouveaux thèmes pour la console</a>. Conformes aux normes WCAG, ces interfaces utilisateur visent à faciliter l\'utilisation de la solution par les utilisateurs souffrant de déficiences visuelles :
<ul>
<li><b>Thème daltonien :</b> Conçu pour aider les utilisateurs daltoniens, ce thème se décompose en deux sous-thèmes pour s\'adapter à des cas spécifiques : </li>
<ul>
@@ -48,8 +48,8 @@ Nous espérons que vous apprécierez cette version autant que nous avons pris pl
</li>
</ul>
</div>',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Des notifications efficaces',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>La Newsroom d\''.ITOP_APPLICATION_SHORT.' vous offre une nouvelle façon d\'<b>automatiser</b> vos alertes en fonction d\'événements récurrents, ce qui vous permet de définir facilement des règles qui vous conviennent.</div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Des notifications évoluées',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>La Newsroom d\''.ITOP_APPLICATION_SHORT.' vous offre une nouvelle façon <a href="%1$s" target="_blank">d\'<b>automatiser</b> vos alertes en fonction d\'événements récurrents</a>, ce qui vous permet de définir facilement des règles qui vous conviennent.</div>
<div>Notre <b>tri des notifications basé sur la priorité</b> garantit que les messages importants sont affichés en premier, tandis que nos options de personnalisation des URL vous permettent de diriger les destinataires au bon endroit.</div>
<br>
<div>Grâce à la prise en charge de <b>plusieurs langues</b>, vous avez désormais un contrôle total sur l\'affichage de vos notifications.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
<br>
<div>Dostosuj swoją aplikację '.ITOP_APPLICATION_SHORT.' w preferencjach dotyczących spersonalizowanych doświadczeń.</div>',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Przywitaj się z newsroomem',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Pożegnaj zaśmiecone skrzynki odbiorcze i przywitaj się ze spersonalizowanymi alertami <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Pożegnaj zaśmiecone skrzynki odbiorcze i przywitaj się ze spersonalizowanymi alertami <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom umożliwia łatwe zarządzanie powiadomieniami w ramach platformy, dzięki czemu możesz być na bieżąco z ważnymi aktualizacjami bez ciągłego sprawdzania poczty. Dzięki możliwości oznaczania wiadomości jako przeczytanych lub nieprzeczytanych oraz automatycznego usuwania starych powiadomień, masz pełną kontrolę nad swoimi powiadomieniami. </div>
<br>
<div>Wypróbuj już dziś i usprawnij swoje doświadczenie komunikacji w ' . ITOP_APPLICATION_SHORT . '!</div>',

View File

@@ -24,7 +24,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -24,7 +24,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
<br>
<div>Customize your '.ITOP_APPLICATION_SHORT.' preferences for a personalized experience.</div>~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Title' => 'Say "Hello" to the newsroom~~',
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <b>'.ITOP_APPLICATION_SHORT.'\'s Newsroom</b>!</div>
'UI:WelcomePopup:Message:320_02_Newsroom:Description' => '<div>Say goodbye to cluttered inboxes and hello to personalized alerts with <a href="%1$s" target="_blank">'.ITOP_APPLICATION_SHORT.'\'s Newsroom</a>!</div>
<div>Newsroom allows you to easily manage notifications within the platform, so you can stay on top of important updates without constantly checking your email. With the ability to mark messages as read or unread, and automatically delete old notifications, you have complete control over your notifications. </div>
<br>
<div>Try it out today and streamline your ' . ITOP_APPLICATION_SHORT . '\'s communication experience!</div>~~',
@@ -32,9 +32,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'UI:WelcomePopup:Message:320_03_NotificationsCenter:Description' => '<div>As we know your information intake is already at its max, you can now easily choose how you receive your notifications - via email, chat, or even the Newsroom feature</div>
<div>You don\'t want to receive a certain type of alerts? Nothing easier with these advanced customization capabilities giving you the flexibility to tailor your experience to your needs. </div>
<br>
<div>Access your notifications center through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
<div>Access your <a href="%1$s" target="_blank">notifications center</a> through the newsroom or through your preferences and avoid information overload on all your communication channels!</div>~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Title' => 'Accessibility for ' . ITOP_APPLICATION_SHORT . '\'s UI~~',
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on new back-office themes. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
'UI:WelcomePopup:Message:320_05_A11yThemes:Description' => '<div>To ensure ' . ITOP_APPLICATION_SHORT . '\'s accessibility, our team has been working on <a href="%1$s" target="_blank">new back-office themes</a>. WCAG compliants, those UI focus on making it easier for users with visual impairments to use the solution:
<ul>
<li><b>Color-blind theme:</b> Designed to help users with colorblindness, this theme actually breaks down in two sub-themes to adapt to specific cases: </li>
<ul>
@@ -46,7 +46,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
</ul>
</div>~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title' => 'Powerful notifications~~',
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <b>automate</b> your alerts based on events with recurrence, so you can easily set up rules that work for you. </div>
'UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description' => '<div>'.ITOP_APPLICATION_SHORT.'\'s Newsroom gives you a new way to <a href="%1$s" target="_blank"><b>automate</b> your alerts based on events</a> with recurrence, so you can easily set up rules that work for you. </div>
<div>Our <b>priority-based notifications sorting</b> ensures that important messages are displayed first, while our URL customization options allow you to direct recipients to the right place. </div>
<br>
<div>With support for <b>multiple languages</b>, you have now complete control over your notifications display.</div>

View File

@@ -19,6 +19,7 @@ $(function()
sources: {},
excluded: {},
export_as_pdf: null,
transaction_id: null,
page_format: { label: 'Page Format:', values: { A3: 'A3', A4: 'A4', Letter: 'Letter' }, 'default': 'A4'},
page_orientation: { label: 'Page Orientation:', values: { P: 'Portait', L: 'Landscape' }, 'default': 'L' },
labels: {
@@ -590,6 +591,7 @@ $(function()
var sHtmlForm = '<div id="GraphExportDlg'+this.element.attr('id')+'"><form id="graph_'+this.element.attr('id')+'_export_dlg" target="_blank" action="'+sSubmitUrl+'" method="post">';
sHtmlForm += '<input type="hidden" name="g" value="'+this.options.grouping_threshold+'">';
sHtmlForm += '<input type="hidden" name="context_key" value="'+this.options.context_key+'">';
sHtmlForm += '<input type="hidden" name="transaction_id" value="'+this.options.transaction_id+'">';
$('#'+sId+'_contexts').multiselect('getChecked').each(function() {
sHtmlForm += '<input type="hidden" name="contexts['+$(this).val()+']" value="'+me.options.additional_contexts[$(this).val()].oql+'">';
});

View File

@@ -32,15 +32,6 @@ try
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/user.preferences.class.inc.php');
// check if header contains X-Combodo-Ajax for POST request (CSRF protection for ajax calls)
if (!isset($_SERVER['HTTP_X_COMBODO_AJAX']) && $_SERVER['REQUEST_METHOD'] !== 'GET') {
$sReferer = $_SERVER['HTTP_REFERER'];
$sErrorMsg = 'Unauthorized access. Please see https://www.itophub.io/wiki/page?id=3_2_0:release:developer#checking_for_the_presence_of_specific_header_in_the_post_to_enhance_protection_against_csrf_attacks';
IssueLog::Error("Unprotected ajax call : $sErrorMsg", LogChannels::SECURITY, ['referer' => $sReferer]);
header('HTTP/1.1 401 Unauthorized');
die($sErrorMsg);
}
IssueLog::Trace('----- Request: '.utils::GetRequestUri(), LogChannels::WEB_REQUEST);
$oKPI = new ExecutionKPI();
$oKPI->ComputeAndReport('Data model loaded');
@@ -67,6 +58,20 @@ try
break;
}
LoginWebPage::DoLoginEx($sRequestedPortalId, false);
// check if header contains X-Combodo-Ajax for POST request (CSRF protection for ajax calls)
// check must be performed after DoLoginEx to be logged in and to be able to check the token (based on the transaction id)
if (!isset($_SERVER['HTTP_X_COMBODO_AJAX']) && $_SERVER['REQUEST_METHOD'] !== 'GET') {
$sTransactionId = utils::ReadPostedParam("transaction_id");
if (!utils::IsTransactionValid($sTransactionId, false)) { // if a form is submitted without header but contains a token... should be exceptional
$sReferer = $_SERVER['HTTP_REFERER'];
$sErrorMsg = 'Unauthorized access. Please see https://www.itophub.io/wiki/page?id=3_2_0:release:developer#checking_for_the_presence_of_specific_header_in_the_post_to_enhance_protection_against_csrf_attacks';
IssueLog::Error("Unprotected ajax call : $sErrorMsg", LogChannels::SECURITY, ['referer' => $sReferer]);
header('HTTP/1.1 401 Unauthorized');
die($sErrorMsg);
}
}
$oKPI->ComputeAndReport('User login');
// N°2780 Fix ContextTag for console
@@ -777,12 +782,12 @@ try
$sClass = utils::ReadParam('className', '', false, 'class');
$sRootClass = utils::ReadParam('baseClass', '', false, 'class');
$currentId = utils::ReadParam('currentId', '');
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$sAction = utils::ReadParam('action', '');
$sSelectionMode = utils::ReadParam('selection_mode', null, false, 'raw_data');
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null, false, 'raw_data');
$scssCount = utils::ReadParam('css_count', null, false, 'raw_data');
$sTableInnerId = utils::ReadParam('table_inner_id', $sTableId, false, 'raw_data');
$sSelectionMode = utils::ReadParam('selection_mode');
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); // actually an Id not a selector
$scssCount = utils::ReadParam('css_count', null,false,utils::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR);
$sTableInnerId = utils::ReadParam('table_inner_id', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$oFilter = new DBObjectSearch($sClass);
$oSet = new CMDBObjectSet($oFilter);

View File

@@ -27,7 +27,11 @@ require_once(APPROOT.'core/metamodel.class.php');
IssueLog::Trace('----- Request: '.utils::GetRequestUri(), LogChannels::WEB_REQUEST);
utils::InitTimeZone();
set_error_handler(function ($errno, $errstr) {
$e = new \Exception("");
\SetupLog::Error("Catching", null, [$e->getMessage(), $e->getTraceAsString()]);
echo $e->getMessage() . '<BR>' .$e->getTraceAsString();
}, E_WARNING);
/**
* @param string $sPagePath full path (if symlink, it will be resolved)

View File

@@ -2259,7 +2259,7 @@ EOF
$this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false);
$this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, '');
$this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir);
$aParameters['class_category'] = $this->GetPropString($oField, 'class_category');
$aParameters['class_category'] = $this->GetPropString($oField, 'class_category', '');
$aParameters['more_values'] = $this->GetPropString($oField, 'more_values', '');
$aParameters['depends_on'] = $sDependencies;
}else {

View File

@@ -919,7 +919,7 @@ class WebPage implements Page
// Check if URI is absolute ("://" do allow any protocol), otherwise warn that it's a deprecated behavior
if (false === stripos($sLinkedScriptAbsUrl, "://")) {
IssueLog::Warning("Linked script added to page via deprecated API with a non absolute URL, it may lead to it not being loaded and causing javascript errors.", null, [
IssueLog::Debug("Linked script added to page with a non absolute URL, it may lead to it not being loaded and causing javascript errors. See alternatives WebPage::LinkScriptFromXXX", null, [
"linked_script_url" => $sLinkedScriptAbsUrl,
"request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */,
]);
@@ -1188,7 +1188,7 @@ JS;
// Check if URI is absolute ("://" do allow any protocol), otherwise warn that it's a deprecated behavior
if (false === stripos($sLinkedStylesheet, "://")) {
IssueLog::Warning("Linked stylesheet added to page via deprecated API with a non absolute URL, it may lead to it not being loaded and causing visual glitches.", null, [
IssueLog::Debug("Linked stylesheet added to page with a non absolute URL, it may lead to it not being loaded and causing visual glitches. See alternatives WebPage::LinkStylesheetFromXXX", null, [
"linked_stylesheet_url" => $sLinkedStylesheet,
"request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */,
]);

View File

@@ -6,6 +6,9 @@
namespace Combodo\iTop\Application\WelcomePopup\Provider;
use Combodo\iTop\Controller\Newsroom\iTopNewsroomController;
use Combodo\iTop\Controller\Notifications\NotificationsCenterController;
use Combodo\iTop\Service\Router\Router;
use Dict;
use AbstractWelcomePopupExtension;
use UserRights;
@@ -34,19 +37,19 @@ class DefaultProvider extends AbstractWelcomePopupExtension
MessageFactory::MakeForLeftIllustrationAsSVGMarkupRightTexts(
"320_02_Newsroom",
Dict::S("UI:WelcomePopup:Message:320_02_Newsroom:Title"),
Dict::S("UI:WelcomePopup:Message:320_02_Newsroom:Description"),
Dict::Format("UI:WelcomePopup:Message:320_02_Newsroom:Description", Router::GetInstance()->GenerateUrl(iTopNewsroomController::ROUTE_NAMESPACE . ".view_all")),
utils::GetAbsoluteUrlAppRoot() . "images/illustrations/undraw_newspaper.svg"
),
MessageFactory::MakeForLeftIllustrationAsSVGMarkupRightTexts(
"320_03_NotificationsCenter",
Dict::S("UI:WelcomePopup:Message:320_03_NotificationsCenter:Title"),
Dict::S("UI:WelcomePopup:Message:320_03_NotificationsCenter:Description"),
Dict::Format("UI:WelcomePopup:Message:320_03_NotificationsCenter:Description", Router::GetInstance()->GenerateUrl(NotificationsCenterController::ROUTE_NAMESPACE . ".display_page")),
utils::GetAbsoluteUrlAppRoot() . "images/illustrations/undraw_preferences_popup.svg"
),
MessageFactory::MakeForLeftTextsRightIllustrationAsSVGMarkup(
"320_05_A11yThemes",
Dict::S("UI:WelcomePopup:Message:320_05_A11yThemes:Title"),
Dict::S("UI:WelcomePopup:Message:320_05_A11yThemes:Description"),
Dict::Format("UI:WelcomePopup:Message:320_05_A11yThemes:Description", utils::GetAbsoluteUrlAppRoot() . "pages/preferences.php"),
utils::GetAbsoluteUrlAppRoot() . "images/illustrations/undraw_designer_mindset.svg"
),
];
@@ -57,7 +60,7 @@ class DefaultProvider extends AbstractWelcomePopupExtension
$aMessages[] = MessageFactory::MakeForLeftTextsRightIllustrationAsSVGMarkup(
"320_04_PowerfulNotifications_AdminOnly",
Dict::S("UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Title"),
Dict::S("UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description"),
Dict::Format("UI:WelcomePopup:Message:320_04_PowerfulNotifications_AdminOnly:Description", utils::GetAbsoluteUrlAppRoot() . "pages/notifications.php"),
utils::GetAbsoluteUrlAppRoot() . "images/illustrations/undraw_new_notifications.svg"
);
}

View File

@@ -30,6 +30,11 @@ class FileUploadField extends AbstractSimpleField
{
/** @var bool DEFAULT_ALLOW_DELETE */
const DEFAULT_ALLOW_DELETE = true;
/**
* @var bool DEFAULT_DISPLAY_OPENED
* @since 3.2.1 N°7534
*/
const DEFAULT_DISPLAY_OPENED = false;
/** @var string|null $sTransactionId */
protected $sTransactionId;
@@ -39,8 +44,18 @@ class FileUploadField extends AbstractSimpleField
protected $sUploadEndpoint;
/** @var string|null $sDownloadEndpoint */
protected $sDownloadEndpoint;
/**
* @var string|null $sViewEndpoint
* @since 3.2.1 N°7534
*/
protected ?string $sDisplayEndpoint;
/** @var bool $bAllowDelete */
protected $bAllowDelete;
/**
* @var bool $bDisplayOpened
* @since 3.2.1 N°7534
*/
protected bool $bDisplayOpened;
/**
* @inheritDoc
@@ -51,7 +66,9 @@ class FileUploadField extends AbstractSimpleField
$this->oObject = null;
$this->sUploadEndpoint = null;
$this->sDownloadEndpoint = null;
$this->sDisplayEndpoint = null;
$this->bAllowDelete = static::DEFAULT_ALLOW_DELETE;
$this->bDisplayOpened = static::DEFAULT_DISPLAY_OPENED;
parent::__construct($sId, $onFinalizeCallback);
}
@@ -134,6 +151,27 @@ class FileUploadField extends AbstractSimpleField
return $this;
}
/**
* @return string|null
* @since 3.2.1 N°7534
*/
public function GetDisplayEndpoint(): ?string
{
return $this->sDisplayEndpoint;
}
/**
* @param string $sDisplayEndpoint
*
* @return FileUploadField
* @since 3.2.1 N°7534
*/
public function SetDisplayEndpoint(string $sDisplayEndpoint): FileUploadField
{
$this->sDisplayEndpoint = $sDisplayEndpoint;
return $this;
}
/**
* @return bool
*/
@@ -153,4 +191,29 @@ class FileUploadField extends AbstractSimpleField
return $this;
}
/**
* Sets if the field should be displayed opened on initialization
*
* @param bool $bDisplayOpened
*
* @return FileUploadField
* @since 3.2.1 N°7534
*/
public function SetDisplayOpened(bool $bDisplayOpened) : FileUploadField
{
$this->bDisplayOpened = $bDisplayOpened;
return $this;
}
/**
* Returns if the field should be displayed opened on initialization
*
* @return boolean
* @since 3.2.1 N°7534
*/
public function GetDisplayOpened() : bool
{
return $this->bDisplayOpened;
}
}

View File

@@ -80,11 +80,17 @@ class BsFileUploadFieldRenderer extends BsFieldRenderer
$sFieldWrapperId = 'form_upload_wrapper_' . $this->oField->GetGlobalId();
$sFieldDescriptionForHTMLTag = ($this->oField->HasDescription()) ? 'data-tooltip-content="'.utils::HtmlEntities($this->oField->GetDescription()).'"' : '';
// If collapsed
$sCollapseTogglerClass .= ' collapsed';
$sCollapseTogglerExpanded = 'false';
$sCollapseTogglerIconClass = $sCollapseTogglerIconHiddenClass;
$sCollapseJSInitState = 'false';
// Preparing collapsed state
if ($this->oField->GetDisplayOpened()) {
$sCollapseTogglerExpanded = 'true';
$sCollapseTogglerIconClass = $sCollapseTogglerIconVisibleClass;
$sCollapseJSInitState = 'true';
} else {
$sCollapseTogglerClass .= ' collapsed';
$sCollapseTogglerExpanded = 'false';
$sCollapseTogglerIconClass = $sCollapseTogglerIconHiddenClass;
$sCollapseJSInitState = 'false';
}
// Label
$oOutput->AddHtml('<div class="form_field_label">');
@@ -183,6 +189,7 @@ JS
'{{iAttId}}',
'{{sLineStyle}}',
'{{sDocDownloadUrl}}',
'{{sDocDisplayUrl}}',
true,
'{{sAttachmentThumbUrl}}',
'{{sFileName}}',
@@ -234,6 +241,7 @@ JS
var \$oAttachmentTBody = $(this).closest('.fileupload_field_content').find('.attachments_container table#$sAttachmentTableId>tbody'),
iAttId = data.result.att_id,
sDownloadLink = '{$this->oField->GetDownloadEndpoint()}'.replace(/-sAttachmentId-/, iAttId),
sDisplayLink = '{$this->oField->GetDisplayEndpoint()}'.replace(/-sAttachmentId-/, iAttId),
sAttachmentMeta = '<input id="attachment_'+iAttId+'" type="hidden" name="attachments[]" value="'+iAttId+'"/>';
// hide "no attachment" line if present
@@ -247,6 +255,7 @@ JS
{search: "{{iAttId}}", replace:iAttId },
{search: "{{lineStyle}}", replace:'' },
{search: "{{sDocDownloadUrl}}", replace:sDownloadLink },
{search: "{{sDocDisplayUrl}}", replace:sDisplayLink },
{search: "{{sAttachmentThumbUrl}}", replace:data.result.icon },
{search: "{{sFileName}}", replace: data.result.msg },
{search: "{{sAttachmentMeta}}", replace:sAttachmentMeta },
@@ -402,6 +411,7 @@ HTML
$sFileName = utils::EscapeHtml($oDoc->GetFileName());
$sDocDownloadUrl = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDownloadEndpoint());
$sDocDisplayUrl = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDisplayEndpoint());
$sAttachmentThumbUrl = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$bHasPreview = false;
@@ -431,6 +441,7 @@ HTML
$iAttId,
$sLineStyle,
$sDocDownloadUrl,
$sDocDisplayUrl,
$bHasPreview,
$sAttachmentThumbUrl,
$sFileName,
@@ -485,6 +496,7 @@ HTML;
* @param int $iAttId
* @param string $sLineStyle
* @param string $sDocDownloadUrl
* @param string $sDocDisplayUrl
* @param bool $bHasPreview replace string $sIconClass since 3.0.1
* @param string $sAttachmentThumbUrl
* @param string $sFileName
@@ -499,7 +511,7 @@ HTML;
* @since 2.7.0
*/
protected static function GetAttachmentTableRow(
$iAttId, $sLineStyle, $sDocDownloadUrl, $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize,
$iAttId, $sLineStyle, $sDocDownloadUrl, $sDocDisplayUrl, $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize,
$iFileSizeRaw, $iFileDownloadsCount, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed
) {
$sDeleteCell = '';
@@ -511,16 +523,16 @@ HTML;
$sHtml = "<tr id=\"display_attachment_{$iAttId}\" class=\"attachment\" $sLineStyle>";
if($bHasPreview) {
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDownloadUrl\" target=\"_blank\" data-tooltip-content=\"<img class='attachment-tooltip' src='{$sDocDownloadUrl}'>\" data-tooltip-html-enabled=true><img src=\"$sAttachmentThumbUrl\" ></a></td>";
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDisplayUrl\" target=\"_blank\" data-tooltip-content=\"<img class='attachment-tooltip' src='{$sDocDownloadUrl}'>\" data-tooltip-html-enabled=true><img src=\"$sAttachmentThumbUrl\" ></a></td>";
} else {
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDownloadUrl\" target=\"_blank\"><img src=\"$sAttachmentThumbUrl\" ></a></td>";
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDisplayUrl\" target=\"_blank\"><img src=\"$sAttachmentThumbUrl\" ></a></td>";
}
$sHtml .= <<<HTML
<td role="filename"><a href="$sDocDownloadUrl" target="_blank">$sFileName</a>$sAttachmentMeta</td>
<td role="filename"><a href="$sDocDisplayUrl" target="_blank">$sFileName</a>$sAttachmentMeta</td>
<td role="formatted-size" data-order="$iFileSizeRaw">$sFileSize</td>
<td role="upload-date" data-order="$iAttachmentDateRaw">$sAttachmentDate</td>
<td role="downloads-count">$iFileDownloadsCount</td>
<td role="downloads-count"><a href="$sDocDownloadUrl" target="_blank"><span class="fas fa-download fa-lg" style="float: right;"></span></a>$iFileDownloadsCount</td>
$sDeleteCell
</tr>
HTML;

View File

@@ -254,7 +254,6 @@ JS
CombodoCKEditorHandler.GetInstance("#{$this->oField->GetGlobalId()}")
.then((oCKEditor) => {
oCKEditor.model.document.on("change:data", () => {
console.log("desc changed!");
const oFieldElem = $("#{$this->oField->GetGlobalId()}");
oFieldElem.closest(".field_set").trigger("field_change", {
id: oFieldElem.attr("id"),

View File

@@ -309,7 +309,7 @@ EOF
$(oInput).datepicker({
"showOn":"button",
"buttonText":"<i class=\"fas fa-calendar-alt\"><\/i>",
"buttonText":"",
"dateFormat": $sJSDateFormat,
"constrainInput":false,
"changeMonth":true,
@@ -332,7 +332,7 @@ EOF
$(oInput).datetimepicker({
showOn: 'button',
buttonText: "<i class=\"fas fa-calendar-alt\"><\/i>",
buttonText: "",
dateFormat: $sJSDateFormat,
constrainInput: false,
changeMonth: true,

View File

@@ -13,143 +13,256 @@ use DeprecatedCallsLog;
class DeprecatedCallsLogTest extends ItopTestCase
{
/**
* We are testing for a undefined offset error. This was throwing a Notice, but starting with PHP 8.0 it was converted to a Warning ! Also the message was changed :(
*
* @link https://www.php.net/manual/en/migration80.incompatible.php check "A number of notices have been converted into warnings:"
* @dataProvider StripCallStackProvider
*/
private function SetUndefinedOffsetExceptionToExpect(): void
public function testStripCallStack($sInputStack, $sExpectedStack)
{
/** @noinspection ConstantCanBeUsedInspection Preferring the function call as it is easier to read and won't cost that much in this PHPUnit context */
if (version_compare(PHP_VERSION, '8.0', '>=')) {
$this->expectWarning();
$sUndefinedOffsetExceptionMessage = 'Undefined array key "tutu"';
} else {
$this->expectNotice();
$sUndefinedOffsetExceptionMessage = 'Undefined index: tutu';
}
$this->expectExceptionMessage($sUndefinedOffsetExceptionMessage);
$this->assertEquals(
$sExpectedStack,
$this->InvokeNonPublicStaticMethod(DeprecatedCallsLog::class, 'StripCallStack', [$sInputStack]),
'The top item of the call stack should be the first item meaningful to track deprecated calls, not the intermediate layers of the PHP engine'
);
}
public function testPhpNoticeWithoutDeprecatedCallsLog(): void
{
$this->SetUndefinedOffsetExceptionToExpect();
$aArray = [];
if ('toto' === $aArray['tutu']) {
//Do nothing, just raising a undefined offset warning
}
}
/**
* @runInSeparateProcess Necessary, due to the DeprecatedCallsLog being enabled (no mean to reset)
*
* The error handler set by DeprecatedCallsLog during startup was causing PHPUnit to miss PHP notices like "undefined offset"
*
* The error handler is now disabled when running PHPUnit
*
* @since 3.0.4 N°6274
* @covers DeprecatedCallsLog::DeprecatedNoticesErrorHandler
*/
public function testPhpNoticeWithDeprecatedCallsLog(): void
{
$this->RequireOnceItopFile('core/log.class.inc.php');
DeprecatedCallsLog::Enable(); // will set error handler
$this->SetUndefinedOffsetExceptionToExpect();
$aArray = [];
if ('toto' === $aArray['tutu']) {
//Do nothing, just raising a undefined offset warning
}
}
/**
* @dataProvider GetMessageFromStackProvider
*/
public function testGetMessageFromStack($aDebugBacktrace, $sExpectedMessage): void
{
$sActualMessage = $this->InvokeNonPublicStaticMethod(DeprecatedCallsLog::class, 'GetMessageFromStack', [$aDebugBacktrace]);
$this->assertEquals($sExpectedMessage, $sActualMessage);
}
public function GetMessageFromStackProvider()
public function StripCallStackProvider()
{
return [
'Call in a file outside of a function or class' => [
[
/* A deprecated PHP method is invoked from scratch.php, at line 25 (note: the name of the method is not present in the callstack) */
'Should preserve the handler when the notice is fired by PHP itself' => [
'in' => [
[
'file' => 'whateverfolder/scratch.php',
'line' => 25,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
],
'out' => [
[
'file' => 'whateverfolder/scratch.php',
'line' => 25,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
],
],
'Should skip the handler when the notice is fired by a call to trigger_error' => [
'in' => [
[
'file' => 'C:\Dev\wamp64\www\itop-32\sources\Application\WebPage\WebPage.php',
'line' => '866',
'function' => 'NotifyDeprecatedPhpMethod',
'class' => 'DeprecatedCallsLog',
'type' => '::',
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php',
'line' => '130',
'function' => 'add_linked_script',
'class' => 'Combodo\iTop\Application\WebPage\WebPage',
'type' => '->',
'file' => 'whateverfolder/scratch.php',
'line' => 25,
'function' => 'trigger_error',
],
],
'out' => [
[
'file' => 'whateverfolder/scratch.php',
'line' => 25,
'function' => 'trigger_error',
],
],
],
'Should skip two levels when the notice is fired by trigger_deprecation (Symfony helper)' => [
'in' => [
[
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'C:\Dev\wamp64\www\itop-32\pages\exec.php',
'line' => '102',
'args' => ['C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php'],
'file' => 'symfony/deprecation.php',
'line' => 12,
'function' => 'trigger_error',
],
[
'file' => 'symfony/service.php',
'line' => 25,
'function' => 'trigger_deprecation',
],
],
'out' => [
[
'file' => 'symfony/service.php',
'line' => 25,
'function' => 'trigger_deprecation',
],
],
],
];
}
/**
* @dataProvider SummarizeCallStackProvider
*/
public function testSummarizeCallStack($sInputStackStripped, $sExpectedSummary)
{
$this->assertEquals(
$sExpectedSummary,
$this->InvokeNonPublicStaticMethod(DeprecatedCallsLog::class, 'SummarizeCallStack', [$sInputStackStripped])
);
}
public function SummarizeCallStackProvider()
{
// All tests are based on a call stack issued from a deprecated PHP function
// Other cases are similar: what counts on the top level item is the file and line number of the deprecated call
return [
'From the main page (deprecated PHP function)' => [
'in:stripped call stack' => [
[
'file' => 'whateverfolder/scratch.php',
'line' => 25,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
],
'out' => 'whateverfolder/scratch.php#25',
],
'From an iTop method (deprecated PHP function)' => [
'in:stripped call stack' => [
[
'file' => 'whateverfolder/someclass.php',
'line' => 18,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'whateverfolder/index.php',
'line' => 25,
'function' => 'SomeMethod',
'class' => 'SomeClass',
'type' => '->',
],
],
'out' => 'SomeClass->SomeMethod (whateverfolder/someclass.php#18), itself called from whateverfolder/index.php#25',
],
'From an iTop static method (deprecated PHP function)' => [
'in:stripped call stack' => [
[
'file' => 'whateverfolder/someclass.php',
'line' => 18,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'whateverfolder/index.php',
'line' => 25,
'function' => 'SomeMethod',
'class' => 'SomeClass',
'type' => '::',
],
],
'out' => 'SomeClass::SomeMethod (whateverfolder/someclass.php#18), itself called from whateverfolder/index.php#25',
],
'From an iTop function (deprecated PHP function)' => [
'in:stripped call stack' => [
[
'file' => 'whateverfolder/someclass.php',
'line' => 18,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'whateverfolder/index.php',
'line' => 25,
'function' => 'SomeFunction',
],
],
'out' => 'SomeFunction (whateverfolder/someclass.php#18), itself called from whateverfolder/index.php#25',
],
'From a code snippet (deprecated PHP function)' => [
'in:stripped call stack' => [
[
'file' => 'itop-root/env-production/core/main.php',
'line' => 1290,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'itop-root/core/metamodel.class.php',
'line' => 6698,
'function' => 'require_once',
],
[
'file' => 'itop-root/env-production/autoload.php',
'line' => 6,
'function' => 'IncludeModule',
'class' => 'MetaModel',
'type' => '::',
],
[
'file' => 'itop-root/core/metamodel.class.php',
'line' => 6487,
'function' => 'require_once',
],
],
'Call to Combodo\iTop\Application\WebPage\WebPage::add_linked_script in C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php#L130 (from C:\Dev\wamp64\www\itop-32\pages\exec.php#L102)',
'out' => 'itop-root/env-production/core/main.php#1290'
],
'Call in a file function, outside of a class' => [
[
[
'file' => 'C:\\Dev\\wamp64\\www\\itop-32\\sources\\Application\\WebPage\\WebPage.php',
'line' => 866,
'function' => 'NotifyDeprecatedPhpMethod',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'C:\\Dev\\wamp64\\www\\itop-32\\extensions\\itop-object-copier\\copy.php',
'line' => 81,
'function' => 'add_linked_script',
'class' => 'Combodo\\iTop\\Application\\WebPage\\WebPage',
'type' => '->',
],
[
'file' => 'C:\\Dev\\wamp64\\www\\itop-32\\extensions\\itop-object-copier\\copy.php',
'line' => 123,
'function' => 'myFunction',
],
'From a persistent object method (deprecated PHP function)' => [
'in:stripped call stack' => [
[
'file' => 'itop-root/env-production/itop-tickets/model.itop-tickets.php',
'line' => 165,
'function' => 'DeprecatedNoticesErrorHandler',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'itop-root/core/dbobject.class.php',
'line' => 6575,
'function' => 'OnBeforeWriteTicket',
'class' => 'Ticket',
'type' => '->',
],
[
'file' => 'itop-root/application/cmdbabstract.class.inc.php',
'line' => 5933,
'function' => 'FireEvent',
'class' => 'DBObject',
'type' => '->',
],
[
'file' => 'itop-root/core/dbobject.class.php',
'line' => 3643,
'function' => 'FireEventBeforeWrite',
'class' => 'cmdbAbstractObject',
'type' => '->',
],
[
'file' => 'itop-root/application/cmdbabstract.class.inc.php',
'line' => 4593,
'function' => 'DBUpdate',
'class' => 'DBObject',
'type' => '->',
],
[
'file' => 'itop-root/sources/Controller/Base/Layout/ObjectController.php',
'line' => 649,
'function' => 'DBUpdate',
'class' => 'cmdbAbstractObject',
'type' => '->',
],
[
'file' => 'itop-root/pages/UI.php',
'line' => 720,
'function' => 'OperationApplyModify',
'class' => 'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController',
'type' => '->',
],
],
'Call to Combodo\iTop\Application\WebPage\WebPage::add_linked_script in C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php#L81 (from C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php#L123)',
],
'Call from a class method' => [
[
[
'file' => 'C:\\Dev\\wamp64\\www\\itop-32\\sources\\Application\\WebPage\\WebPage.php',
'line' => 866,
'function' => 'NotifyDeprecatedPhpMethod',
'class' => 'DeprecatedCallsLog',
'type' => '::',
],
[
'file' => 'C:\\Dev\\wamp64\\www\\itop-32\\extensions\\itop-object-copier\\copy.php',
'line' => 82,
'function' => 'add_linked_script',
'class' => 'Combodo\\iTop\\Application\\WebPage\\WebPage',
'type' => '->',
],
[
'file' => 'C:\\Dev\\wamp64\\www\\itop-32\\extensions\\itop-object-copier\\copy.php',
'line' => 125,
'function' => 'MyMethod',
'class' => 'MyClass',
'type' => '::',
],
],
'Call to Combodo\iTop\Application\WebPage\WebPage::add_linked_script in C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php#L82 (from MyClass::MyMethod in C:\Dev\wamp64\www\itop-32\extensions\itop-object-copier\copy.php#L125)',
'out' => 'Ticket->OnBeforeWriteTicket (itop-root/env-production/itop-tickets/model.itop-tickets.php#165), itself called from DBObject->FireEvent (itop-root/core/dbobject.class.php#6575)'
],
];
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Hub;
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\iUIBlockFactory;
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class HubSetupTest extends ItopDataTestCase {
const USE_TRANSACTION = false;
public function setUp() : void {
parent::setUp();
$this->RequireOnceItopFile("application/utils.inc.php");
$this->RequireOnceItopFile("core/log.class.inc.php");
$this->RequireOnceItopFile("setup/runtimeenv.class.inc.php");
$this->RequireOnceItopFile("setup/backup.class.inc.php");
$this->RequireOnceItopFile("core/mutex.class.inc.php");
$this->RequireOnceItopFile("core/dict.class.inc.php");
$this->RequireOnceItopFile("setup/xmldataloader.class.inc.php");
$this->RequireOnceItopFile("datamodels/2.x/itop-hub-connector/hubruntimeenvironment.class.inc.php");
}
public function testSetupViaHub() {
$sEnvironment = 'test';
$_REQUEST['switch_env']=$sEnvironment;
Session::Set('itop_env', $sEnvironment);
require_once (APPROOT.'/application/startup.inc.php');
require_once (APPROOT.'/application/loginwebpage.class.inc.php');
/*$aSelectedExtensionCodes = ['molkobain-datacenter-view'];
$aSelectedExtensionDirs = $aSelectedExtensionCodes;
ini_set('display_errors', 1);
$oRuntimeEnv = new \HubRunTimeEnvironment($sEnvironment, false); // use a temp environment: production-build
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);
$oConfig = new \Config(APPCONF."$sEnvironment/".ITOP_CONFIG_FILE);
$oRuntimeEnv->CompileFrom("production", false); // WARNING symlinks does not seem to be compatible with manual Commit
$oRuntimeEnv->UpdateIncludes($oConfig);*/
//$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
// Safety check: check the inter dependencies, will throw an exception in case of inconsistency
/*$oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
$oRuntimeEnv->CheckMetaModel(); // Will throw an exception if a problem is detected
// Everything seems Ok so far, commit in env-production!
$oRuntimeEnv->WriteConfigFileSafe($oConfig);
$oRuntimeEnv->Commit();*/
/*$sPath = APPROOT.'data/downloaded-extensions/';
$aExtraDirs = array();
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new \iTopExtensionsMap($sEnvironment, true, $aExtraDirs);*/
//InterfaceDiscovery::GetInstance()->FindItopClasses(iUIBlockFactory::class);
$sPassword = "abCDEF12345@";
/** @var User oUser */
$this->oUser = $this->CreateContactlessUser('login' . uniqid(),
ItopDataTestCase::$aURP_Profiles['Administrator'],
$sPassword
);
$sLogin = $this->oUser->Get('login');
$aPostFields = [
'auth_user' => $sLogin,
'auth_pwd' => $sPassword,
];
//$oConfig = new \Config();
$sConfigPath = APPCONF . "$sEnvironment/config-itop.php"; //$oConfig->GetLoadedFile();
@chmod($sConfigPath, 0770);
//$oConfig->WriteToFile($sConfigPath);
//@chmod($sConfigPath, 0440);
$sOutput = $this->CallItopUrl("/pages/exec.php?exec_module=itop-hub-connector&exec_page=ajax.php&switch_env=$sEnvironment&exec_env=$sEnvironment&maintenance=1",
[
'auth_user' => $sLogin,
'auth_pwd' => $sPassword,
'operation' => "compile",
'extension_codes[]' => "molkobain-datacenter-view",
'extension_dirs[]' => "molkobain-datacenter-view",
'authent' => '14b5da9d092f84044187421419a0347e7317bc8cd2b486fdda631be06b959269',
]);
/*$sOutput = $this->CallItopUrl("/pages/exec.php?exec_module=itop-hub-connector&exec_page=land.php&switch_env=$sEnvironment&exec_env=$sEnvironment&operation=install",
$aPostFields);*/
//var_dump($sOutput);
$aRes = json_decode($sOutput, true);
$this->assertNotNull($aRes, "output should be a json without any warning:" . PHP_EOL . $sOutput);
}
protected function CallItopUrl($sUri, ?array $aPostFields = null, $bXDebugEnabled = false)
{
$ch = curl_init();
if ($bXDebugEnabled) {
curl_setopt($ch, CURLOPT_COOKIE, 'XDEBUG_SESSION=phpstorm');
}
$sUrl = \MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
var_dump($sUrl);
curl_setopt($ch, CURLOPT_URL, $sUrl);
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$sOutput = curl_exec($ch);
//echo "$sUrl error code:".curl_error($ch);
curl_close($ch);
return $sOutput;
}
}