Compare commits

...

58 Commits

Author SHA1 Message Date
odain
18c52f1a71 enhance/fix recursive deps computation 2025-09-19 16:57:58 +02:00
odain
045a9c5658 fix class renaming 2025-09-19 16:32:57 +02:00
odain
4367bb2868 ci : fix path check 2025-09-19 16:00:02 +02:00
odain
5f1765504c fix folder not existing (ie production-modules) 2025-09-19 15:45:32 +02:00
odain
bca79a1a06 mv ModulesDependencyValidationServiceTest to run it at commit 2025-09-19 15:37:34 +02:00
odain
8c30199e9f cleanup after rebasing 2025-09-19 15:10:41 +02:00
odain
996b98c4c7 refactoring: code cleanup/standardization/remove all prototype stuffs 2025-09-15 14:32:48 +02:00
odain
5dea3c7ce2 refactoring: code cleanup/standardization/remove all prototype stuffs 2025-09-15 11:14:14 +02:00
odain
609215857b add deps validation to extension ci job 2025-09-11 22:07:04 +02:00
odain
ae83d677a3 fix ci 2025-09-11 19:00:59 +02:00
odain
8a017b6e95 fix ci: test broken when dir to scan did not exist like production-modules 2025-09-11 17:32:06 +02:00
odain
dda99616a5 fix tests 2025-09-11 16:49:17 +02:00
odain
e37322e631 module dependency validation moved in a core folder + cleanup dedicated unit/integration tests 2025-09-11 12:09:24 +02:00
odain
64bec2181f forget dependency computation optimization seen as too risky + keep only user friendly sort in case of setup error 2025-09-11 12:08:11 +02:00
odain
6ed0f8ef3a rebase on develop + split new sort computation apart from modulediscovery 2025-09-09 23:12:18 +02:00
odain
b194a0b17c revert to previous legacy order + gather new module computation classes in a dedicated folder 2025-09-09 21:59:25 +02:00
odain
4207b25bde make validation work (dirty way) + cleanup 2025-09-09 21:57:57 +02:00
odain
9f2da19da0 make setup deterministic: complete dependency order with alphabetical one when 2 module elements are at same position 2025-09-09 21:57:57 +02:00
odain
21c4ffa9e3 final deps validation bases on DM and PHP classes 2025-09-09 21:57:57 +02:00
odain
b31a7fac54 init in beforeclass + read defined classes/interfaces by module 2025-09-09 21:57:57 +02:00
odain
46d8591761 module discovery classes renaming to avoid collision with customer DM definitions 2025-09-09 21:57:57 +02:00
odain
18a66e6884 read module file data apart from ModuleDiscovery 2025-09-09 21:57:57 +02:00
odain
6c13464011 cleanup 2025-09-09 21:57:57 +02:00
odain
809d1a5f7d cleanup 2025-09-09 21:57:57 +02:00
odain
217e890650 fix inconsistent module dependencies 2025-09-09 21:57:57 +02:00
odain
d7e02d3859 fix integration check 2025-09-09 21:57:57 +02:00
odain
1d7e776f6d save tmp work before trying to fetch other wml deps 2025-09-09 21:57:57 +02:00
odain
6319d0a9cb fix module dependencies 2025-09-09 21:57:57 +02:00
odain
5c051a30d3 fix DM filename typo 2025-09-09 21:57:57 +02:00
odain
78e13fffb4 rename ModuleXXX classes by iTopCoreModuleXXX to reduce collisions with extensions 2025-09-09 21:57:57 +02:00
odain
bb16108f04 add phpdoc + add more tests 2025-09-09 21:57:57 +02:00
odain
460a3e356e module dependency optimization - refacto + dependency new sort order 2025-09-09 21:57:57 +02:00
odain
dd68017020 module dependency optimization - stop computation when no new dependency is resolved 2025-09-09 21:56:26 +02:00
odain
0281721638 enhance module dependency computation for optimization and admin feedback 2025-09-09 21:56:26 +02:00
odain-cbd
15103dc49f N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746)
* N°4789 - Parse datamodel module.xxx.php files instead of interpreting them - refactoring all in a dedicated service first

* N°4789 - fix broken setup + tests

* N°4789 - replace legacy eval by module file parsing

* N°4789 - handle constants and if conditional structures

* N°4789 - compute boolean expressions

* N°4789 - make autoselect and dependencies work as well

* cleanup

* N°4789 - fix BeforeWritingConfig calls during setup

* N°4789 - refactor and split in ModuleDiscoveryEvaluationService + handle ModuleInstallerAPI methods calls during setup

* N°4789 - PR review changes with Romain

* PR review + code cleanup + added usecases and test cover

* temp evaluation work

* replace eval by iTop custom evaluation classes

* move PhpParser/Evaluation classes in a specific namespave + composer dumpautoload

* fix broken setup

* fix broken setup

* complete Evaluators list + autoload

* cleanup useless testing resources

* cleanup + replace last eval call in VariableEvaluator

* fix few Evaluators code

* enhance nikic evaluators + test with/without nikic lib

* Evaluator fixes/enhancements + tests

* bump to nikic fork temporarly

* bump nikic-parser fork + use only nikic fork  evaluation + cleanup itop redondant evaluators

* review with Romain: use distinct whitelists in setup time/runtime + move ModuleFileParser internal logic into ModuleFileReader

* PhpExpressionEvaluator used via constructor and not as a service

* dumpautoload again after rebase
2025-09-09 17:54:18 +02:00
jf-cbd
2ee68ff819 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	sources/SessionTracker/iSessionHandlerExtension.php
2025-09-05 15:19:38 +02:00
Benjamin Dalsass
44972f34e5 N°8210 - Remove iApplicationObjectExtension (#737) 2025-09-05 09:27:55 +02:00
jf-cbd
65c9145b10 N°8570 - Allow @ as part of url - add test 2025-09-04 17:04:21 +02:00
Stephen Abello
a104310379 N°8579 - Fix calls to Twig "spaceless" deprecated filter 2025-09-04 14:57:24 +02:00
Anne-Cath
f5cce23bb4 Revert "N°8579 - remove "spaceless" in twig files"
This reverts commit ee993ef80a.
2025-09-03 10:37:26 +02:00
Stephen Abello
0f39106b56 N°7761 - De-hardcode SCSS values 2025-09-02 16:30:20 +02:00
Anne-Cath
ee993ef80a N°8579 - remove "spaceless" in twig files 2025-08-29 17:15:47 +02:00
Anne-Cath
0a4180f7fc N°7257 - Check and migrate old Combodo JS functions 2025-08-29 16:15:31 +02:00
Anne-Cath
b56113aada N°8190 - Userrights management override is deprecated - add deprecated 2025-08-29 15:12:36 +02:00
Anne-Cath
c82c150411 N°7257 - Check and migrate old Combodo JS functions 2025-08-29 14:27:38 +02:00
Anne-Cath
00818f411f N°3817 - Audit and fix calls to deprecated jQuery method - rollback change on caselog-entry-form.js 2025-08-29 11:07:44 +02:00
Timmy38
a6a459967e N°6759 factorize config (#738)
* N°6759 - Factorize code in config
2025-08-28 11:04:49 +02:00
Anne-Cath
2ba4109343 N°6932 - Split applicationextension.inc.php APIs in separate files 2025-08-26 17:46:10 +02:00
Timmy38
738664e560 N°4107 - DBObject::GetName() is now final 2025-08-26 14:39:43 +02:00
Anne-Cath
d48bc15211 N°8190 - Userrights management should not be overridable anymore 2025-08-26 09:50:14 +02:00
Anne-Cath
32f0cfb091 Remove deprecated message 2025-08-26 09:32:31 +02:00
Anne-Cath
20393b55b9 N°8190 - Userrights management should not be overridable anymore 2025-08-26 09:17:51 +02:00
Stephen Abello
437296eab9 N°8634 - Newsroom providers are hardly visible if there's more than 2 2025-08-20 16:00:14 +02:00
Stephen Abello
7cf6f65265 Fix typo thanks to @jbostoen 2025-08-06 09:04:36 +02:00
Björn Rudner
491f55c7bd 🐛 Allow @ as part of url (#729) 2025-08-01 15:27:37 +02:00
Stephen Abello
7b44ec23a1 Fix return type in PHPDoc thanks to @jbostoen 2025-07-28 16:14:13 +02:00
BenGrenoble
f00b8d529c N°8286 - Be able to add additional data in iTop session files via custom classes
Change to fit convention.
2025-07-24 10:59:48 +02:00
BenGrenoble
af44ffd0ad N°8286 - Be able to add additional data in iTop session files via custom classes
Experimental warning
2025-07-24 10:28:15 +02:00
193 changed files with 8613 additions and 4968 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
<?php
/**
* Extend this class instead of implementing iApplicationUIExtension if you don't need to overload
*
* @api
* @package UIExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
{
/**
* @inheritDoc
*/
public function OnDisplayProperties($oObject, \Combodo\iTop\Application\WebPage\WebPage $oPage, $bEditMode = false)
{
}
/**
* @inheritDoc
*/
public function OnDisplayRelations($oObject, \Combodo\iTop\Application\WebPage\WebPage $oPage, $bEditMode = false)
{
}
/**
* @inheritDoc
*/
public function OnFormSubmit($oObject, $sFormPrefix = '')
{
}
/**
* @inheritDoc
*/
public function OnFormCancel($sTempId)
{
}
/**
* @inheritDoc
*/
public function EnumUsedAttributes($oObject)
{
return array();
}
/**
* @inheritDoc
*/
public function GetIcon($oObject)
{
return '';
}
/**
* @inheritDoc
*/
public function GetHilightClass($oObject)
{
return HILIGHT_CLASS_NONE;
}
/**
* @inheritDoc
*/
public function EnumAllowedActions(DBObjectSet $oSet)
{
return array();
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Extend this class instead of iPageUIBlockExtension if you don't need to overload all methods
*
* @api
* @package UIBlockExtensibilityAPI
* @since 3.0.0
*/
abstract class AbstractPageUIBlockExtension implements iPageUIBlockExtension
{
/**
* @inheritDoc
*/
public function GetBannerBlock()
{
return null;
}
/**
* @inheritDoc
*/
public function GetHeaderBlock()
{
return null;
}
/**
* @inheritDoc
*/
public function GetFooterBlock()
{
return null;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* Extend this class instead of implementing iPreferencesExtension if you don't need to overload all methods
*
* @api
* @package PreferencesExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractPreferencesExtension implements iPreferencesExtension
{
/**
* @inheritDoc
*/
public function DisplayPreferences(\Combodo\iTop\Application\WebPage\WebPage $oPage)
{
// Do nothing
}
/**
* @inheritDoc
*/
public function ApplyPreferences(\Combodo\iTop\Application\WebPage\WebPage $oPage, $sOperation)
{
// Do nothing
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Inherit from this class to provide messages to be displayed in the "Welcome Popup"
*
* @api
* @since 3.2.0
*/
abstract class AbstractWelcomePopupExtension implements iWelcomePopupExtension
{
/**
* @inheritDoc
*/
public function GetIconRelPath(): string
{
return \Combodo\iTop\Application\Branding::$aLogoPaths[\Combodo\iTop\Application\Branding::ENUM_LOGO_TYPE_MAIN_LOGO_COMPACT]['default'];
}
/**
* @inheritDoc
*/
public function GetMessages(): array
{
return [];
}
/**
* @inheritDoc
*/
public function AcknowledgeMessage(string $sMessageId): void
{
// No need to process the acknowledgment notice by default
return;
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Base class for the various types of custom menus
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
abstract class ApplicationPopupMenuItem
{
/** @ignore */
protected $sUID;
/** @ignore */
protected $sLabel;
/** @ignore */
protected $sTooltip;
/** @ignore */
protected $sIconClass;
/** @ignore */
protected $aCssClasses;
/**
* Constructor
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @api
*/
public function __construct($sUID, $sLabel)
{
$this->sUID = $sUID;
$this->sLabel = $sLabel;
$this->sTooltip = '';
$this->sIconClass = '';
$this->aCssClasses = array();
}
/**
* Get the UID
*
* @return string The unique identifier
* @ignore
*/
public function GetUID()
{
return $this->sUID;
}
/**
* Get the label
*
* @return string The label
* @ignore
*/
public function GetLabel()
{
return $this->sLabel;
}
/**
* Get the CSS classes
*
* @return array
* @ignore
*/
public function GetCssClasses()
{
return $this->aCssClasses;
}
/**
* @param $aCssClasses
* @api
*/
public function SetCssClasses($aCssClasses)
{
$this->aCssClasses = $aCssClasses;
}
/**
* Adds a CSS class to the CSS classes that will be put on the menu item
*
* @param $sCssClass
* @api
*/
public function AddCssClass($sCssClass)
{
$this->aCssClasses[] = $sCssClass;
}
/**
* @param $sTooltip
*
* @api
* @since 3.0.0
*/
public function SetTooltip($sTooltip)
{
$this->sTooltip = $sTooltip;
}
/**
* @return string
*
* @api
* @since 3.0.0
*/
public function GetTooltip()
{
return $this->sTooltip;
}
/**
* @param $sIconClass
*
* @api
* @since 3.0.0
*/
public function SetIconClass($sIconClass)
{
$this->sIconClass = $sIconClass;
}
/**
* @return string
*
* @api
* @since 3.0.0
*/
public function GetIconClass()
{
return $this->sIconClass;
}
/**
* Returns the components to create a popup menu item in HTML
*
* @return array A hash array: array('label' => , 'url' => , 'target' => , 'onclick' => )
* @ignore
*/
abstract public function GetMenuItem();
/** @ignore */
public function GetLinkedScripts()
{
return array();
}
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Class for adding an item as a button that runs some JS code
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
class JSButtonItem extends JSPopupMenuItem
{
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Class for adding an item into a popup menu that triggers some Javascript code
*
* Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
class JSPopupMenuItem extends ApplicationPopupMenuItem
{
/** @ignore */
protected $sJsCode;
/** @ignore */
protected $sUrl;
/** @ignore */
protected $aIncludeJSFiles;
/**
* Class for adding an item that triggers some Javascript code
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sJSCode In case the menu consists in executing some havascript code inside the page, pass it here. If supplied $sURL
* ans $sTarget will be ignored
* @param array $aIncludeJSFiles An array of file URLs to be included (once) to provide some JS libraries for the page.
* @api
*/
public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array())
{
parent::__construct($sUID, $sLabel);
$this->sJsCode = $sJSCode;
$this->sUrl = '#';
$this->aIncludeJSFiles = $aIncludeJSFiles;
}
/** @ignore */
public function GetMenuItem()
{
// Note: the semicolumn is a must here!
return array(
'label' => $this->GetLabel(),
'onclick' => $this->GetJsCode() . '; return false;',
'url' => $this->GetUrl(),
'css_classes' => $this->GetCssClasses(),
'icon_class' => $this->sIconClass,
'tooltip' => $this->sTooltip
);
}
/** @ignore */
public function GetLinkedScripts()
{
return $this->aIncludeJSFiles;
}
/** @ignore */
public function GetJsCode()
{
return $this->sJsCode;
}
/** @ignore */
public function GetUrl()
{
return $this->sUrl;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Class for adding a separator (horizontal line, not selectable) the output
* will automatically reduce several consecutive separators to just one
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
{
static $idx = 0;
/**
* Constructor
* @api
*/
public function __construct()
{
parent::__construct('_separator_' . (self::$idx++), '');
}
/** @ignore */
public function GetMenuItem()
{
return array('label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses);
}
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Class for adding an item as a button that browses to the given URL
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
class URLButtonItem extends URLPopupMenuItem
{
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* Class for adding an item into a popup menu that browses to the given URL
*
* Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
class URLPopupMenuItem extends ApplicationPopupMenuItem
{
/** @ignore */
protected $sUrl;
/** @ignore */
protected $sTarget;
/**
* Constructor
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sUrl If the menu is an hyperlink, provide the absolute hyperlink here
* @param string $sTarget In case the menu is an hyperlink and a specific target is needed (_blank for example), pass it here
* @api
*/
public function __construct($sUID, $sLabel, $sUrl, $sTarget = '_top')
{
parent::__construct($sUID, $sLabel);
$this->sUrl = $sUrl;
$this->sTarget = $sTarget;
}
/** @ignore */
public function GetMenuItem()
{
return array('label' => $this->GetLabel(),
'url' => $this->GetUrl(),
'target' => $this->GetTarget(),
'css_classes' => $this->aCssClasses,
'icon_class' => $this->sIconClass,
'tooltip' => $this->sTooltip
);
}
/** @ignore */
public function GetUrl()
{
return $this->sUrl;
}
/** @ignore */
public function GetTarget()
{
return $this->sTarget;
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* Implement this interface to change the behavior of the GUI for some objects.
*
* All methods are invoked by iTop for a given object. There are basically two usages:
*
* 1) To tweak the form of an object, you will have to implement a specific behavior within:
*
* * OnDisplayProperties (bEditMode = true)
* * OnFormSubmit
* * OnFormCancel
*
* 2) To tune the display of the object details, you can use:
*
* * OnDisplayProperties
* * OnDisplayRelations
* * GetIcon
* * GetHilightClass
*
* Please note that some of the APIs can be called several times for a single page displayed.
* Therefore it is not recommended to perform too many operations, such as querying the database.
* A recommended pattern is to cache data by the mean of static members.
*
* @api
* @package UIExtensibilityAPI
*/
interface iApplicationUIExtension
{
/**
* Invoked when an object is being displayed (wiew or edit)
*
* The method is called right after the main tab has been displayed.
* You can add output to the page, either to change the display, or to add a form input
*
* Example:
* <code>
* if ($bEditMode)
* {
* $oPage->p('Age of the captain: &lt;input type="text" name="captain_age"/&gt;');
* }
* else
* {
* $oPage->p('Age of the captain: '.$iCaptainAge);
* }
* </code>
*
* @api
*
*@param \Combodo\iTop\Application\WebPage\WebPage $oPage The output context
* @param boolean $bEditMode True if the edition form is being displayed
*
* @param DBObject $oObject The object being displayed
*
* @return void
*/
public function OnDisplayProperties($oObject, \Combodo\iTop\Application\WebPage\WebPage $oPage, $bEditMode = false);
/**
* Invoked when an object is being displayed (wiew or edit)
*
* The method is called rigth after all the tabs have been displayed
*
* @api
*
*@param \Combodo\iTop\Application\WebPage\WebPage $oPage The output context
* @param boolean $bEditMode True if the edition form is being displayed
*
* @param DBObject $oObject The object being displayed
*
* @return void
*/
public function OnDisplayRelations($oObject, \Combodo\iTop\Application\WebPage\WebPage $oPage, $bEditMode = false);
/**
* Invoked when the end-user clicks on Modify from the object edition form
*
* The method is called after the changes from the standard form have been
* taken into account, and before saving the changes into the database.
*
* @param DBObject $oObject The object being edited
* @param string $sFormPrefix Prefix given to the HTML form inputs
*
* @return void
* @api
*/
public function OnFormSubmit($oObject, $sFormPrefix = '');
/**
* Invoked when the end-user clicks on Cancel from the object edition form
*
* Implement here any cleanup. This is necessary when you have injected some
* javascript into the edition form, and if that code requires to store temporary data
* (this is the case when a file must be uploaded).
*
* @param string $sTempId Unique temporary identifier made of session_id and transaction_id. It identifies the object in a unique way.
*
* @return void
* @api
*/
public function OnFormCancel($sTempId);
/**
* Not yet called by the framework!
*
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
*
* @param DBObject $oObject The object being displayed
*
* @return string[] desc
* @api
*/
public function EnumUsedAttributes($oObject); // Not yet implemented
/**
* Not yet called by the framework!
*
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
*
* @param DBObject $oObject The object being displayed
*
* @return string Path of the icon, relative to the modules directory.
* @api
*/
public function GetIcon($oObject); // Not yet implemented
/**
* Invoked when the object is displayed alone or within a list
*
* Returns a value influencing the appearance of the object depending on its
* state.
*
* Possible values are:
*
* * HILIGHT_CLASS_CRITICAL
* * HILIGHT_CLASS_WARNING
* * HILIGHT_CLASS_OK
* * HILIGHT_CLASS_NONE
*
* @param DBObject $oObject The object being displayed
*
* @return integer The value representing the mood of the object
* @api
*/
public function GetHilightClass($oObject);
/**
* Called when building the Actions menu for a single object or a list of objects
*
* Use this to add items to the Actions menu. You will have to specify a label and an URL.
*
* Example:
* <code>
* $oObject = $oSet->fetch();
* if ($oObject instanceof Sheep)
* {
* return array('View in my app' => 'http://myserver/view_sheeps?id='.$oObject->Get('name'));
* }
* else
* {
* return array();
* }
* </code>
*
* See also iPopupMenuExtension for greater flexibility
*
* @param DBObjectSet $oSet A set of persistent objects (DBObject)
*
* @return array
* @api
*/
public function EnumAllowedActions(DBObjectSet $oSet);
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add Dict entries
*
* @see \iTopWebPage::$a_dict_entries
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeDictEntriesExtension
{
/**
* @return array
* @see \iTopWebPage::a_dict_entries
* @api
*/
public function GetDictEntries(): array;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add Dict entries prefixes
*
* @see \iTopWebPage::$a_dict_entries_prefixes
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeDictEntriesPrefixesExtension
{
/**
* @return array
* @see \iTopWebPage::a_dict_entries_prefixes
* @api
*/
public function GetDictEntriesPrefixes(): array;
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Implement this interface to add inline script (JS) to the backoffice pages' head.
* Will be executed first, BEFORE the DOM interpretation.
*
* @see \iTopWebPage::$a_early_scripts
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeEarlyScriptExtension
{
/**
* @return string
* @see \iTopWebPage::$a_early_scripts
* @api
*/
public function GetEarlyScript(): string;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add inline script (JS) to the backoffice pages that will be executed right when the DOM is ready.
*
* @see \iTopWebPage::$a_init_scripts
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeInitScriptExtension
{
/**
* @return string
* @see \iTopWebPage::$a_init_scripts
* @api
*/
public function GetInitScript(): string;
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Implement this interface to add script (JS) files to the backoffice pages
*
* @see \iTopWebPage::$a_linked_scripts
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeLinkedScriptsExtension
{
/**
* Each script will be included using this property
* @return array An array of absolute URLs to the files to include
* @see \iTopWebPage::$a_linked_scripts
* @api
*/
public function GetLinkedScriptsAbsUrls(): array;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add stylesheets (CSS) to the backoffice pages
*
* @see \iTopWebPage::$a_linked_stylesheets
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeLinkedStylesheetsExtension
{
/**
* @return array An array of absolute URLs to the files to include
* @see \iTopWebPage::$a_linked_stylesheets
* @api
*/
public function GetLinkedStylesheetsAbsUrls(): array;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add inline script (JS) to the backoffice pages that will be executed slightly AFTER the DOM is ready (just after the init. scripts).
*
* @see \iTopWebPage::$a_ready_scripts
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeReadyScriptExtension
{
/**
* @return string
* @see \iTopWebPage::$a_ready_scripts
* @api
*/
public function GetReadyScript(): string;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add sass file (SCSS) to the backoffice pages.
* example: return "css/setup.scss"
*
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.3.0
*/
interface iBackofficeSassExtension
{
/**
* @return string
* @see \iTopWebPage::$a_styles
* @api
*/
public function GetSass(): string;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add inline script (JS) to the backoffice pages that will be executed immediately, without waiting for the DOM to be ready.
*
* @see \iTopWebPage::$a_scripts
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeScriptExtension
{
/**
* @return string
* @see \iTopWebPage::$a_scripts
* @api
*/
public function GetScript(): string;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Implement this interface to add inline style (CSS) to the backoffice pages' head.
*
* @see \iTopWebPage::$a_styles
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iBackofficeStyleExtension
{
/**
* @return string
* @see \iTopWebPage::$a_styles
* @api
*/
public function GetStyle(): string;
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Implement this interface to register a new field renderer mapping to either:
* - Add the rendering of a new attribute type
* - Overload the default rendering of an attribute type
*
* @since 3.1.0 N°6041
*
* @experimental Form / Field / Renderer should be used in more places in next iTop releases, which may introduce major API changes
*/
interface iFieldRendererMappingsExtension
{
/**
* @return array {
* array: {
* field: string,
* form_renderer: string,
* field_renderer: string
* }
* } List of field renderer mapping: FQCN field class, FQCN Form Renderer class, FQCN Field Renderer class
*
* Example:
*
* ```php
* [
* ['field' => 'FQCN\FieldA', 'form_renderer' => 'Combodo\iTop\Renderer\Console\ConsoleFormRenderer', 'field_renderer' => 'FQCN\FieldRendererA'],
* ['field' => 'FQCN\FieldB', 'form_renderer' => 'Combodo\iTop\Renderer\Console\ConsoleFormRenderer', 'field_renderer' => 'FQCN\FieldRendererB'],
* ['field' => 'FQCN\FieldA', 'form_renderer' => 'Combodo\iTop\Renderer\Bootstrap\BsFormRenderer', 'field_renderer' => 'FQCN\FieldRendererA'],
* ['field' => 'FQCN\FieldB', 'form_renderer' => 'Combodo\iTop\Renderer\Bootstrap\BsFormRenderer', 'field_renderer' => 'FQCN\FieldRendererB'],
* ]
* ```
*/
public static function RegisterSupportedFields(): array;
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Implement this interface to add content to any iTopWebPage
*
* There are 3 places where content can be added:
*
* * The north pane: (normaly empty/hidden) at the top of the page, spanning the whole
* width of the page
* * The south pane: (normaly empty/hidden) at the bottom of the page, spanning the whole
* width of the page
* * The admin banner (two tones gray background) at the left of the global search.
* Limited space, use it for short messages
*
* Each of the methods of this interface is supposed to return the HTML to be inserted at
* the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions
*
* @api
* @package BackofficeUIExtensibilityAPI
* @since 3.0.0
*/
interface iPageUIBlockExtension
{
/**
* Add content to the "admin banner"
*
* @api
* @return \Combodo\iTop\Application\UI\Base\iUIBlock|null The Block to add into the page
*/
public function GetBannerBlock();
/**
* Add content to the header of the page
*
* @api
* @return \Combodo\iTop\Application\UI\Base\iUIBlock|null The Block to add into the page
*/
public function GetHeaderBlock();
/**
* Add content to the footer of the page
*
* @api
* @return \Combodo\iTop\Application\UI\Base\iUIBlock|null The Block to add into the page
*/
public function GetFooterBlock();
}

View File

@@ -0,0 +1,112 @@
<?php
/**
* New extension to add menu items in the "popup" menus inside iTop. Provides a greater flexibility than
* iApplicationUIExtension::EnumAllowedActions.
*
* To add some menus into iTop, declare a class that implements this interface, it will be called automatically
* by the application, as long as the class definition is included somewhere in the code
*
* @api
* @package UIExtensibilityAPI
* @since 2.0
*/
interface iPopupMenuExtension
{
/**
* Insert an item into the Actions menu of a list
*
* $param is a DBObjectSet containing the list of objects
* @api
*/
const MENU_OBJLIST_ACTIONS = 1;
/**
* Insert an item into the Toolkit menu of a list
*
* $param is a DBObjectSet containing the list of objects
* @api
*/
const MENU_OBJLIST_TOOLKIT = 2;
/**
* Insert an item into the Actions menu on an object details page
*
* $param is a DBObject instance: the object currently displayed
* @api
*/
const MENU_OBJDETAILS_ACTIONS = 3;
/**
* Insert an item into the Dashboard menu
*
* The dashboad menu is shown on the top right corner when a dashboard
* is being displayed.
*
* $param is a Dashboard instance: the dashboard currently displayed
* @api
*/
const MENU_DASHBOARD_ACTIONS = 4;
/**
* Insert an item into the User menu (upper right corner)
*
* $param is null
* @api
*/
const MENU_USER_ACTIONS = 5;
/**
* Insert an item into the Action menu on an object item in an objects list in the portal
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on
* the current line)
* @api
*/
const PORTAL_OBJLISTITEM_ACTIONS = 7;
/**
* Insert an item into the Action menu on an object details page in the portal
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object
* currently displayed)
* @api
*/
const PORTAL_OBJDETAILS_ACTIONS = 8;
/**
* Insert an item into the Actions menu of a list in the portal
* Note: This is not implemented yet !
*
* $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects
*
* @todo
*/
const PORTAL_OBJLIST_ACTIONS = 6;
/**
* Insert an item into the user menu of the portal
* Note: This is not implemented yet !
*
* $param is the portal id
*
* @todo
*/
const PORTAL_USER_ACTIONS = 9;
/**
* Insert an item into the navigation menu of the portal
* Note: This is not implemented yet !
*
* $param is the portal id
*
* @todo
*/
const PORTAL_MENU_ACTIONS = 10;
/**
* Get the list of items to be added to a menu.
*
* This method is called by the framework for each menu.
* The items will be inserted in the menu in the order of the returned array.
*
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx
* @param mixed $param Depends on $iMenuId, see the constants defined above
*
* @return object[] An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu
* @api
*/
public static function EnumItems($iMenuId, $param);
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* @api
* @package PreferencesExtensibilityAPI
* @since 2.7.0
*/
interface iPreferencesExtension
{
/**
* @api
*
* @param \Combodo\iTop\Application\WebPage\WebPage $oPage
*
*/
public function DisplayPreferences(\Combodo\iTop\Application\WebPage\WebPage $oPage);
/**
* @api
*
* @param string $sOperation
*
* @param \Combodo\iTop\Application\WebPage\WebPage $oPage
*
* @return bool true if the operation has been used
*/
public function ApplyPreferences(\Combodo\iTop\Application\WebPage\WebPage $oPage, $sOperation);
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Interface to provide messages to be displayed in the "Welcome Popup"
*
* @api
* @since 3.2.0
*/
interface iWelcomePopupExtension
{
// Importance for ordering messages
// Just two levels since less important messages have nothing to do in the welcome popup
public const ENUM_IMPORTANCE_CRITICAL = 0;
public const ENUM_IMPORTANCE_HIGH = 1;
public const DEFAULT_IMPORTANCE = self::ENUM_IMPORTANCE_HIGH;
/**
* Overload this method if you need to display an icon representing the provider (eg. your own company logo, module icon, ...)
*
* @return string Relative path (from app. root) of the icon representing the provider
* @api
*/
public function GetIconRelPath(): string;
/**
* @return \Combodo\iTop\Application\WelcomePopup\Message[]
* @api
*/
public function GetMessages(): array;
/**
* Overload this method if the provider needs to do some additional processing after the message ($sMessageId) has been acknowledged by the current user
*
* @param string $sMessageId
* @api
*/
public function AcknowledgeMessage(string $sMessageId): void;
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Implement this interface to add files to the backup
*
* @api
* @since 3.2.0
*/
interface iBackupExtraFilesExtension
{
/**
* @return string[] Array of relative paths (from app root) for files and directories to be included in the backup
* @api
*/
public function GetExtraFilesRelPaths(): array;
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* KPI logging extensibility point
*
* KPI Logger extension
*/
interface iKPILoggerExtension
{
/**
* Init the statistics collected
*
* @return void
*/
public function InitStats();
/**
* Add a new KPI to the stats
*
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
*
* @return mixed
*/
public function LogOperation($oKpiLogData);
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Helpers for modules extensibility, with discover performed by the MetaModel.
*
*
* @api
* @package Extensibility
*/
interface iModuleExtension
{
/**
* @api
*/
public function __construct();
}

View File

@@ -0,0 +1,159 @@
<?php
/**
* Login finite state machine
*
* Execute the action corresponding to the current login state.
*
* * If a page is displayed, the action must exit at this point
* * if LoginWebPage::LOGIN_FSM_RETURN_ERROR is returned $iErrorCode must be set
* * if LoginWebPage::LOGIN_FSM_RETURN_OK is returned then the login is OK and terminated
* * if LoginWebPage::LOGIN_FSM_CONTINUE is returned then the FSM will proceed to next plugin or to next state
*
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
{
/**
* @inheritDoc
*/
abstract public function ListSupportedLoginModes();
/**
* @inheritDoc
*/
public function LoginAction($sLoginState, &$iErrorCode)
{
switch ($sLoginState) {
case LoginWebPage::LOGIN_STATE_START:
return $this->OnStart($iErrorCode);
case LoginWebPage::LOGIN_STATE_MODE_DETECTION:
return $this->OnModeDetection($iErrorCode);
case LoginWebPage::LOGIN_STATE_READ_CREDENTIALS:
return $this->OnReadCredentials($iErrorCode);
case LoginWebPage::LOGIN_STATE_CHECK_CREDENTIALS:
return $this->OnCheckCredentials($iErrorCode);
case LoginWebPage::LOGIN_STATE_CREDENTIALS_OK:
return $this->OnCredentialsOK($iErrorCode);
case LoginWebPage::LOGIN_STATE_USER_OK:
return $this->OnUsersOK($iErrorCode);
case LoginWebPage::LOGIN_STATE_CONNECTED:
return $this->OnConnected($iErrorCode);
case LoginWebPage::LOGIN_STATE_ERROR:
return $this->OnError($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* Initialization
*
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnStart(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* Detect login mode explicitly without respecting configured order (legacy mode)
* In most case do nothing here
*
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnModeDetection(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* Obtain the credentials either if login mode is empty or set to yours.
* This step can be called multiple times by the FSM:
* for example:
* 1 - display login form
* 2 - read the values posted by the user (store that in session)
*
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnReadCredentials(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* Control the validity of the data from the session
* Automatic user provisioning can be done here
*
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnCheckCredentials(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnCredentialsOK(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnUsersOK(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnConnected(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
protected function OnError(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
interface iLoginExtension
{
/**
* Return the list of supported login modes for this plugin
*
* @return array of supported login modes
* @api
*/
public function ListSupportedLoginModes();
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
interface iLoginFSMExtension extends iLoginExtension
{
/**
* Execute action for this login state
* If a page is displayed, the action must exit at this point
* if LoginWebPage::LOGIN_FSM_RETURN_ERROR is returned $iErrorCode must be set
* if LoginWebPage::LOGIN_FSM_RETURN_OK is returned then the login is OK and terminated
* if LoginWebPage::LOGIN_FSM_CONTINUE is returned then the FSM will proceed to next plugin or state
*
* @param string $sLoginState (see LoginWebPage::LOGIN_STATE_...)
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_CONTINUE
* @api
*/
public function LoginAction($sLoginState, &$iErrorCode);
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Login page extensibility
*
* @api
* @package UIExtensibilityAPI
* @since 2.7.0
*/
interface iLoginUIExtension extends iLoginExtension
{
/**
* @return LoginTwigContext
* @api
*/
public function GetTwigContext();
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* @api
* @package LoginExtensibilityAPI
* @since 2.7.0
*/
interface iLogoutExtension extends iLoginExtension
{
/**
* Execute all actions to log out properly
* @api
*/
public function LogoutAction();
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Extend this class instead of iPortalUIExtension if you don't need to overload all methods
*
* @api
* @package PortalExtensibilityAPI
* @since 2.4.0
*/
abstract class AbstractPortalUIExtension implements iPortalUIExtension
{
/**
* @inheritDoc
*/
public function GetCSSFiles(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return array();
}
/**
* @inheritDoc
*/
public function GetCSSInline(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return null;
}
/**
* @inheritDoc
*/
public function GetJSFiles(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return array();
}
/**
* @inheritDoc
*/
public function GetJSInline(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return null;
}
/**
* @inheritDoc
*/
public function GetBodyHTML(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return null;
}
/**
* @inheritDoc
*/
public function GetMainContentHTML(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return null;
}
/**
* @inheritDoc
*/
public function GetNavigationMenuHTML(\Symfony\Component\DependencyInjection\Container $oContainer)
{
return null;
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* Implement this interface to add content to any enhanced portal page
*
* @api
* @package PortalExtensibilityAPI
*
* @since 2.4.0 interface creation
* @since 2.7.0 change method signatures due to Silex to Symfony migration
*/
interface iPortalUIExtension
{
const ENUM_PORTAL_EXT_UI_BODY = 'Body';
const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu';
const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent';
/**
* Returns an array of CSS file urls
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return array
* @api
*/
public function GetCSSFiles(\Symfony\Component\DependencyInjection\Container $oContainer);
/**
* Returns inline (raw) CSS
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
* @api
*/
public function GetCSSInline(\Symfony\Component\DependencyInjection\Container $oContainer);
/**
* Returns an array of JS file urls
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return array
* @api
*/
public function GetJSFiles(\Symfony\Component\DependencyInjection\Container $oContainer);
/**
* Returns raw JS code
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
* @api
*/
public function GetJSInline(\Symfony\Component\DependencyInjection\Container $oContainer);
/**
* Returns raw HTML code to put at the end of the <body> tag
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
* @api
*/
public function GetBodyHTML(\Symfony\Component\DependencyInjection\Container $oContainer);
/**
* Returns raw HTML code to put at the end of the #main-wrapper element
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
* @api
*/
public function GetMainContentHTML(\Symfony\Component\DependencyInjection\Container $oContainer);
/**
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
*
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
* @api
*/
public function GetNavigationMenuHTML(\Symfony\Component\DependencyInjection\Container $oContainer);
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Minimal REST response structure. Derive this structure to add response data and error codes.
*
* @api
* @package RESTAPI
* @since 2.0.1
*/
class RestResult
{
/**
* Result: no issue has been encountered
* @api
*/
const OK = 0;
/**
* Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation
* @api
*/
const UNAUTHORIZED = 1;
/**
* Result: the parameter 'version' is missing
* @api
*/
const MISSING_VERSION = 2;
/**
* Result: the parameter 'json_data' is missing
* @api
*/
const MISSING_JSON = 3;
/**
* Result: the input structure is not a valid JSON string
* @api
*/
const INVALID_JSON = 4;
/**
* Result: the parameter 'auth_user' is missing, authentication aborted
* @api
*/
const MISSING_AUTH_USER = 5;
/**
* Result: the parameter 'auth_pwd' is missing, authentication aborted
* @api
*/
const MISSING_AUTH_PWD = 6;
/**
* Result: no operation is available for the specified version
* @api
*/
const UNSUPPORTED_VERSION = 10;
/**
* Result: the requested operation is not valid for the specified version
* @api
*/
const UNKNOWN_OPERATION = 11;
/**
* Result: the requested operation cannot be performed because it can cause data (integrity) loss
* @api
*/
const UNSAFE = 12;
/**
* Result: the request page number is not valid. It must be an integer greater than 0
* @api
*/
const INVALID_PAGE = 13;
/**
* Result: the operation could not be performed, see the message for troubleshooting
* @api
*/
const INTERNAL_ERROR = 100;
/**
* Default constructor - ok!
* @api
*/
public function __construct()
{
$this->code = RestResult::OK;
}
/**
* Result code
* @var int
* @api
*/
public $code;
/**
* Result message
* @var string
* @api
*/
public $message;
/**
* Sanitize the content of this result to hide sensitive information
*/
public function SanitizeContent()
{
// The default implementation does nothing
}
}

View File

@@ -0,0 +1,368 @@
<?php
/**
* Helpers for implementing REST services
*
* @api
* @package RESTAPI
*/
class RestUtils
{
/**
* Registering tracking information. Any further object modification be associated with the given comment, when the modification gets
* recorded into the DB
*
* @param StdClass $oData Structured input data. Must contain 'comment'.
*
* @return void
* @throws Exception
* @api
*
*/
public static function InitTrackingComment($oData)
{
$sComment = self::GetMandatoryParam($oData, 'comment');
CMDBObject::SetTrackInfo($sComment);
}
/**
* Read a mandatory parameter from from a Rest/Json structure.
*
* @param string $sParamName Name of the parameter to fetch from the input data
* @param StdClass $oData Structured input data. Must contain the entry defined by sParamName.
*
* @return mixed parameter value if present
* @throws Exception If the parameter is missing
* @api
*/
public static function GetMandatoryParam($oData, $sParamName)
{
if (isset($oData->$sParamName)) {
return $oData->$sParamName;
} else {
throw new Exception("Missing parameter '$sParamName'");
}
}
/**
* Read an optional parameter from a Rest/Json structure.
*
* @param string $sParamName Name of the parameter to fetch from the input data
* @param mixed $default Default value if the parameter is not found in the input data
*
* @param StdClass $oData Structured input data.
*
* @return mixed
* @throws Exception
* @api
*/
public static function GetOptionalParam($oData, $sParamName, $default)
{
if (isset($oData->$sParamName)) {
return $oData->$sParamName;
} else {
return $default;
}
}
/**
* Read a class from a Rest/Json structure.
*
* @param string $sParamName Name of the parameter to fetch from the input data
* @param StdClass $oData Structured input data. Must contain the entry defined by sParamName.
*
* @return string
* @throws Exception If the parameter is missing or the class is unknown
* @api
*/
public static function GetClass($oData, $sParamName)
{
$sClass = self::GetMandatoryParam($oData, $sParamName);
if (!MetaModel::IsValidClass($sClass)) {
throw new Exception("$sParamName: '$sClass' is not a valid class'");
}
return $sClass;
}
/**
* Read a list of attribute codes from a Rest/Json structure.
*
* @param StdClass $oData Structured input data.
* @param string $sParamName Name of the parameter to fetch from the input data
*
* @param string $sClass Name of the class
*
* @return array of class => list of attributes (see RestResultWithObjects::AddObject that uses it)
* @throws Exception
* @api
*/
public static function GetFieldList($sClass, $oData, $sParamName)
{
$sFields = self::GetOptionalParam($oData, $sParamName, '*');
$aShowFields = array();
if ($sFields == '*') {
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
$aShowFields[$sClass][] = $sAttCode;
}
} elseif ($sFields == '*+') {
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass) {
foreach (MetaModel::ListAttributeDefs($sRefClass) as $sAttCode => $oAttDef) {
$aShowFields[$sRefClass][] = $sAttCode;
}
}
} else {
foreach (explode(',', $sFields) as $sAttCode) {
$sAttCode = trim($sAttCode);
if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode($sClass, $sAttCode))) {
throw new Exception("$sParamName: invalid attribute code '$sAttCode'");
}
$aShowFields[$sClass][] = $sAttCode;
}
}
return $aShowFields;
}
/**
* Read and interpret object search criteria from a Rest/Json structure
*
* @param string $sClass Name of the class
* @param StdClass $oCriteria Hash of attribute code => value (can be a substructure or a scalar, depending on the nature of the
* attriute)
*
* @return object The object found
* @throws Exception If the input structure is not valid or it could not find exactly one object
* @api
*/
protected static function FindObjectFromCriteria($sClass, $oCriteria)
{
$aCriteriaReport = array();
if (isset($oCriteria->finalclass)) {
if (!MetaModel::IsValidClass($oCriteria->finalclass)) {
throw new Exception("finalclass: Unknown class '" . $oCriteria->finalclass . "'");
}
if (!MetaModel::IsParentClass($sClass, $oCriteria->finalclass)) {
throw new Exception("finalclass: '" . $oCriteria->finalclass . "' is not a child class of '$sClass'");
}
$sClass = $oCriteria->finalclass;
}
$oSearch = new DBObjectSearch($sClass);
foreach ($oCriteria as $sAttCode => $value) {
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
if (is_object($value) || is_array($value)) {
$value = json_encode($value);
}
$aCriteriaReport[] = "$sAttCode: $value ($realValue)";
}
$oSet = new DBObjectSet($oSearch);
$iCount = $oSet->Count();
if ($iCount == 0) {
throw new Exception("No item found with criteria: " . implode(', ', $aCriteriaReport));
} elseif ($iCount > 1) {
throw new Exception("Several items found ($iCount) with criteria: " . implode(', ', $aCriteriaReport));
}
$res = $oSet->Fetch();
return $res;
}
/**
* Find an object from a polymorph search specification (Rest/Json)
*
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
* @param bool $bAllowNullValue Allow the cases such as key = 0 or key = {null} and return null then
* @param string $sClass Name of the class
*
* @return DBObject The object found
* @throws Exception If the input structure is not valid or it could not find exactly one object
*
* @api
* @see DBObject::CheckChangedExtKeysValues() generic method to check that we can access the linked object isn't used in that use case because values can be literal, OQL, friendlyname
*/
public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false)
{
if (is_object($key)) {
$res = static::FindObjectFromCriteria($sClass, $key);
} elseif (is_numeric($key)) {
if ($bAllowNullValue && ($key == 0)) {
$res = null;
} else {
$res = MetaModel::GetObject($sClass, $key, false);
if (is_null($res)) {
throw new Exception("Invalid object $sClass::$key");
}
}
} elseif (is_string($key)) {
// OQL
$oSearch = DBObjectSearch::FromOQL($key);
$oSet = new DBObjectSet($oSearch);
$iCount = $oSet->Count();
if ($iCount == 0) {
throw new Exception("No item found for query: $key");
} elseif ($iCount > 1) {
throw new Exception("Several items found ($iCount) for query: $key");
}
$res = $oSet->Fetch();
} else {
throw new Exception("Wrong format for key");
}
return $res;
}
/**
* Search objects from a polymorph search specification (Rest/Json)
*
* @param string $sClass Name of the class
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
* @param int $iLimit The limit of results to return
* @param int $iOffset The offset of results to return
*
* @return DBObjectSet The search result set
* @throws Exception If the input structure is not valid
* @api
*/
public static function GetObjectSetFromKey($sClass, $key, $iLimit = 0, $iOffset = 0)
{
if (is_object($key)) {
if (isset($key->finalclass)) {
$sClass = $key->finalclass;
if (!MetaModel::IsValidClass($sClass)) {
throw new Exception("finalclass: Unknown class '$sClass'");
}
}
$oSearch = new DBObjectSearch($sClass);
foreach ($key as $sAttCode => $value) {
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
}
} elseif (is_numeric($key)) {
$oSearch = new DBObjectSearch($sClass);
$oSearch->AddCondition('id', $key);
} elseif (is_string($key)) {
// OQL
try {
$oSearch = DBObjectSearch::FromOQL($key);
} catch (Exception $e) {
throw new CoreOqlException('Query failed to execute', [
'query' => $key,
'exception_class' => get_class($e),
'exception_message' => $e->getMessage(),
]);
}
} else {
throw new Exception("Wrong format for key");
}
$oObjectSet = new DBObjectSet($oSearch, array(), array(), null, $iLimit, $iOffset);
return $oObjectSet;
}
/**
* Interpret the Rest/Json value and get a valid attribute value
*
* @param string $sAttCode Attribute code
* @param mixed $value Depending on the type of attribute (a scalar, or search criteria, or list of related objects...)
* @param string $sClass Name of the class
*
* @return mixed The value that can be used with DBObject::Set()
* @throws Exception If the specification of the value is not valid.
* @api
*/
public static function MakeValue($sClass, $sAttCode, $value)
{
try {
if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) {
throw new Exception("Unknown attribute");
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeExternalKey) {
$oExtKeyObject = static::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
$value = ($oExtKeyObject != null) ? $oExtKeyObject->GetKey() : 0;
} elseif ($oAttDef instanceof AttributeLinkedSet) {
if (!is_array($value)) {
throw new Exception("A link set must be defined by an array of objects");
}
$sLnkClass = $oAttDef->GetLinkedClass();
$aLinks = array();
foreach ($value as $oValues) {
$oLnk = static::MakeObjectFromFields($sLnkClass, $oValues);
// Fix for N°1939
if (($oAttDef instanceof AttributeLinkedSetIndirect) && ($oLnk->Get($oAttDef->GetExtKeyToRemote()) == 0)) {
continue;
}
$aLinks[] = $oLnk;
}
$value = DBObjectSet::FromArray($sLnkClass, $aLinks);
} elseif ($oAttDef instanceof AttributeTagSet) {
if (!is_array($value)) {
throw new Exception("A tag set must be defined by an array of tag codes");
}
$value = $oAttDef->FromJSONToValue($value);
} else {
$value = $oAttDef->FromJSONToValue($value);
}
} catch (Exception $e) {
throw new Exception("$sAttCode: " . $e->getMessage(), $e->getCode());
}
return $value;
}
/**
* Interpret a Rest/Json structure that defines attribute values, and build an object
*
* @param array $aFields A hash of attribute code => value specification.
* @param string $sClass Name of the class
*
* @return DBObject The newly created object
* @throws Exception If the specification of the values is not valid
* @api
*/
public static function MakeObjectFromFields($sClass, $aFields)
{
$oObject = MetaModel::NewObject($sClass);
foreach ($aFields as $sAttCode => $value) {
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try {
$oObject->Set($sAttCode, $realValue);
} catch (Exception $e) {
throw new Exception("$sAttCode: " . $e->getMessage(), $e->getCode());
}
}
return $oObject;
}
/**
* Interpret a Rest/Json structure that defines attribute values, and update the given object
*
* @param array $aFields A hash of attribute code => value specification.
* @param DBObject $oObject The object being modified
*
* @return DBObject The object modified
* @throws Exception If the specification of the values is not valid
* @api
*/
public static function UpdateObjectFromFields($oObject, $aFields)
{
$sClass = get_class($oObject);
foreach ($aFields as $sAttCode => $value) {
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try {
$oObject->Set($sAttCode, $realValue);
} catch (Exception $e) {
throw new Exception("$sAttCode: " . $e->getMessage(), $e->getCode());
}
}
return $oObject;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* A REST service provider implementing this interface will have its input JSON data sanitized for logging purposes
*
* @see \iRestServiceProvider
* @since 2.7.13, 3.2.1-1
*/
interface iRestInputSanitizer
{
public function SanitizeJsonInput(string $sJsonInput): string;
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Implement this interface to add new operations to the REST/JSON web service
*
* @api
* @package RESTExtensibilityAPI
* @since 2.0.1
*/
interface iRestServiceProvider
{
/**
* Enumerate services delivered by this class
*
* @param string $sVersion The version (e.g. 1.0) supported by the services
*
* @return array An array of hash 'verb' => verb, 'description' => description
* @api
*/
public function ListOperations($sVersion);
/**
* Enumerate services delivered by this class
*
* @param string $sVersion The version (e.g. 1.0) supported by the services
* @param string $sVerb
* @param array $aParams
*
* @return RestResult The standardized result structure (at least a message)
* @api
*/
public function ExecOperation($sVersion, $sVerb, $aParams);
}

View File

@@ -214,6 +214,15 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
*/
protected static bool $bBlockEventDBLinksChanged = false;
/**
* If set to true, the object is considered as modified, whatever the actual state is.
* This is used when an object is modified indirectly (eg. through a linked set)
*
* @var bool
*
* @since 3.3.0 N°8210 - Remove iApplicationObjectExtension
*/
private bool $bIsMarkedAsModified = false;
/**
* Constructor from a row of data (as a hash 'attcode' => value)
@@ -4542,21 +4551,6 @@ HTML;
return $res;
}
protected function PostInsertActions(): void
{
parent::PostInsertActions();
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
}
/**
* @inheritdoc
* Attaches InlineImages to the current object
@@ -4589,21 +4583,6 @@ HTML;
return $res;
}
protected function PostUpdateActions(array $aChanges): void
{
parent::PostUpdateActions($aChanges);
// Invoke extensions after the update (could be before)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
/**
* @param string $sMessageIdPrefix
*
@@ -4639,21 +4618,6 @@ HTML;
return $oDeletionPlan;
}
final protected function PreDeleteActions(): void
{
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
parent::PreDeleteActions();
}
final protected function PostDeleteActions(): void
{
parent::PostDeleteActions();
@@ -4666,25 +4630,20 @@ HTML;
return true;
}
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified()");
$oKPI = new ExecutionKPI();
$bIsModified = $oExtensionInstance->OnIsModified($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
if ($bIsModified) {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
return true;
} else {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> false");
}
}
return $this->bIsMarkedAsModified;
}
return false;
/**
* Override the default modification state of the object.
*
* The object is considered as modified, whatever the actual state is.
* This is used when an object is modified indirectly (eg. through a linked set)
*
* @return void
*/
public function MarkObjectAsModified(): void
{
$this->bIsMarkedAsModified = true;
}
/**
@@ -4698,7 +4657,7 @@ HTML;
}
/**
* Whether to bypass the checks of user rights when writing this object, could be used in {@link \iApplicationObjectExtension::OnCheckToWrite()}
* Whether to bypass the checks of user rights when writing this object
*
* @return bool
*/
@@ -4727,22 +4686,6 @@ HTML;
{
parent::DoCheckToWrite();
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToWrite()");
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
}
}
// User rights
//
if (!$this->bAllowWrite)
@@ -4779,22 +4722,6 @@ HTML;
{
parent::DoCheckToDelete($oDeletionPlan);
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToDelete()");
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
if (is_array($aNewIssues) && count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
}
}
// User rights
//
if (! $this->bAllowDelete)
@@ -5809,7 +5736,7 @@ JS
{
$this->NotifyAttachedObjectsOnLinkClassModification();
$this->RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey());
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges, 'stimulus_applied' => $sStimulusBeingApplied]);
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges, 'stimulus_applied' => $sStimulusBeingApplied, 'cmdb_change' => self::GetCurrentChange()]);
}
//////////////
@@ -5847,7 +5774,7 @@ JS
final protected function FireEventAfterDelete(): void
{
$this->NotifyAttachedObjectsOnLinkClassModification();
$this->FireEvent(EVENT_DB_AFTER_DELETE);
$this->FireEvent(EVENT_DB_AFTER_DELETE, ['cmdb_change' => self::GetCurrentChange()]);
}
/**

View File

@@ -18,7 +18,7 @@
"laminas/laminas-mail": "^2.11",
"laminas/laminas-servicemanager": "^3.5",
"league/oauth2-google": "^4.0.1",
"nikic/php-parser": "~5.6.0",
"nikic/php-parser": "dev-master",
"pear/archive_tar": "~1.4.14",
"pelago/emogrifier": "^7.2.0",
"psr/log": "^3.0.0",
@@ -41,6 +41,10 @@
"symfony/stopwatch": "~6.4.0",
"symfony/web-profiler-bundle": "~6.4.0"
},
"repositories": [{
"type": "vcs",
"url": "https://github.com/Combodo/PHP-Parser"
}],
"suggest": {
"ext-libsodium": "Required to use the AttributeEncryptedString.",
"ext-openssl": "Can be used as a polyfill if libsodium is not installed",

30
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a0021a80fcc9bccc3f577530893e2974",
"content-hash": "be4951ced82be6e0ac8c18fa6ddaafc9",
"packages": [
{
"name": "apereo/phpcas",
@@ -1020,16 +1020,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.6.0",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
"url": "https://github.com/Combodo/PHP-Parser.git",
"reference": "8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"url": "https://api.github.com/repos/Combodo/PHP-Parser/zipball/8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a",
"reference": "8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a",
"shasum": ""
},
"require": {
@@ -1042,13 +1042,14 @@
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^9.0"
},
"default-branch": true,
"bin": [
"bin/php-parse"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
"dev-master": "5.x-dev"
}
},
"autoload": {
@@ -1056,7 +1057,11 @@
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.org/downloads/",
"autoload-dev": {
"psr-4": {
"PhpParser\\": "test/PhpParser/"
}
},
"license": [
"BSD-3-Clause"
],
@@ -1071,10 +1076,9 @@
"php"
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
"source": "https://github.com/Combodo/PHP-Parser/tree/master"
},
"time": "2025-07-27T20:03:57+00:00"
"time": "2025-09-09T09:14:16+00:00"
},
{
"name": "paragonie/random_compat",
@@ -5243,7 +5247,9 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": {
"nikic/php-parser": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@@ -8161,7 +8161,7 @@ class AttributeURL extends AttributeString
* @since 3.0.3 moved from Config to AttributeURL constant
*/
public const DEFAULT_VALIDATION_PATTERN = /** @lang RegExp */
'(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?';
'(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?';
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)

View File

@@ -226,27 +226,23 @@ class CMDBSource
*/
public static function InitServerAndPort($sDbHost, &$sServer, &$iPort)
{
$aConnectInfo = explode(':', $sDbHost);
if ($sDbHost != null) {
$aConnectInfo = explode(':', $sDbHost);
$bUsePersistentConnection = false;
if (strcasecmp($aConnectInfo[0], 'p') === 0)
{
$bUsePersistentConnection = true;
$sServer = $aConnectInfo[0].':'.$aConnectInfo[1];
}
else
{
$sServer = $aConnectInfo[0];
}
$bUsePersistentConnection = false;
if (strcasecmp($aConnectInfo[0], 'p') === 0) {
$bUsePersistentConnection = true;
$sServer = $aConnectInfo[0].':'.$aConnectInfo[1];
} else {
$sServer = $aConnectInfo[0];
}
$iConnectInfoCount = count($aConnectInfo);
if ($bUsePersistentConnection && ($iConnectInfoCount == 3))
{
$iPort = (int)($aConnectInfo[2]);
}
else if (!$bUsePersistentConnection && ($iConnectInfoCount == 2))
{
$iPort = (int)($aConnectInfo[1]);
$iConnectInfoCount = count($aConnectInfo);
if ($bUsePersistentConnection && ($iConnectInfoCount == 3)) {
$iPort = (int)($aConnectInfo[2]);
} else if (!$bUsePersistentConnection && ($iConnectInfoCount == 2)) {
$iPort = (int)($aConnectInfo[1]);
}
}
}

View File

@@ -20,6 +20,9 @@
*/
use Combodo\iTop\Config\Validator\iTopConfigAstValidator;
use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator;
define('ITOP_APPLICATION', 'iTop');
define('ITOP_APPLICATION_SHORT', 'iTop');
@@ -1816,6 +1819,7 @@ class Config
return (array_key_exists($sPropCode, $this->m_aSettings));
}
/**
* @return string identifier that can be used for example to name WebStorage/SessionStorage keys (they
* are related to a whole domain, and a domain can host multiple itop)
@@ -2017,10 +2021,7 @@ class Config
$bLoadConfig = false;
}
$this->m_aAddons = array(
// Default AddOn, always present can be moved to an official iTop Module later if needed
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
);
$this->m_aAddons = [];
foreach ($this->m_aSettings as $sPropCode => $aSettingInfo)
{
@@ -2148,19 +2149,13 @@ class Config
array('file' => $sConfigFile, 'expected' => '$MySettings'));
}
if (!array_key_exists('addons', $MyModules))
{
throw new ConfigException('Missing item in configuration file',
array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
}
if (!array_key_exists('user rights', $MyModules['addons']))
if (!array_key_exists('addons', $MyModules) || !array_key_exists('user rights', $MyModules['addons']))
{
// Add one, by default
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
$MyModules['addons']['user rights'] = 'addons/userrights/userrightsprofile.class.inc.php';
$this->m_aAddons = $MyModules['addons'];
}
$this->m_aAddons = $MyModules['addons'];
foreach ($MySettings as $sPropCode => $rawvalue)
{
if ($this->IsProperty($sPropCode))
@@ -2264,11 +2259,21 @@ class Config
$this->m_aModuleSettings[$sModule][$sProperty] = $value;
}
/**
* @deprecated 3.3.0 N°8190
*/
public function GetAddons()
{
return $this->m_aAddons;
if (array_key_exists("user rights", $this->m_aAddons)) {
return $this->m_aAddons;
} else {
return array_merge($this->m_aAddons,['user rights' => 'addons/userrights/userrightsprofile.class.inc.php']);
}
}
/**
* @deprecated 3.3.0 N°8190
*/
public function SetAddons($aAddons)
{
$this->m_aAddons = $aAddons;
@@ -2702,15 +2707,6 @@ class Config
{
fwrite($hFile, "\t'addons' => {$aParserValue['value']},\n");
}
else
{
fwrite($hFile, "\t'addons' => array (\n");
foreach ($this->m_aAddons as $sKey => $sFile)
{
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
}
fwrite($hFile, "\t),\n");
}
fwrite($hFile, ");\n");
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
@@ -2856,20 +2852,8 @@ class Config
}
}
}
if (isset($aModuleInfo['installer']))
{
$sModuleInstallerClass = $aModuleInfo['installer'];
if (!class_exists($sModuleInstallerClass))
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']);
}
if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI'))
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']);
}
$aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig');
call_user_func_array($aCallSpec, array($this));
}
RunTimeEnvironment::CallInstallerHandler($aModuleInfo, "BeforeWritingConfig", [$this]);
}
}
$this->SetAddOns($aAddOns);
@@ -3037,4 +3021,6 @@ class ConfigPlaceholdersResolver
IssueLog::Error($sErrorMessage, self::class, array($sSourceName, $sKey, $sDefault, $sWholeMask));
throw new ConfigException($sErrorMessage);
}
}

View File

@@ -1748,9 +1748,9 @@ abstract class DBObject implements iDisplay
* @throws \CoreException
* @since 3.0.0 N°4106 Method should not be overloaded anymore for performances reasons. It will be set final in 3.1.0 (N°4107)
* @since 3.0.0 N°580 New $sType parameter
*
* @since 3.3.0 N°4107 Should never be overriden
*/
public function GetName($sType = FriendlyNameType::SHORT)
public final function GetName($sType = FriendlyNameType::SHORT)
{
return utils::EscapeHtml($this->GetRawName($sType));
}

View File

@@ -7027,7 +7027,7 @@ abstract class MetaModel
* @param array $aParams
* @param bool $bAllowAllData
*
* @return \DBObject
* @return \DBObject|null
* @throws \OQLException
*/
public static function GetObjectFromOQL($sQuery, $aParams = null, $bAllowAllData = false)
@@ -7709,7 +7709,6 @@ abstract class MetaModel
'iLoginUIExtension',
'iPreferencesExtension',
'iApplicationUIExtension',
'iApplicationObjectExtension',
'iPopupMenuExtension',
'iPageUIBlockExtension',
'iBackofficeLinkedScriptsExtension',

View File

@@ -39,6 +39,23 @@
//
// .site-nav a { color:#BADA55!important; }
$ibo-shame--switch--width: 36px !default;
$ibo-shame--switch--height: 20px !default;
$ibo-shame--slider--background-color: $ibo-color-secondary-600 !default;
$ibo-shame--slider--before--height: 15px !default;
$ibo-shame--slider--before--width: 15px !default;
$ibo-shame--slider--before--background-color: $ibo-color-secondary-300 !default;
$ibo-shame--slider--is-checked--background-color: $ibo-color-primary-600 !default;
$ibo-shame--slider--is-focus--box-shadow: 0 0 1px $ibo-color-primary-600 !default;
$ibo-shame--slider--is-round--border-radius: 20px !default;
$ibo-shame--slider--is-round--before--border-radius: 7px !default;
// N°2847 - Recolor svg illustrations with iTop's primary color
.ibo-svg-illustration--container > svg *[fill="#6c63ff"]{
fill: $ibo-svg-illustration--fill;
@@ -56,8 +73,8 @@
.switch {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
width: $ibo-shame--switch--width;
height: $ibo-shame--switch--height;
vertical-align: baseline;
}
@@ -74,27 +91,27 @@
left: 0;
right: 0;
bottom: 0;
background-color: $ibo-color-secondary-600;
background-color: $ibo-shame--slider--background-color;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 15px;
width: 15px;
height: $ibo-shame--slider--before--height;
width: $ibo-shame--slider--before--width;
left: 3px;
bottom: 3px;
background-color: $ibo-color-secondary-300;
background-color: $ibo-shame--slider--before--background-color;
transition: .4s;
}
input:checked + .slider {
background-color: $ibo-color-primary-600;
background-color: $ibo-shame--slider--is-checked--background-color;
}
input:focus + .slider {
box-shadow: 0 0 1px $ibo-color-primary-600;
box-shadow: $ibo-shame--slider--is-focus--box-shadow;
}
input:checked + .slider:before {
@@ -103,9 +120,9 @@ input:checked + .slider:before {
/* Rounded sliders */
.slider.round {
border-radius: 20px;
border-radius: $ibo-shame--slider--is-round--border-radius;
}
.slider.round:before {
border-radius: 7px;
border-radius: $ibo-shame--slider--is-round--before--border-radius;
}

View File

@@ -3,12 +3,14 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-bulk--bulk-modify--incompatible-attribute {
$ibo-bulk--bulk-modify--incompatible-attribute--color--margin-right: $ibo-vendors-selectize--item--icon--margin-right !default;
$ibo-bulk--bulk-modify--incompatible-attribute--color: $ibo-color-information-500 !default;
.ibo-bulk--bulk-modify--incompatible-attribute {
&:before{
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
margin-right: $ibo-bulk--bulk-modify--incompatible-attribute--color--margin-right;
@extend %fa-solid-base;
content: '\f05a';
color: $ibo-color-information-500;
color: $ibo-bulk--bulk-modify--incompatible-attribute--color;
}
}

View File

@@ -3,12 +3,16 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-linked-set--bulk-tooltip-info--content: '\f05a' !default;
$ibo-linked-set--bulk-tooltip-info--color: $ibo-color-information-500 !default;
$ibo-linked-set--bulk-tooltip-info--margin-right: $ibo-vendors-selectize--item--icon--margin-right !default;
.ibo-linked-set--bulk-tooltip-info {
font-size: $ibo-font-size-100;
@extend %ibo-font-ral-nor-100;
&:before{
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
margin-right: $ibo-linked-set--bulk-tooltip-info--margin-right;
@extend %fa-solid-base;
content: '\f05a';
color: $ibo-color-information-500;
content: $ibo-linked-set--bulk-tooltip-info--content;
color: $ibo-linked-set--bulk-tooltip-info--color;
}
}

View File

@@ -16,7 +16,7 @@
@import "panel/all";
@import "pill/all";
@import "dashlet/all";
@import "add-to-dashboard";
@import "prop-within-details";
@import "caselog-entry-form-within-activity-panel";
@import "tab-container-within-panel";
@import "medallion-with-blocklist";

View File

@@ -3,13 +3,17 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-medallion-with-blocklist--icon--image--margin-x: auto !default;
$ibo-medallion-with-blocklist--icon--image--margin-y: 0 !default;
$ibo-medallion-with-blocklist--icon--description--margin-top: $ibo-spacing-400 !default;
.ibo-block-list--medallion{
flex-direction: column;
align-items: center;
> .ibo-medallion-icon--image{
margin: 0 auto;
margin: $ibo-medallion-with-blocklist--icon--image--margin-y $ibo-medallion-with-blocklist--icon--image--margin-x;
~ .ibo-medallion-icon--description{
margin-top: 12px;
margin-top: $ibo-medallion-with-blocklist--icon--description--margin-top;
}
}
> .ibo-medallion-icon--description{

View File

@@ -9,8 +9,4 @@
.ibo-details .ibo-prop--apply {
display: table-column;
}
.ibo-details {
margin-top: 5px;
}

View File

@@ -15,6 +15,13 @@ $ibo-panel-with-tab-container--margin-bottom: -1 * $ibo-panel--body--padding-bot
$ibo-panel-with-tab-container--tab-toggler--font-size--is-sticking: $ibo-font-size-100 !default;
$ibo-tab-container-within-panel--tabs-list--padding-top: $ibo-spacing-800 !default;
// I'm not sure where these values come from, might need to be replaced by variables
$ibo-tab-container-within-panel--tabs-list--min-width: calc(32px + 90px + 32px) !default;
$ibo-tab-container-within-panel--tabs-header--height: $ibo-spacing-800 !default;
$ibo-tab-container-within-panel--tabs--list--is-sticking--z-index: 10 !default;
$ibo-tab-container-within-panel--tabs--list--is-sticking--is-not-vertical--padding-left: $ibo-spacing-0 !default;
// Note: We use the child ">" selector to ensure this applies only to the child tab container, not another one that would be nested
.ibo-panel {
> .ibo-panel--body {
@@ -35,15 +42,15 @@ $ibo-panel-with-tab-container--tab-toggler--font-size--is-sticking: $ibo-font-si
flex-direction: row;
> .ibo-tab-container--tabs-list {
padding-top: 50px;
padding-top: $ibo-tab-container-within-panel--tabs-list--padding-top;
flex-direction: column;
height: auto;
padding-left: unset;
margin-right: unset;
min-width: calc(32px + 90px + 32px);
min-width: $ibo-tab-container-within-panel--tabs-list--min-width;
> .ibo-tab-container--tab-header {
height: 50px;
height: $ibo-tab-container-within-panel--tabs-header--height;
width: 100%;
justify-content: left;
@@ -68,12 +75,12 @@ $ibo-panel-with-tab-container--tab-toggler--font-size--is-sticking: $ibo-font-si
> .ibo-tab-container {
> .ibo-tab-container--tabs-list.ibo-is-sticking {
position: fixed;
z-index: 10;
z-index: $ibo-tab-container-within-panel--tabs--list--is-sticking--z-index;
}
&:not(.ibo-is-vertical){
> .ibo-tab-container--tabs-list.ibo-is-sticking {
padding-left: 0;
padding-left: $ibo-tab-container-within-panel--tabs--list--is-sticking--is-not-vertical--padding-left;
.ibo-tab-container--tab-toggler,
.ibo-tab-container--extra-tabs-list-toggler {

View File

@@ -3,8 +3,11 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-datatable-within-panel--panel-body--width: 100% !default;
.ibo-panel .ibo-panel--body{
.ibo-datatable{
width: 100%
width: $ibo-datatable-within-panel--panel-body--width;
}
}

View File

@@ -3,6 +3,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-object-details-with-tab-container--tab-list--padding-left: calc(#{$ibo-object-details--icon--spacing--as-medallion} + #{$ibo-object-details--icon--size} + #{$ibo-object-details--icon--spacing--as-medallion} - #{$ibo-tab-container--tab-toggler--padding-x}) !default;
// Note: We use the child ">" selector to ensure this applies only the child tab container, not another one that would be nested
.ibo-object-details.ibo-has-medallion-icon {
> .ibo-panel--body {
@@ -10,7 +12,7 @@
> .ibo-tab-container:not(.ibo-is-vertical) {
> .ibo-tab-container--tabs-list {
// Align tab toggler's title with the panel's title
padding-left: calc(#{$ibo-object-details--icon--spacing--as-medallion} + #{$ibo-object-details--icon--size} + #{$ibo-object-details--icon--spacing--as-medallion} - #{$ibo-tab-container--tab-toggler--padding-x});
padding-left: $ibo-object-details-with-tab-container--tab-list--padding-left;
}
}
}

View File

@@ -9,6 +9,12 @@ $ibo-dashlet--width--is-inline: auto !default;
$ibo-dashlet--elements-spacing-x: $ibo-spacing-600 !default;
$ibo-dashlet--elements-spacing-y: $ibo-spacing-600 !default;
$ibo-dashlet-blocker--z-index: 9 !default; /* To be above calendar links & all, but below .close-box (9) */
$ibo-dashlet-blocker--top: 0 !default;
$ibo-dashlet-blocker--left: 0 !default;
$ibo-dashlet-blocker--width: 100% !default;
$ibo-dashlet-blocker--height: 100% !default;
/* Rules */
.ibo-dashlet {
position: relative;
@@ -23,18 +29,13 @@ $ibo-dashlet--elements-spacing-y: $ibo-spacing-600 !default;
.ibo-dashlet--is-inline {
width: $ibo-dashlet--width--is-inline;
}
.ibo-details > .ibo-prop--apply {
display: table-column;
}
.ibo-details{
margin-top: 5px;
}
.ibo-dashlet-blocker{
position: absolute;
z-index: 9; /* To be above calendar links & all, but below .close-box (9) */
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: $ibo-dashlet-blocker--z-index;
top: $ibo-dashlet-blocker--top;
left: $ibo-dashlet-blocker--left;
width: $ibo-dashlet-blocker--width;
height: $ibo-dashlet-blocker--height;
cursor: not-allowed;
}

View File

@@ -24,6 +24,8 @@ $ibo-datatable--sort-order--color: $ibo-color-primary-600 !default;
$ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
$ibo-datatable--selected-result-count--padding-right: 0.2em !default;
$ibo-datatable--selected-result-count--padding-left: 0.1em !default;
/* CSS variables (can be changed directly from the browser) */
:root {
@@ -134,8 +136,8 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
}
.ibo-datatable--selected-count, .ibo-datatable--result-count{
padding-right: 0.2em;
padding-left: 0.1em;
padding-right: $ibo-datatable--selected-result-count--padding-right;
padding-left: $ibo-datatable--selected-result-count--padding-left;
}
//

View File

@@ -3,7 +3,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-image--image-view--min-height: 96px !default;
$ibo-input-image--image-view--min-height: $ibo-size-500 !default;
$ibo-input-image--image-view--background-color: $ibo-color-grey-200 !default;
$ibo-input-image--image-view--border-radius: $ibo-border-radius-500 !default;

View File

@@ -1,5 +1,14 @@
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-richtext-placeholder--height: $ibo-size-600 !default;
$ibo-input-richtext-placeholder--width: 100% !default;
.ibo-input-richtext-placeholder{
height: 200px;
width: 100%;
height: $ibo-input-richtext-placeholder--height;
width: $ibo-input-richtext-placeholder--width;
visibility: hidden;
}

View File

@@ -3,6 +3,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-select-icon--menu--icon--max-height: 100% !default;
$ibo-input-select-icon--menu--icon--max-width: 100% !default;
$ibo-input-select-icon--icon--padding-right: $ibo-spacing-200 !default;
$ibo-input-select-icon--menu--z-index: 21 !default;
@@ -16,8 +18,8 @@ $ibo-input-select-icon--menu--icon--margin-right: 10px !default;
display: inline-flex;
text-align: left;
>img{
max-height: 100%;
max-width: 100%;
max-height: $ibo-input-select-icon--menu--icon--max-height;
max-width: $ibo-input-select-icon--menu--icon--max-width;
padding-right: $ibo-input-select-icon--icon--padding-right;
}
>span{

View File

@@ -20,6 +20,7 @@ $ibo-input-select-selectize--item--active--background-color: $ibo-color-blue-100
$ibo-input-select-wrapper--width: 100% !default;
$ibo-input-select-wrapper--after--content: "\f0d7" !default;
$ibo-input-select-wrapper--after--right: 8px !default;
$ibo-input-select-wrapper--after--height: 28px !default;
$ibo-input-select-wrapper--after--margin-left: -16px !default;
@@ -32,6 +33,7 @@ $ibo-input-select--action-button--height: 28px !default;
$ibo-input-select--action-button--width: 23px !default;
$ibo-input-select--action-button--margin-top: $ibo-spacing-0 !default;
$ibo-input-select--action-button--margin-right: 3px !default;
$ibo-input-select--action-button--font-size: $ibo-font-size-100 !default;
$ibo-input-select--action-button--background-color: inherit !default;
$ibo-input-select--action-button--color: $ibo-color-grey-800 !default;
$ibo-input-select--action-button--padding-x: $ibo-spacing-100 !default;
@@ -57,7 +59,7 @@ $ibo-input-select--autocomplete-item-image--border: 1px solid $ibo-color-grey-60
min-width: $ibo-input-select-selectize--value--min-midth !important;
input {
border-width: 0px;
border-width: 0;
color: inherit;
border-color: $ibo-color-white-100;
padding-left: $ibo-input-select--padding-x;
@@ -121,7 +123,7 @@ $ibo-input-select--autocomplete-item-image--border: 1px solid $ibo-color-grey-60
.ibo-input-select-wrapper::after{
position: absolute;
z-index: 1;
content: "\f0d7";
content: $ibo-input-select-wrapper--after--content;
font-family: "Font Awesome 5 Free";
font-weight: 600;
@@ -145,7 +147,7 @@ $ibo-input-select--autocomplete-item-image--border: 1px solid $ibo-color-grey-60
.ibo-input-select-wrapper--with-buttons:not(.ibo-input-select-autocomplete-wrapper)::after {
position: absolute;
z-index: 1;
content: "\f0d7";
content: $ibo-input-select-wrapper--after--content;
font-family: "Font Awesome 5 Free";
font-weight: 600;
cursor: pointer;
@@ -168,7 +170,7 @@ $ibo-input-select--autocomplete-item-image--border: 1px solid $ibo-color-grey-60
margin-top: $ibo-input-select--action-button--margin-top;
margin-right: $ibo-input-select--action-button--margin-right;
font-size: $ibo-font-size-100;
font-size: $ibo-input-select--action-button--font-size;
background-color: $ibo-input-select--action-button--background-color;
color: $ibo-input-select--action-button--color;
padding: $ibo-input-select--action-button--padding-y $ibo-input-select--action-button--padding-x;

View File

@@ -8,6 +8,9 @@ $ibo-input-text--min-height: 12rem !default;
$ibo-input-text--padding-x: $ibo-spacing-400 !default;
$ibo-input-text--padding-y: 10px !default;
$ibo-input-text--export--width: 100% !default;
$ibo-input-text--export--min-height: 15em !default;
.ibo-input-text {
width: $ibo-input-text--width;
min-height: $ibo-input-text--min-height;
@@ -23,6 +26,6 @@ $ibo-input-text--padding-y: 10px !default;
}
.ibo-input-text--export {
width: 100%;
min-height: 15em;
width: $ibo-input-text--export--width;
min-height: $ibo-input-text--export--min-height;
}

View File

@@ -9,6 +9,8 @@ $ibo-toggler--wrapper--height: 20px !default;
$ibo-toggler--slider--border-radius: $ibo-border-radius-900 !default;
$ibo-toggler--slider--background-color: $ibo-color-secondary-600 !default;
$ibo-toggler--slider--before--left: 3px !default;
$ibo-toggler--slider--before--bottom: 3px !default;
$ibo-toggler--slider--before--height: 15px !default;
$ibo-toggler--slider--before--width: 15px !default;
$ibo-toggler--slider--before--border-radius: $ibo-border-radius-full !default;
@@ -46,8 +48,8 @@ $ibo-toggler--label--margin-left: 4px !default;
.ibo-toggler--slider:before {
content: "";
position: absolute;
left: 3px;
bottom: 3px;
left: $ibo-toggler--slider--before--left;
bottom: $ibo-toggler--slider--before--bottom;
height: $ibo-toggler--slider--before--height;
width: $ibo-toggler--slider--before--width;
border-radius: $ibo-toggler--slider--before--border-radius;

View File

@@ -27,6 +27,9 @@ $ibo-input--disabled--background-color: $ibo-color-grey-300 !default;
$ibo-input--placeholder--color: $ibo-color-grey-700 !default;
$ibo-input-wrapper--is-error--border-color: $ibo-color-red-600 !default;
$ibo-input-wrapper--vanilla--is-error--border: 0 !default;
$ibo-input-wrapper--vanilla--is-error--background-color: $ibo-color-transparent !default;
$ibo-field-validation: $ibo-color-red-700 !default;
$ibo-input--margin-x: $ibo-spacing-200 !default;
@@ -62,8 +65,8 @@ textarea.ibo-input{
border-color: $ibo-input-wrapper--is-error--border-color;
}
.ibo-input-vanilla input{
border: 0;
background-color: #11ffee00;
border: $ibo-input-wrapper--vanilla--is-error--border;
background-color: $ibo-input-wrapper--vanilla--is-error--background-color;
}
}
input.ibo-input-vanilla{

View File

@@ -10,6 +10,10 @@ $ibo-popover-menu--padding: $ibo-spacing-0 !default;
$ibo-popover-menu--toggler-visual-hint--margin-left: 0.5rem !default;
$ibo-popover-menu--section--height: 100% !default;
$ibo-popover-menu--section--margin-x: $ibo-spacing-0 !default;
$ibo-popover-menu--section--margin-y: $ibo-spacing-0 !default;
$ibo-popover-menu--section-border-radius: $ibo-popover-menu--border-radius !default;
.ibo-popover-menu {
@@ -35,8 +39,8 @@ $ibo-popover-menu--section-border-radius: $ibo-popover-menu--border-radius !defa
display: flex;
flex-direction: column;
align-self: flex-start;
margin: $ibo-spacing-0 $ibo-spacing-0;
width: 100%;
margin: $ibo-popover-menu--section--margin-y $ibo-popover-menu--section--margin-x;
width: $ibo-popover-menu--section--height;
white-space: nowrap;
overflow: hidden; /* To avoid first/last entries of the menu to have no border-radius on hover */

View File

@@ -6,6 +6,7 @@
@import "navigation-menu";
@import "top-bar";
@import "content";
@import "details";
@import "tab-container/tab-container";
@import "tab-container/tab";
@import "multi-column/multi-column";

View File

@@ -2,6 +2,9 @@
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-v-spacer--padding-top: 1em !default;
$ibo-side-content--background-color: $ibo-content-block--background-color !default;
$ibo-side-content--border-left: $ibo-content-block--border !default;
/* Note: We have to wrap it in the ID in order to overload its rules, otherwise, the ID takes over the simple CSS class... */
#ibo-center-container {
@@ -16,10 +19,10 @@
}
.ibo-v-spacer {
padding-top: 1em;
padding-top: $ibo-v-spacer--padding-top;
}
#ibo-side-content {
background-color: $ibo-content-block--background-color;
border-left: $ibo-content-block--border;
background-color: $ibo-side-content--background-color;
border-left: $ibo-side-content--border-left;
}

View File

@@ -0,0 +1,9 @@
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-details--margin-top: 5px !default;
.ibo-details {
margin-top: $ibo-details--margin-top;
}

View File

@@ -218,7 +218,8 @@ $ibo-navigation-menu--user-info--height--is-expanded: 100% !default;
.ibo-navigation-menu--body{
width: $ibo-navigation-menu--body--width-expanded;
.ibo-navigation-menu--toggler-bar{
/* Values are hardcoded to ease the animation and there's no point in overloading them */
.ibo-navigation-menu--toggler-bar{
&:nth-child(1){
top: 4px;
left: 7px;
@@ -412,6 +413,7 @@ $ibo-navigation-menu--user-info--height--is-expanded: 100% !default;
transition: all 0.2s linear;
background-color: $ibo-navigation-menu--body--text-color;
/* Values are hardcoded to ease the animation and there's no point in overloading them */
&:nth-child(1){
top: 0;
}

View File

@@ -18,6 +18,7 @@ $ibo-activity-entry--medallion--has-no-image--background-color: $ibo-color-blue-
$ibo-activity-entry--medallion--has-no-image--text-color: $ibo-color-white-100 !default;
$ibo-activity-entry--medallion--has-no-image--border: 1px solid $ibo-color-grey-200 !default;
$ibo-activity-entry--information--margin-to-side: $ibo-spacing-0 !default;
$ibo-activity-entry--information--margin-to-other-side: $ibo-activity-entry--medallion--diameter + $ibo-activity-entry--medallion--margin-with-information !default;
$ibo-activity-entry--main-information--padding-x: $ibo-spacing-500 !default;
@@ -88,7 +89,7 @@ $ibo-activity-panel--load-all-entries--is-hover--margin-left: ($ibo-activity-pan
margin-left: $ibo-activity-entry--medallion--margin-with-information;
}
.ibo-activity-entry--information{
margin-right: 0;
margin-right: $ibo-activity-entry--information--margin-to-side;
margin-left: $ibo-activity-entry--information--margin-to-other-side;
}
.ibo-activity-entry--main-information{
@@ -109,7 +110,7 @@ $ibo-activity-panel--load-all-entries--is-hover--margin-left: ($ibo-activity-pan
&:not(.ibo-is-current-user){
.ibo-activity-entry--information{
margin-right: $ibo-activity-entry--information--margin-to-other-side;
margin-left: 0;
margin-left: $ibo-activity-entry--information--margin-to-side;
}
/* Bubble tip on the left for last entry of the group */
&:last-child{

View File

@@ -104,6 +104,7 @@ $ibo-activity-panel--add-caselog-entry-button--icon--line-height: 33px !default;
$ibo-activity-panel--entry-forms-confirmation-explanation--spacing: $ibo-spacing-500 !default;
$ibo-activity-panel--entry-forms-confirmation-preference-input--spacing: 0.5rem !default;
$ibo-activity-panel--closed-cover--z-index: 2 !default;
$ibo-activity-panel--closed-cover--background-color: $ibo-activity-panel--header--background-color !default;
$ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
@@ -433,14 +434,12 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
.ibo-activity-panel--closed-cover {
display: none;
position: absolute;
z-index: 2; // Above the compose button and all
// padding-top: 64px;
z-index: $ibo-activity-panel--closed-cover--z-index; // Above the compose button and all
top: 0;
bottom: 0;
left: 0;
right: 0;
@extend %ibo-fully-centered-content;
// align-items: flex-start;
background-color: $ibo-activity-panel--closed-cover--background-color;
cursor: pointer;
}

View File

@@ -11,7 +11,7 @@ $ibo-caselog-entry-form--actions--margin-top: $ibo-spacing-300 !default;
$ibo-caselog-entry-form--actions--margin-bottom: $ibo-caselog-entry-form--actions--margin-top !default;
$ibo-caselog-entry-form--lock-indicator--margin-top: $ibo-caselog-entry-form--padding-bottom !default;
$ibo-caselog-entry-form--lock-icon--size: 32px !default;
$ibo-caselog-entry-form--lock-icon--size: $ibo-size-350 !default;
$ibo-caselog-entry-form--lock-icon--text-color: $ibo-color-grey-50 !default;
$ibo-caselog-entry-form--lock-icon--background-color: $ibo-color-grey-800 !default;
$ibo-caselog-entry-form--lock-icon--border-radius: $ibo-border-radius-full !default;

View File

@@ -25,6 +25,10 @@ $ibo-dashboard-editor--delete-dashlet-icon--right: 9px !default;
$ibo-dashboard-editor--delete-dashlet-icon--padding: $ibo-spacing-100 6px !default;
$ibo-dashboard-editor--delete-dashlet-icon--z-index: 21 !default;
$ibo-dashlet--properties--padding-bottom: 20px !default;
$ibo-dashlet--properties--table--width: 100% !default;
$ibo-dashlet--properties--table--cell--margin-bottom: 14px !default;
.ibo-dashboard-editor--pane{
flex-grow: 1;
@@ -47,13 +51,13 @@ $ibo-dashboard-editor--delete-dashlet-icon--z-index: 21 !default;
.ibo-dashlet--properties{
display: flex;
flex-direction: column;
padding-bottom: 20px;
padding-bottom: $ibo-dashlet--properties--padding-bottom;
table{
width: 100%;
width: $ibo-dashlet--properties--table--width;
text-align: left;
td, th {
margin-bottom: 14px;
margin-bottom: $ibo-dashlet--properties--table--cell--margin-bottom;
.ibo-field {
@extend %ibo-font-size-100;

View File

@@ -7,8 +7,38 @@
$ibo-dashboard--grid--width: 100% !default;
$ibo-dashboard--grid--elements-spacing-x: $ibo-dashlet--elements-spacing-x !default;
$ibo-dashboard--grid--elements-spacing-y: $ibo-dashlet--elements-spacing-y !default;
$ibo-dashboard--grid--edit-mode--margin: 1px !default;
$ibo-dashboard--grid--edit-mode--border-color: $ibo-color-grey-400 !default;
$ibo-dashboard--grid--edit-mode--border: 2px $ibo-dashboard--grid--edit-mode--border-color dashed !default;
$ibo-dashboard--grid--edit-mode--width: 100% !default;
$ibo-dashboard--grid--edit-mode--min-height: 40px !default;
$ibo-dashboard--top-bar-padding-bottom: 20px !default;
$ibo-dashboard--selector--margin-left: $ibo-spacing-400 !default;
$ibo-dashboard--selector--margin-right: 1 !default;
$ibo-dashboard--selector--hover--background-color: $ibo-color-secondary-100 !default;
$ibo-dashboard--selector--hover--border-radius: $ibo-button--border-radius !default;
$ibo-dashboard--selector--selector-label--margin-x: 10px !default;
$ibo-dashboard--switch--width: 30px !default;
$ibo-dashboard--switch--height: $ibo-size-300 !default;
$ibo-dashboard--slider--before--content: "\f007" !default;
$ibo-dashboard--slider--before--font-size: $ibo-font-size-50 !default;
$ibo-dashboard--slider--before--color: $ibo-color-secondary-800 !default;
$ibo-dashboard--slider--before--right: 5px !default;
$ibo-dashboard--slider--before--bottom: 3px !default;
$ibo-dashboard--slider--after--content: "\f1ad" !default;
$ibo-dashboard--slider--after--font-size: $ibo-font-size-150 !default;
$ibo-dashboard--slider--after--color: $ibo-color-primary-600 !default;
$ibo-dashboard--slider--after--left: 6px !default;
$ibo-dashboard--slider--after--bottom: 1px !default;
/* Rules */
.ibo-dashboard--top-bar {
@extend %ibo-full-height-content;
@@ -30,18 +60,18 @@ $ibo-dashboard--top-bar-padding-bottom: 20px !default;
.ibo-dashboard--selector {
display: flex;
align-items: center;
margin-left: 12px;
margin-right: 1px;
margin-left: $ibo-dashboard--selector--margin-left;
margin-right: $ibo-dashboard--selector--margin-right;
&:hover {
background-color: $ibo-color-secondary-100;
border-radius: $ibo-button--border-radius;
background-color: $ibo-dashboard--selector--hover--background-color;
border-radius: $ibo-dashboard--selector--hover--border-radius;
}
.selector-label {
display: inline-block;
margin-left: 10px;
margin-right: 10px;
margin-left: $ibo-dashboard--selector--selector-label--margin-x;
margin-right: $ibo-dashboard--selector--selector-label--margin-x;
vertical-align: super;
}
}
@@ -75,7 +105,7 @@ $ibo-dashboard--top-bar-padding-bottom: 20px !default;
margin: calc(-1 * #{$ibo-dashboard--grid--elements-spacing-y} / 2) calc(-1 * #{$ibo-dashboard--grid--elements-spacing-x} / 2); /* Because of the margin all around the dashlets, we need to compensate it */
min-width: 0;
/* Compensate negative margin on inner borders to simulate egal dashlets spacing between columns */
/* Compensate negative margin on inner borders to simulate equal dashlets spacing between columns */
&:not(:last-child) {
margin-right: 0;
}
@@ -85,10 +115,10 @@ $ibo-dashboard--top-bar-padding-bottom: 20px !default;
}
&.edit_mode {
margin: 1px;
border: 2px #ccc dashed;
width: 100%;
min-height: 40px;
margin: $ibo-dashboard--grid--edit-mode--margin;
border: $ibo-dashboard--grid--edit-mode--border;
width: $ibo-dashboard--grid--edit-mode--width;
min-height: $ibo-dashboard--grid--edit-mode--min-height;
}
}
@@ -98,8 +128,8 @@ $ibo-dashboard--top-bar-padding-bottom: 20px !default;
.ibo-dashboard--switch {
position: relative;
display: inline-block;
width: 30px;
height: 24px;
width: $ibo-dashboard--switch--width;
height: $ibo-dashboard--switch--height;
vertical-align: baseline;
}
@@ -119,31 +149,29 @@ $ibo-dashboard--top-bar-padding-bottom: 20px !default;
}
.ibo-dashboard--slider:before {
@extend %fa-solid-base;
content: $ibo-dashboard--slider--before--content;
font-size: $ibo-dashboard--slider--before--font-size;
color: $ibo-dashboard--slider--before--color;
position: absolute;
right: 5px;
bottom: 3px;
color: $ibo-color-secondary-800;
content: "\f007";
font-family: "Font Awesome 5 Free", serif;
font-size: $ibo-font-size-50;
font-weight: 900;
right: $ibo-dashboard--slider--before--right;
bottom: $ibo-dashboard--slider--before--bottom;
}
.ibo-dashboard--slider:after {
@extend %fa-solid-base;
content: $ibo-dashboard--slider--after--content;
font-size: $ibo-dashboard--slider--after--font-size;
color: $ibo-dashboard--slider--after--color;
position: absolute;
left: 6px;
bottom: 1px;
color: $ibo-color-primary-600;
content: "\f1ad";
font-family: "Font Awesome 5 Free", serif;
font-size: $ibo-font-size-150;
font-weight: 900;
left: $ibo-dashboard--slider--after--left;
bottom: $ibo-dashboard--slider--after--bottom;
}
input:checked + .ibo-dashboard--slider:before {
content: "\f1ad";
content: $ibo-dashboard--slider--after--content;
}
input:checked + .ibo-dashboard--slider:after {
content: "\f007";
content: $ibo-dashboard--slider--before--content;
}

View File

@@ -4,10 +4,15 @@
*/
$ibo-column--min-width: 300px !default;
$ibo-mini-column--min-width: 30px !default;
$ibo-column--padding-x: abs($ibo-multi-column--margin-x) !default;
$ibo-column--padding-y: $ibo-spacing-0 !default;
$ibo-mini-column--min-width: 30px !default;
$ibo-mini-column--padding-x: $ibo-column--padding-x !default;
$ibo-mini-column--padding-y: $ibo-column--padding-y !default;
$ibo-mini-column--button-margin-left: $ibo-spacing-0 !default;
$ibo-mini-column--button-margin-right: $ibo-spacing-0 !default;
$ibo-column--margin-bottom--is-last-element: $ibo-spacing-800 !default;
.ibo-column {
@@ -26,14 +31,14 @@ $ibo-column--margin-bottom--is-last-element: $ibo-spacing-800 !default;
min-width: $ibo-mini-column--min-width;
flex-grow: 1;
flex-shrink: 1;
padding: $ibo-column--padding-y $ibo-column--padding-x;
padding: $ibo-mini-column--padding-y $ibo-mini-column--padding-x;
flex-basis: 10%;
display: flex;
flex-direction: column;
>.ibo-button{
margin-left: 0;
margin-right: 0;
margin-left: $ibo-mini-column--button-margin-left;
margin-right: $ibo-mini-column--button-margin-right;
}
&:not(:last-child) {
margin-bottom: $ibo-column--margin-bottom--is-last-element;

View File

@@ -3,7 +3,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-multi-column--margin-x: -16px !default; /* This is to compensate columns padding and make the whole multicolumn align with the parent borders (cf. Bootstrap rows / cols) */
$ibo-multi-column--margin-x: -$ibo-spacing-500 !default; /* This is to compensate columns padding and make the whole multicolumn align with the parent borders (cf. Bootstrap rows / cols) */
$ibo-multi-column--margin-y: $ibo-spacing-0 !default;
.ibo-multi-column {

View File

@@ -16,6 +16,8 @@ $ibo-object-details--icon--spacing--as-medallion--is-sticking: $ibo-object-detai
$ibo-object-details--status-dot--size: 10px !default;
$ibo-object-details--status-dot--spacing: $ibo-spacing-300 !default;
$ibo-object-details--status-dot--border-radius: $ibo-border-radius-full !default;
$ibo-object-details--status--class--before--content: "(" !default;
$ibo-object-details--status--class--after--content: ")" !default;
$ibo-object-details--tag--sibling-spacing: $ibo-spacing-400 !default;
$ibo-object-details--tag--color: $ibo-panel--subtitle--color !default;
@@ -86,11 +88,11 @@ $ibo-object-details--header-right--padding-right--is-sticking: $ibo-spacing-300
display: inline-flex; /* To avoid having spaces around the class name due to the indentation */
&::before {
content: "(";
content: $ibo-object-details--status--class--before--content;
}
&::after {
content: ")";
content: $ibo-object-details--status--class--after--content;
}
}

View File

@@ -5,12 +5,13 @@
$ibo-wizard-container--padding: 10px $ibo-spacing-500 !default;
$ibo-wizard-container--background-color: $ibo-color-blue-200 !default;
$ibo-wizard-container--border-radius: $ibo-border-radius-300 !default;
$ibo-wizard-container--border-color: $ibo-color-blue-600 !default;
$ibo-wizard-container--border-width: 3px !default;
.ibo-wizard-container {
padding: $ibo-wizard-container--padding;
background: $ibo-wizard-container--background-color;
border-radius: $ibo-border-radius-300;
border-radius: $ibo-wizard-container--border-radius;
border-left: $ibo-wizard-container--border-width solid $ibo-wizard-container--border-color;
}

View File

@@ -63,11 +63,10 @@ $ibo-attachment--tab-header--drop-in--icon--color: $ibo-color-blue-600 !default;
background-color: $ibo-attachment--tab-header--drop-in--background-color;
color: $ibo-attachment--tab-header--drop-in--color;
> a::after{
padding-left: $ibo-attachment--tab-header--drop-in--icon--padding-left;
font-family: "Font Awesome 5 Free";
content: $ibo-attachment--tab-header--drop-in--icon--content;
font-weight: 900;
color: $ibo-color-blue-600;
@extend %fa-solid-base;
padding-left: $ibo-attachment--tab-header--drop-in--icon--padding-left;
color: $ibo-attachment--tab-header--drop-in--icon--color;
}
}
}

View File

@@ -19,18 +19,19 @@ $ibo-audit--audit-line--csv-download--height: 2.5em !default;
$ibo-audit--audit-line--status-indicator--diameter: 12px !default;
$ibo-audit--audit-line--status-indicator--margin-right: 5px !default;
/* Use semantic colors to ease accessibility */
$ibo-audit--status--color: (
'red': (
$ibo-color-red-700,
$ibo-color-danger-700,
),
'orange': (
$ibo-color-orange-700,
$ibo-color-warning-700,
),
'green': (
$ibo-color-green-800,
$ibo-color-success-800,
),
);
@each $sColor, $aAttributes in $ibo-audit--status--color {
$bg-color: nth($aAttributes, 1);
.ibo-audit--audit-category--panel .ibo-panel--body {

View File

@@ -7,6 +7,7 @@ $ibo-csv-import--cell-message--padding-top: 3px !default;
$ibo-csv-import--cell-modified--color: $ibo-color-blue-700 !default;
$ibo-csv-import--cell-error--color: $ibo-color-red-700 !default;
$ibo-csv-import--row--border-color: $ibo-color-grey-400 !default;
$ibo-csv-import--row--border: 1px $ibo-csv-import--row--border-color solid !default;
$ibo-csv-import--row-error--background-color: $ibo-color-red-200 !default;
$ibo-csv-import--download-file--color: $ibo-color-primary-400 !default;
$ibo-csv-import--download-file--font-size: 4em !default;
@@ -31,20 +32,20 @@ div.ibo-csv-import--cell-message {
}
tr.ibo-csv-import--row-unchanged td {
border-bottom: 1px $ibo-csv-import--row--border-color solid;
border-bottom: $ibo-csv-import--row--border;
}
.wizContainer table tr.ibo-csv-import--row-error td {
border-bottom: 1px $ibo-csv-import--row--border-color solid;
border-bottom: $ibo-csv-import--row--border;
background-color: $ibo-csv-import--row-error--background-color;
}
tr.ibo-csv-import--row-modified td {
border-bottom: 1px $ibo-csv-import--row--border-color solid;
border-bottom: $ibo-csv-import--row--border;
}
tr.ibo-csv-import--row-added td {
border-bottom: 1px $ibo-csv-import--row--border-color solid;
border-bottom: $ibo-csv-import--row--border;
}
.ibo-csv-import--download-file {

View File

@@ -11,6 +11,9 @@ $ibo-data-synchro-source--replicas-table--cell--arrow--min-width: 100px !default
$ibo-data-synchro-source--replicas-status--warning--margin: $ibo-spacing-0 5px $ibo-spacing-0 $ibo-spacing-300 !default;
$ibo-data-synchro-source--replicas-status-separator--border-color: $ibo-color-white-100 !default;
$ibo-data-synchro-source--replicas-status-separator--border: 2px solid $ibo-data-synchro-source--replicas-status-separator--border-color !default;
$ibo-data-synchro-source--replicas-status--color: (
'grey': (
$ibo-color-grey-400,
@@ -42,6 +45,10 @@ $ibo-data-synchro-source--replicas-status--color: (
),
) !default;
$ibo-data-synchro-source--synoptics--cell--arrow--border-color: $ibo-color-grey-50 !default;
$ibo-data-synchro-source--synoptics--cell--arrow--border: 2px solid $ibo-data-synchro-source--synoptics--cell--arrow--border-color !default;
@each $sColor, $aAttributes in $ibo-data-synchro-source--replicas-status--color {
$bg-color: nth($aAttributes, 1);
$color: nth($aAttributes, 2);
@@ -50,8 +57,9 @@ $ibo-data-synchro-source--replicas-status--color: (
background-color: $bg-color;
}
}
.ibo-data-synchro-source--replicas-status-separator {
border-top: 2px solid $ibo-color-white-100;
border-top: $ibo-data-synchro-source--replicas-status-separator--border-color;
}
.ibo-data-synchro-source--replicas-status.ibo-is-light{
@@ -67,7 +75,7 @@ $ibo-data-synchro-source--replicas-status--color: (
text-align: center;
&.arrow {
min-width: $ibo-data-synchro-source--replicas-table--cell--arrow--min-width;
border-top: 2px solid $ibo-color-grey-50;
border-top: $ibo-data-synchro-source--synoptics--cell--arrow--border;
}
}
.ibo-data-synchro-source--replicas-status--warning{

View File

@@ -29,12 +29,14 @@ $ibo-datamodel-viewer--schema--self-referencing--hover--fill: $ibo-datamodel-vie
$ibo-datamodel-viewer--schema--tooltip--fill: $ibo-color-white-100 !default;
$ibo-datamodel-viewer--schema--tooltip--background-color: $ibo-color-grey-900 !default;
$ibo-datamodel-viewer--schema--tooltip--border-color: $ibo-color-grey-700 !default;
$ibo-datamodel-viewer--schema--tooltip--border: 1px solid $ibo-datamodel-viewer--schema--tooltip--border-color !default;
$ibo-datamodel-viewer--schema--tooltip--border-radius: $ibo-border-radius-300 !default;
$ibo-datamodel-viewer--schema--tooltip--icon--font-size: $ibo-font-size-100 !default;
$ibo-datamodel-viewer--schema--tooltip--span--margin: 3px !default;
$ibo-datamodel-viewer--schema--tooltip-top--border-color: $ibo-color-grey-700 !default;
$ibo-datamodel-viewer--schema--tooltip-top--border: 1px solid $ibo-datamodel-viewer--schema--tooltip-top--border-color !default;
$ibo-datamodel-viewer--schema--tooltip-top--padding: 3px !default;
$ibo-datamodel-viewer--lifecycle-image--margin-bottom: $ibo-spacing-500 !default;
@@ -116,7 +118,7 @@ $ibo-datamodel-viewer--lifecycle-image--margin-bottom: $ibo-spacing-500 !default
position: fixed;
text-align: center;
background: $ibo-datamodel-viewer--schema--tooltip--fill;
border: 1px solid $ibo-datamodel-viewer--schema--tooltip--border-color;
border: $ibo-datamodel-viewer--schema--tooltip--border;
border-radius: $ibo-datamodel-viewer--schema--tooltip--border-radius;
pointer-events: none;
fill: $ibo-datamodel-viewer--schema--tooltip--background-color;
@@ -132,7 +134,7 @@ $ibo-datamodel-viewer--lifecycle-image--margin-bottom: $ibo-spacing-500 !default
#tooltipD3_top {
@extend %ibo-font-ral-bol-100;
border-bottom: 1px solid $ibo-datamodel-viewer--schema--tooltip-top--border-color;
border-bottom: $ibo-datamodel-viewer--schema--tooltip-top--border;
padding: $ibo-datamodel-viewer--schema--tooltip-top--padding;
}

View File

@@ -22,6 +22,10 @@ $ibo-display-graph--search-box--criterion--content--padding-x: 15px !default;
$ibo-display-graph--search-box--criterion--content--checkbox--margin-right: 10px !default;
$ibo-display-graph--graph-grouping-threshold--padding-right: $ibo-spacing-0 !default;
$ibo-display-graph--impacted-placeholder-height: $ibo-size-650 !default;
.itop-simple-graph {
margin-top: $ibo-simple-graph--margin-top;
border: 1px dotted transparent;
@@ -83,7 +87,7 @@ $ibo-display-graph--search-box--criterion--content--checkbox--margin-right: 10px
}
#graph_grouping_threshold{
width: auto;
padding-right: 0;
padding-right: $ibo-display-graph--graph-grouping-threshold--padding-right;
}
.ibo-display-graph--search-box {
.sf_criterion_area{
@@ -121,5 +125,5 @@ $ibo-display-graph--search-box--criterion--content--checkbox--margin-right: 10px
}
}
#impacted_objects_lists_placeholder, #impacted_groups_placeholder{
height: 250px;
height: $ibo-display-graph--impacted-placeholder-height;
}

View File

@@ -2,6 +2,7 @@
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-select--notification-item--mixed-value--font-size: $ibo-font-size-100 !default;
$ibo-input-select--notification-item--mixed-value--color: $ibo-color-primary-800 !default;
$ibo-input-select--notification-item--mixed-value--margin-left: 4px !default;
@@ -12,7 +13,7 @@ $ibo-input-select--notification-item--mixed-value--margin-left: 4px !default;
}
.ibo-input-select--notification-item--mixed-value{
font-size: $ibo-font-size-100;
font-size: $ibo-input-select--notification-item--mixed-value--font-size;
color: $ibo-input-select--notification-item--mixed-value--color;
margin-left: $ibo-input-select--notification-item--mixed-value--margin-left;
}

View File

@@ -4,11 +4,15 @@
*/
$ibo-notifications--view-all--container--grid-gap: $ibo-spacing-600 !default;
$ibo-notifications--view-all--container--object-summary--title--font-size: $ibo-font-size-250 !default;
$ibo-notifications--view-all--container--object-summary--toolbar--min-width: $ibo-size-500 !default;
$ibo-notifications--view-all--container--object-summary--panel--body--max-height: 400px !default;
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-red-600 !default;
$ibo-notifications--view-all--item--read--highlight--background-color: $ibo-color-grey-200 !default;
$ibo-notifications--view-all--container--read-unread--action--margin-left: $ibo-spacing-0 !default;
$ibo-notifications--view-all--container--large--grid-template-columns: repeat(3, 1fr) !default;
$ibo-notifications--view-all--container--medium--grid-template-columns: repeat(2, 1fr) !default;
$ibo-notifications--view-all--container--small--grid-template-columns: repeat(1, 1fr) !default;
@@ -20,10 +24,10 @@ $ibo-notifications--view-all--empty--svg--max-width: 30% !default;
display: grid;
grid-gap: $ibo-notifications--view-all--container--grid-gap;
.ibo-object-summary .ibo-panel--title{
font-size: $ibo-font-size-250;
font-size: $ibo-notifications--view-all--container--object-summary--title--font-size;
}
.ibo-object-summary .ibo-panel--toolbar{
min-width: 102px;
min-width: $ibo-notifications--view-all--container--object-summary--toolbar--min-width;
}
.ibo-object-summary > .ibo-panel--body{
box-shadow: none;
@@ -59,7 +63,7 @@ $ibo-notifications--view-all--empty--svg--max-width: 30% !default;
.ibo-notifications--view-all--container {
.ibo-notifications--view-all--read-action, .ibo-notifications--view-all--unread-action {
margin-left: 0 !important;
margin-left: $ibo-notifications--view-all--container--read-unread--action--margin-left !important;
}
}

View File

@@ -1,3 +1,9 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-oauth-wizard--illustration--svg--max-height: $ibo-size-700 !default;
.ibo-oauth-wizard .ibo-panel--body{
.ibo-oauth-wizard--form--container{
display: flex;
@@ -8,7 +14,7 @@
}
.ibo-oauth-wizard--illustration svg{
max-height: 400px;
max-height: $ibo-oauth-wizard--illustration--svg--max-height;
}
}
#ibo-oauth-wizard--conf--result{

View File

@@ -9,6 +9,8 @@ $ibo-preferences--user-preferences--picture-placeholder--image--diameter: 54px !
$ibo-preferences--user-preferences--picture-placeholder--image--border-radius: $ibo-border-radius-full !default;
$ibo-preferences--user-preferences--picture-placeholder--image--margin: $ibo-spacing-400 !default;
$ibo-preferences--user-preferences--picture-placeholder--image--background-color: $ibo-color-grey-300 !default;
$ibo-preferences--user-preferences--picture-placeholder--image--border-color: $ibo-preferences--user-preferences--picture-placeholder--image--background-color !default;
$ibo-preferences--user-preferences--picture-placeholder--image--border: solid 3px $ibo-preferences--user-preferences--picture-placeholder--image--border-color !default;
$ibo-preferences--user-preferences--picture-placeholder--image--active--border-color: $ibo-color-blue-800;
$ibo-preferences--user-preferences--picture-placeholder--image--hover--border-color: $ibo-color-blue-600;
@@ -18,6 +20,8 @@ $ibo-keyboard-shortcut--shortcut--width: 30% !default;
$ibo-keyboard-shortcut--input--color: $ibo-color-grey-800 !default;
$ibo-keyboard-shortcut--input--background-color: transparent !default;
$ibo-keyboard-shortcut--input--border-color: $ibo-color-grey-500 !default;
$ibo-keyboard-shortcut--input--border: 1px solid $ibo-keyboard-shortcut--input--border-color !default;
$ibo-keyboard-shortcut--input--border-bottom: 2px solid $ibo-keyboard-shortcut--input--border-color !default;
$ibo-keyboard-shortcut--input--border-radius: $ibo-border-radius-300 !default;
$ibo-keyboard-shortcut--input--padding-y: $ibo-spacing-100 !default;
$ibo-keyboard-shortcut--input--padding-x: $ibo-spacing-200 !default;
@@ -26,6 +30,8 @@ $ibo-keyboard-shortcut--input--margin-bottom: 5px !default;
$ibo-keyboard-shortcut--input--is-focus--color: $ibo-color-primary-800 !default;
$ibo-keyboard-shortcut--input--is-focus--border-color: $ibo-color-primary-600 !default;
$ibo-favorite-organizations--toolbar--padding-top: $ibo-button--padding-y/2 !default;
#ibo-main-content >.ibo-panel{
margin-left: $ibo-preferences--panel--margin-x;
margin-right: $ibo-preferences--panel--margin-x;
@@ -41,7 +47,7 @@ $ibo-keyboard-shortcut--input--is-focus--border-color: $ibo-color-primary-600 !d
width: $ibo-preferences--user-preferences--picture-placeholder--image--diameter;
border-radius: $ibo-preferences--user-preferences--picture-placeholder--image--border-radius;
margin: $ibo-preferences--user-preferences--picture-placeholder--image--margin;
border: solid 3px $ibo-preferences--user-preferences--picture-placeholder--image--background-color;
border: $ibo-preferences--user-preferences--picture-placeholder--image--border;
> img{
border-radius: $ibo-preferences--user-preferences--picture-placeholder--image--border-radius;
@@ -72,8 +78,8 @@ $ibo-keyboard-shortcut--input--is-focus--border-color: $ibo-color-primary-600 !d
color: $ibo-keyboard-shortcut--input--color;
background-color: $ibo-keyboard-shortcut--input--background-color;
border: 1px solid $ibo-keyboard-shortcut--input--border-color;
border-bottom: 2px solid $ibo-keyboard-shortcut--input--border-color;
border: $ibo-keyboard-shortcut--input--border;
border-bottom: $ibo-keyboard-shortcut--input--border-bottom;
border-radius: $ibo-keyboard-shortcut--input--border-radius;
padding: $ibo-keyboard-shortcut--input--padding-y $ibo-keyboard-shortcut--input--padding-x;
@@ -92,5 +98,5 @@ $ibo-keyboard-shortcut--input--is-focus--border-color: $ibo-color-primary-600 !d
}
#ibo-favorite-organizations .ibo-datatable--toolbar {
padding-top: $ibo-button--padding-y/2;
padding-top: $ibo-favorite-organizations--toolbar--padding-top;
}

View File

@@ -6,14 +6,15 @@
$ibo-page-banner--background-color: $ibo-color-red-600 !default;
$ibo-page-banner--text-color: $ibo-color-red-100 !default;
$ibo-page-banner--text-content: "THIS IS NOT PRODUCTION INSTANCE" !default;
$ibo-page-banner--padding: 0.2 !default;
$ibo-page-banner--font-size: 1rem !default;
#ibo-page-banner::before {
display: block;
width: 100%;
padding: 0.2rem;
padding: $ibo-page-banner--padding;
text-align: center;
font-size: 1rem;
font-size: $ibo-page-banner--font-size;
background: $ibo-page-banner--background-color;
color: $ibo-page-banner--text-color;
content: $ibo-page-banner--text-content;

View File

@@ -19,6 +19,9 @@ $ibo-hyperlink-text-decoration--on-hover: $common-hyperlink-text-decoration--on-
$ibo-hyperlink-color--on-active: $common-hyperlink-color--on-active !default;
$ibo-hyperlink-text-decoration--on-active: $common-hyperlink-text-decoration--on-active !default;
$ibo-paragraph--spacing-top: 0.25em !default;
$ibo-paragraph--spacing-bottom: 0.25em !default;
$ibo-figure--spacing-x: $common-figure--spacing-x !default; /* Mind that this matches Bulma rule for figure */
$ibo-figure--spacing-y: $common-figure--spacing-y !default;
@@ -122,8 +125,8 @@ $ibo-figure--spacing-y: $common-figure--spacing-y !default;
}
p {
margin-top: 0.25em;
margin-bottom: 0.25em;
margin-top: $ibo-paragraph--spacing-top;
margin-bottom: $ibo-paragraph--spacing-bottom;
}
figure {

View File

@@ -3,12 +3,15 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-selectable--content: ' ' !default;
$ibo-selectable--background-color: $common-selectable--background-color !default;
$ibo-selectable--hover--content: '\f058' !default;
$ibo-selectable--hover--color: $common-selectable--hover--color !default;
$ibo-selectable--hover--background-color: $common-selectable--hover--background-color !default;
$ibo-selectable--hover--background-opacity: $common-selectable--hover--background-opacity !default;
$ibo-selected--content: '\f058' !default;
$ibo-selected--color: $common-selected--color !default;
$ibo-selected--background-color: $common-selected--background-color !default;
$ibo-selected--background-opacity: $common-selected--background-opacity !default;
@@ -17,21 +20,21 @@ $ibo-selected--hover--background-color: $common-selected--hover--background-colo
$ibo-selected--hover--background-opacity: $common-selected--hover--background-opacity !default;
@mixin ibo-selectable {
content: ' ';
content: $ibo-selectable--content;
@extend %fa-solid-base;
background-color: $ibo-selectable--background-color;
cursor: pointer;
}
@mixin ibo-selectable-hover {
@extend %fa-regular-base;
content: '\f058';
content: $ibo-selectable--hover--content;
color: $ibo-selectable--hover--color;
background-color: transparentize($ibo-selectable--hover--background-color, $ibo-selectable--hover--background-opacity);
}
@mixin ibo-selected {
@extend %fa-solid-base;
content: '\f058';
content: $ibo-selected--content;
color: $ibo-selected--color;
background-color: transparentize($ibo-selected--background-color, $ibo-selected--background-opacity);
}

View File

@@ -5,15 +5,16 @@
*/
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\iEventServiceSetup;
class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExtension
class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
{
const ENUM_GUI_ALL = 'all';
const ENUM_GUI_BACKOFFICE = 'backoffice';
const ENUM_GUI_PORTALS = 'portals';
protected static $m_bIsModified = false;
public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false)
{
if ($this->GetAttachmentsPosition() == 'properties')
@@ -158,45 +159,39 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
return array();
}
public function OnIsModified($oObject)
public function RegisterEventsAndListeners() : void
{
return self::$m_bIsModified;
EventService::RegisterListener(EVENT_DB_AFTER_WRITE, [$this, 'OnDBAfterWrite']);
EventService::RegisterListener(EVENT_DB_AFTER_DELETE, [$this, 'OnDBAfterDelete']);
}
public function OnCheckToWrite($oObject)
public function OnDBAfterWrite(EventData $oEventData)
{
return array();
}
$oObject = $oEventData->Get('object');
$oCMDBChange = $oEventData->Get('cmdb_change');
$bIsNew = $oEventData->Get('is_new');
public function OnCheckToDelete($oObject)
{
return array();
}
public function OnDBUpdate($oObject, $oChange = null)
{
if ($this->IsTargetObject($oObject))
{
// Get all current attachments
$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
$oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
while ($oAttachment = $oSet->Fetch())
{
$oAttachment->SetItem($oObject, true /*updateonchange*/);
if($bIsNew){
self::UpdateAttachments($oObject, $oCMDBChange);
}
else{
// Get all current attachments
$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
$oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($oObject), 'item_id' => $oObject->GetKey()));
while ($oAttachment = $oSet->Fetch())
{
$oAttachment->SetItem($oObject, true /*updateonchange*/);
}
}
}
}
public function OnDBInsert($oObject, $oChange = null)
public function OnDBAfterDelete(EventData $oEventData)
{
if ($this->IsTargetObject($oObject))
{
self::UpdateAttachments($oObject, $oChange);
}
}
$oObject = $oEventData->Get('object');
public function OnDBDelete($oObject, $oChange = null)
{
if ($this->IsTargetObject($oObject))
{
$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
@@ -291,7 +286,7 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
* @see ObjectFormManager::FinalizeAttachments() for the portal version
*
* @param $oObject
* @param $oChange
* @param $oCMDBChange
*
* @return void
* @throws \ArchivedObjectException
@@ -303,10 +298,8 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
protected static function UpdateAttachments($oObject, $oChange = null)
protected static function UpdateAttachments($oObject, $oCMDBChange = null)
{
self::$m_bIsModified = false;
if (utils::ReadParam('attachment_plugin', 'not-in-form') == 'not-in-form')
{
// Workaround to an issue in iTop < 2.0
@@ -363,9 +356,10 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
{
foreach ($aActions as $oChangeOp)
{
self::RecordHistory($oChange, $oObject, $oChangeOp);
self::RecordHistory($oCMDBChange, $oObject, $oChangeOp);
}
self::$m_bIsModified = true;
$oObject->MarkObjectAsModified();
}
}
}
@@ -556,11 +550,11 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
}
/////////////////////////////////////////////////////////////////////////
private static function RecordHistory($oChange, $oTargetObject, $oMyChangeOp)
private static function RecordHistory($oCMDBChange, $oTargetObject, $oMyChangeOp)
{
if (!is_null($oChange))
if (!is_null($oCMDBChange))
{
$oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("change", $oCMDBChange->GetKey());
}
$oMyChangeOp->Set("objclass", get_class($oTargetObject));
$oMyChangeOp->Set("objkey", $oTargetObject->GetKey());
@@ -648,6 +642,8 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
return $bReadonly;
}
}
/**

View File

@@ -4,362 +4,20 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Base\Component\Alert\Alert;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Config\Validator\iTopConfigAstValidator;
use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator;
use Combodo\iTop\Config\Controller\ConfigEditorController;
use Combodo\iTop\Config\Validator\iTopConfigValidator;
require_once(APPROOT.'application/startup.inc.php');
const CONFIG_ERROR = 0;
const CONFIG_WARNING = 1;
const CONFIG_INFO = 2;
/**
* @param $sContents
* @param $oP
*
* @throws \Exception
*/
function TestConfig($sContents, $oP)
{
/// 1- first check if there is no malicious code
$oiTopConfigValidator = new iTopConfigAstValidator();
$oiTopConfigValidator->Validate($sContents);
/// 2 - only after we are sure that there is no malicious cade, we can perform a syntax check!
$oiTopConfigValidator = new iTopConfigSyntaxValidator();
$oiTopConfigValidator->Validate($sContents);
}
/**
* @param $sSafeContent
*
* @return bool
*/
function DBPasswordInNewConfigIsOk($sSafeContent)
{
$bIsWindows = (array_key_exists('WINDIR', $_SERVER) || array_key_exists('windir', $_SERVER));
if ($bIsWindows && (preg_match("@'db_pwd' => '[^%!\"]+',@U", $sSafeContent) === 0)) {
return false;
}
return true;
}
function CheckAsyncTasksRetryConfig(Config $oTempConfig, iTopWebPage $oP)
{
$iWarnings = 0;
foreach (get_declared_classes() as $sPHPClass) {
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->isSubclassOf('AsyncTask') && !$oRefClass->isAbstract()) {
$aMessages = AsyncTask::CheckRetryConfig($oTempConfig, $oRefClass->getName());
if (count($aMessages) !== 0) {
foreach ($aMessages as $sMessage) {
$oAlert = AlertUIBlockFactory::MakeForWarning('', $sMessage);
$oP->AddUiBlock($oAlert);
$iWarnings++;
}
}
}
}
return $iWarnings;
}
/**
* @param \Exception $e
*
* @return \Combodo\iTop\Application\UI\Base\Component\Alert\Alert
*/
function GetAlertFromException(Exception $e): Alert
{
switch ($e->getCode()) {
case CONFIG_WARNING:
$oAlert = AlertUIBlockFactory::MakeForWarning('', $e->getMessage());
break;
case CONFIG_INFO:
$oAlert = AlertUIBlockFactory::MakeForInformation('', $e->getMessage());
break;
case CONFIG_ERROR:
default:
$oAlert = AlertUIBlockFactory::MakeForDanger('', $e->getMessage());
}
return $oAlert;
}
/////////////////////////////////////////////////////////////////////
// Main program
//
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
ApplicationMenu::CheckMenuIdEnabled('ConfigEditor');
//$sOperation = utils::ReadParam('operation', 'menu');
//$oAppContext = new ApplicationContext();
$oP = new iTopWebPage(Dict::S('config-edit-title'));
$oP->set_base(utils::GetAbsoluteUrlAppRoot().'pages/');
$sAceDir = 'node_modules/ace-builds/src-min/';
$oP->LinkScriptFromAppRoot($sAceDir.'ace.js');
$oP->LinkScriptFromAppRoot($sAceDir.'mode-php.js');
$oP->LinkScriptFromAppRoot($sAceDir.'theme-eclipse.js');
$oP->LinkScriptFromAppRoot($sAceDir.'ext-searchbox.js');
$oConfigEditorController = new ConfigEditorController();
$oConfigEditorController->SetDefaultOperation('Edit');
$oConfigEditorController->HandleOperation();
try {
$sOperation = utils::ReadParam('operation', '');
$iEditorTopMargin = 2;
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled()) {
$iEditorTopMargin += 6;
}
$oP->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::S('config-edit-title')));
if (MetaModel::GetConfig()->Get('demo_mode')) {
throw new Exception(Dict::S('config-not-allowed-in-demo'), CONFIG_INFO);
}
if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled') {
throw new Exception(Dict::S('config-interactive-not-allowed'), CONFIG_WARNING);
}
$sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php';
$iEditorTopMargin += 9;
$sConfigContent = file_get_contents($sConfigFile);
$sConfigChecksum = md5($sConfigContent);
$sConfig = str_replace("\r\n", "\n", $sConfigContent);
$sOriginalConfig = $sConfig;
if (!empty($sOperation)) {
$iEditorTopMargin += 5;
$sConfig = utils::ReadParam('new_config', '', false, 'raw_data');
}
try {
if ($sOperation == 'revert') {
throw new Exception(Dict::S('config-reverted'), CONFIG_WARNING);
}
if ($sOperation == 'save') {
$sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
if (!utils::IsTransactionValid($sTransactionId, true)) {
throw new Exception(Dict::S('config-error-transaction'), CONFIG_ERROR);
}
$sChecksum = utils::ReadParam('checksum');
if ($sChecksum !== $sConfigChecksum) {
throw new Exception(Dict::S('config-error-file-changed'), CONFIG_ERROR);
}
if ($sConfig === $sOriginalConfig) {
throw new Exception(Dict::S('config-no-change'), CONFIG_INFO);
}
TestConfig($sConfig, $oP); // throws exceptions
@chmod($sConfigFile, 0770); // Allow overwriting the file
$sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-');
// Don't write the file as-is since it would allow to inject any kind of PHP code.
// Instead, write the interpreted version of the file
// Note:
// The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above
// and a second time during the load of the Config object below.
// If you are really concerned about an iTop administrator crafting some malicious
// PHP code inside the config file, then turn off the interactive configuration
// editor by adding the configuration parameter:
// 'itop-config' => array(
// 'config_editor' => 'disabled',
// )
file_put_contents($sTmpFile, $sConfig);
$oTempConfig = new Config($sTmpFile, true);
$oTempConfig->WriteToFile($sConfigFile);
@unlink($sTmpFile);
@chmod($sConfigFile, 0440); // Read-only
if (DBPasswordInNewConfigIsOk($sConfig)) {
$oAlert = AlertUIBlockFactory::MakeForSuccess('', Dict::S('config-saved'));
} else {
$oAlert = AlertUIBlockFactory::MakeForInformation('', Dict::S('config-saved-warning-db-password'));
}
$oP->AddUiBlock($oAlert);
$iWarnings = CheckAsyncTasksRetryConfig($oTempConfig, $oP);
// Read the config from disk after save
$sConfigContent = file_get_contents($sConfigFile);
$sConfigChecksum = md5($sConfigContent);
$sConfig = str_replace("\r\n", "\n", $sConfigContent);
$sOriginalConfig = $sConfig;
}
}
catch (Exception $e) {
$oAlert = GetAlertFromException($e);
$oP->AddUiBlock($oAlert);
}
// (remove EscapeHtml) N°5914 - Wrong encoding in modules configuration editor
$oP->AddUiBlock(new Html('<p>'.Dict::S('config-edit-intro').'</p>'));
$oForm = new Form();
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'save', 'operation'));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId()));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('checksum', $sConfigChecksum));
//--- Cancel button
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('config-cancel'), 'cancel_button', null, true, 'cancel_button');
$oCancelButton->SetOnClickJsCode("return ResetConfig();");
$oForm->AddSubBlock($oCancelButton);
//--- Submit button
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('config-apply'), null, Dict::S('config-apply'), true, 'submit_button');
$oForm->AddSubBlock($oSubmitButton);
//--- Config editor
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('prev_config', $sOriginalConfig, 'prev_config'));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('new_config', $sOriginalConfig));
$oForm->AddHtml("<div id =\"new_config\" style=\"position: absolute; top: ".$iEditorTopMargin."em; bottom: 0; left: 5px; right: 5px;\"></div>");
$oP->AddUiBlock($oForm);
$oP->add_script(
<<<'JS'
var EditorUtils = (function() {
var STORAGE_RANGE_KEY = 'cfgEditorRange';
var STORAGE_LINE_KEY = 'cfgEditorFirstline';
var _editorSavedRange = null;
var _editorSavedFirstLine = null;
var saveEditorDisplay = function(editor) {
_initObjectValues(editor);
_persistObjectValues();
};
var _initObjectValues = function(editor) {
_editorSavedRange = editor.getSelectionRange();
_editorSavedFirstLine = editor.renderer.getFirstVisibleRow();
};
var _persistObjectValues = function() {
sessionStorage.setItem(EditorUtils.STORAGE_RANGE_KEY, JSON.stringify(_editorSavedRange));
sessionStorage.setItem(EditorUtils.STORAGE_LINE_KEY, _editorSavedFirstLine);
};
var restoreEditorDisplay = function(editor) {
_restoreObjectValues();
_setEditorDisplay(editor);
};
var _restoreObjectValues = function() {
if ((sessionStorage.getItem(STORAGE_RANGE_KEY) == null)
|| (sessionStorage.getItem(STORAGE_LINE_KEY) == null)) {
return;
}
_editorSavedRange = JSON.parse(sessionStorage.getItem(EditorUtils.STORAGE_RANGE_KEY));
_editorSavedFirstLine = sessionStorage.getItem(EditorUtils.STORAGE_LINE_KEY);
sessionStorage.removeItem(STORAGE_RANGE_KEY);
sessionStorage.removeItem(STORAGE_LINE_KEY);
};
var _setEditorDisplay = function(editor) {
if ((_editorSavedRange == null) || (_editorSavedFirstLine == null)) {
return;
}
editor.selection.setRange(_editorSavedRange);
editor.renderer.scrollToRow(_editorSavedFirstLine);
};
var getEditorForm = function(editor) {
var editorContainer = $(editor.container);
return editorContainer.closest("form");
};
var updateConfigEditorButtonState = function(editor) {
var isSameContent = (editor.getValue() == $('#prev_config').val());
var hasNoError = $.isEmptyObject(editor.getSession().getAnnotations());
$('#cancel_button').prop('disabled', isSameContent);
$('#submit_button').prop('disabled', isSameContent || !hasNoError);
};
return {
STORAGE_RANGE_KEY: STORAGE_RANGE_KEY,
STORAGE_LINE_KEY : STORAGE_LINE_KEY,
saveEditorDisplay : saveEditorDisplay,
restoreEditorDisplay : restoreEditorDisplay,
getEditorForm : getEditorForm,
updateConfigEditorButtonState : updateConfigEditorButtonState
};
})();
JS
);
$oP->add_ready_script(<<<'JS'
var editor = ace.edit("new_config");
var configurationSource = $('input[name="new_config"]');
editor.getSession().setValue(configurationSource.val());
editor.getSession().on('change', function()
{
configurationSource.val(editor.getSession().getValue());
EditorUtils.updateConfigEditorButtonState(editor);
});
editor.getSession().on("changeAnnotation", function()
{
EditorUtils.updateConfigEditorButtonState(editor);
});
editor.setTheme("ace/theme/eclipse");
editor.getSession().setMode("ace/mode/php");
editor.commands.addCommand({
name: 'save',
bindKey: {win: "Ctrl-S", "mac": "Cmd-S"},
exec: function(editor) {
var editorForm = EditorUtils.getEditorForm(editor);
var submitButton = $('#submit_button');
if (submitButton.is(":enabled")) {
editorForm.trigger('submit');
}
}
});
var editorForm = EditorUtils.getEditorForm(editor);
editorForm.on('submit', function() {
EditorUtils.saveEditorDisplay(editor);
});
EditorUtils.restoreEditorDisplay(editor);
editor.focus();
JS
);
$sConfirmCancel = addslashes(Dict::S('config-confirm-cancel'));
$oP->add_script(<<<JS
function ResetConfig()
{
$("#operation").attr('value', 'revert');
if (confirm('$sConfirmCancel'))
{
$('input[name="new_config"]').val(prevConfig.val());
return true;
}
return false;
}
JS
);
} catch (Exception $e) {
$oAlert = GetAlertFromException($e);
$oP->AddUiBlock($oAlert);
}
$oP->output();

View File

@@ -22,6 +22,8 @@ SetupWebPage::AddModule(
'src/Validator/ConfigNodesVisitor.php',
'src/Validator/iTopConfigAstValidator.php',
'src/Validator/iTopConfigSyntaxValidator.php',
'src/Validator/iTopConfigValidator.php',
'src/Controller/ConfigEditorController.php',
),
'webservice' => array(),
'dictionary' => array(

View File

@@ -0,0 +1,175 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Config\Controller;
use Combodo\iTop\Application\TwigBase\Controller\Controller;
use Combodo\iTop\Config\Validator\iTopConfigValidator;
use Config;
use Dict;
use Exception;
use MetaModel;
use SetupUtils;
use utils;
class ConfigEditorController extends Controller
{
public const ROUTE_NAMESPACE = 'config_editor';
public const MODULE_NAME = "itop-config";
protected array $aWarnings = [];
protected array $aInfo = [];
protected array $aErrors = [];
protected array $aSuccesses = [];
public function __construct() {
parent::__construct(MODULESROOT.static::MODULE_NAME.'/templates', static::MODULE_NAME);
}
public function OperationEdit() : void
{
$bShowEditor = true;
$sConfigChecksum = '';
$sCurrentConfig = '';
try {
$sOperation = utils::ReadParam('edit_operation');
if (MetaModel::GetConfig()->Get('demo_mode')) {
throw new Exception(Dict::S('config-not-allowed-in-demo'), iTopConfigValidator::CONFIG_INFO);
}
if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled') {
throw new Exception(Dict::S('config-interactive-not-allowed'), iTopConfigValidator::CONFIG_WARNING);
}
$sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php';
$sCurrentConfig = file_get_contents($sConfigFile);
$sConfigChecksum = md5($sCurrentConfig);
try {
if ($sOperation == 'revert') {
$this->AddAlert(Dict::S('config-reverted'), iTopConfigValidator::CONFIG_WARNING);
}
else if ($sOperation == 'save') {
$sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
if (!utils::IsTransactionValid($sTransactionId)) {
throw new Exception(Dict::S('config-error-transaction'), iTopConfigValidator::CONFIG_ERROR);
}
$sChecksum = utils::ReadParam('checksum');
if ($sChecksum !== $sConfigChecksum) {
throw new Exception(Dict::S('config-error-file-changed'), iTopConfigValidator::CONFIG_ERROR);
}
$sNewConfig = utils::ReadParam('new_config', '', false, 'raw_data');
$sNewConfig = str_replace("\r\n", "\n", $sNewConfig);
if ($sNewConfig === $sCurrentConfig) {
throw new Exception(Dict::S('config-no-change'), iTopConfigValidator::CONFIG_INFO);
}
$oValidator = new iTopConfigValidator();
$oValidator->Validate($sNewConfig);// throws exceptions
@chmod($sConfigFile, 0770); // Allow overwriting the file
$sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-');
// Don't write the file as-is since it would allow to inject any kind of PHP code.
// Instead, write the interpreted version of the file
// Note:
// The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above
// and a second time during the load of the Config object below.
// If you are really concerned about an iTop administrator crafting some malicious
// PHP code inside the config file, then turn off the interactive configuration
// editor by adding the configuration parameter:
// 'itop-config' => array(
// 'config_editor' => 'disabled',
// )
file_put_contents($sTmpFile, $sNewConfig);
$oTempConfig = new Config($sTmpFile, true);
$oTempConfig->WriteToFile($sConfigFile);
@unlink($sTmpFile);
@chmod($sConfigFile, 0440); // Read-only
if ($oValidator->DBPasswordIsOk($oTempConfig->Get('db_pwd'))) {
$this->AddAlert(Dict::S('config-saved'), iTopConfigValidator::CONFIG_SUCCESS);
} else {
$this->AddAlert(Dict::S('config-saved-warning-db-password'), iTopConfigValidator::CONFIG_INFO);
}
$this->AddAlert($oValidator->CheckAsyncTasksRetryConfig($oTempConfig), iTopConfigValidator::CONFIG_WARNING);
// Read the config from disk after save
$sCurrentConfig = file_get_contents($sConfigFile);
$sConfigChecksum = md5($sCurrentConfig);
}
}
catch (Exception $e) {
$this->AddAlertFromException($e);
}
$this->AddAceScripts();
}
catch (Exception $e) {
$bShowEditor = false;
$this->AddAlertFromException($e);
}
// display page
$this->DisplayPage([
'aErrors' => $this->aErrors,
'aWarnings' => $this->aWarnings,
'aNotices' => $this->aInfo,
'aSuccesses' => $this->aSuccesses,
'bShowEditor' => $bShowEditor,
'sTransactionId' => utils::GetNewTransactionId(),
'sChecksum' => $sConfigChecksum,
'sPrevConfig' => $sCurrentConfig,
'sNewConfig' => $sCurrentConfig,
]);
}
/**
* @return void
* @throws \Exception
*/
protected function AddAceScripts(): void
{
$sAceDir = 'node_modules/ace-builds/src-min/';
$this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().$sAceDir.'ace.js');
$this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().$sAceDir.'mode-php.js');
$this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().$sAceDir.'theme-eclipse.js');
$this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().$sAceDir.'ext-searchbox.js');
}
public function AddAlertFromException(Exception $e): void
{
$this->AddAlert($e->getMessage(), $e->getCode());
}
public function AddAlert(array|string $sMessage, $iLevel): void
{
if (is_array($sMessage)) {
foreach ($sMessage as $sSingleMessage) {
$this->AddAlert($sSingleMessage, $iLevel);
}
return;
}
switch ($iLevel) {
case iTopConfigValidator::CONFIG_SUCCESS :
$this->aSuccesses[] = $sMessage;
break;
case iTopConfigValidator::CONFIG_WARNING :
$this->aWarnings[] = $sMessage;
break;
case iTopConfigValidator::CONFIG_INFO :
$this->aInfo[] = $sMessage;
break;
default :
$this->aErrors[] = $sMessage;
}
}
}

View File

@@ -31,10 +31,10 @@ class iTopConfigAstValidator
$aInitialNodes = $oParser->parse($sConfig);
} catch (\Error $e) {
$sMessage = 'Invalid configuration: '. \Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
throw new \Exception($sMessage, 0, $e);
throw new \Exception($sMessage, iTopConfigValidator::CONFIG_ERROR, $e);
}catch (\Exception $e) {
$sMessage = 'Invalid configuration: '. \Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
throw new \Exception($sMessage, 0, $e);
throw new \Exception($sMessage, iTopConfigValidator::CONFIG_ERROR, $e);
}
$oTraverser = new NodeTraverser();

View File

@@ -17,8 +17,7 @@ class iTopConfigSyntaxValidator
*/
public function Validate($sRawConfig)
{
try
{
try {
ini_set('display_errors', 1);
ob_start();
// in PHP < 7.0.0 syntax errors are in output
@@ -27,29 +26,24 @@ class iTopConfigSyntaxValidator
eval('if(0){'.trim($sConfig).'}');
$sNoise = trim(ob_get_contents());
}
catch (\Error $e)
{
catch (\Error $e) {
// ParseError only thrown in PHP7
throw new \Exception('Error in configuration: '.$e->getMessage().' at line '.$e->getLine());
throw new \Exception('Error in configuration: '.$e->getMessage().' at line '.$e->getLine(), iTopConfigValidator::CONFIG_ERROR);
}
finally
{
finally {
ob_end_clean();
}
if (strlen($sNoise) > 0)
{
if (preg_match("/(Error|Parse error|Notice|Warning): (.+) in \S+ : eval\(\)'d code on line (\d+)/i", strip_tags($sNoise), $aMatches))
{
if (strlen($sNoise) > 0) {
if (preg_match("/(Error|Parse error|Notice|Warning): (.+) in \S+ : eval\(\)'d code on line (\d+)/i", strip_tags($sNoise), $aMatches)) {
$sMessage = $aMatches[2];
$sLine = $aMatches[3];
$sMessage = \Dict::Format('config-parse-error', $sMessage, $sLine);
throw new \Exception($sMessage);
throw new \Exception($sMessage, iTopConfigValidator::CONFIG_ERROR);
}
else
{
else {
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new \Exception('Syntax error in configuration file: <tt>'.$sNoise.'</tt>');
throw new \Exception('Syntax error in configuration file: <tt>'.$sNoise.'</tt>', iTopConfigValidator::CONFIG_ERROR);
}
}
}

Some files were not shown because too many files have changed in this diff Show More