Merge branch 'develop' into feature/faf_doc_twig_blocks

This commit is contained in:
Eric Espie
2021-09-28 14:26:12 +02:00
1211 changed files with 30475 additions and 72781 deletions

13
.make/git-hooks/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Git hooks for iTop
## ❓ Goal
Those [git hooks](https://git-scm.com/docs/githooks) aims to ease developing on [iTop](https://github.com/Combodo/iTop).
## ☑ Available hooks
* pre-commit : rejects commit if you have at least one SCSS file staged, and no CSS file
## ⚙ Install
Just run install.php !

View File

@@ -0,0 +1,26 @@
<?php
$aHooks = [
'pre-commit.php',
];
$sAppRoot = dirname(__DIR__, 2);
foreach ($aHooks as $sSourceHookFileName) {
echo "Processing for `{$sSourceHookFileName}`...\n";
$sSourceHookPath = __DIR__.DIRECTORY_SEPARATOR.$sSourceHookFileName;
$aPathParts = pathinfo($sSourceHookFileName);
$sTargetHookPath = $sAppRoot.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'hooks'.DIRECTORY_SEPARATOR.$aPathParts['filename'];
if (file_exists($sTargetHookPath) || is_link($sTargetHookPath)) {
echo "Existing $sTargetHookPath ! Removing...";
unlink($sTargetHookPath);
echo "OK !\n";
}
echo "Creating symlink for hook in $sTargetHookPath...";
symlink($sSourceHookPath, $sTargetHookPath);
echo "OK !\n";
}

View File

@@ -0,0 +1,49 @@
#!/usr/bin/php
<?php
/**
* Reject any commit containing .scss files, but no .css file !
*/
echo "Checking files staged...\n";
$sFilesToCommit = shell_exec('git diff --cached --name-only --diff-filter=ACMRT');
$aFilesToCommit = explode("\n", $sFilesToCommit);
$aScssFiles = GetFilesWithExtension('scss', $aFilesToCommit);
if (count($aScssFiles) === 0) {
echo "No scss file : OK to go !\n";
exit(0);
}
$aCssFiles = GetFilesWithExtension('css', $aFilesToCommit);
if (count($aCssFiles) === 0) {
echo "There are SCSS files staged but no CSS file : REJECTING commit.\n";
echo "You must add the compiled SCSS files by running the setup !\n";
exit(1);
}
echo "We have SCSS but also CSS => OK to commit !\n";
exit(0);
function GetFilesWithExtension($sExtension, $aFiles) {
return array_filter(
$aFiles,
function($item) use ($sExtension) {
return (endsWith($item, '.'.$sExtension));
}
);
}
function endsWith( $haystack, $needle ) {
$length = strlen( $needle );
if( !$length ) {
return true;
}
return substr( $haystack, -$length ) === $needle;
}
function exitWithMessage($sMessage, $iCode) {
echo $sMessage;
exit($iCode);
}

View File

@@ -152,5 +152,9 @@ Stickers' design might change from one year to another. For the first year we wa
* White hat: Find and/or fix a vulnerability
* Contributor: Contribute by finding a bug, making an extension or any other way
* Partner: For Combodo's official partners
* Graduated: Follow a Combodo's iTop training
* Ambassador: Outstanding community contributors
* Beta tester: Test and give feedback on beta releases
* Extension developer: Develop and publish an extension
![](documentation/contributing-guide/contributing-stickers-side-by-side.png)

View File

@@ -24,6 +24,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
@@ -227,6 +228,20 @@ class ApplicationContext
}
return $sContext;
}
/**
* Returns the context an array of input blocks
*
* @return array The context as a sequence of <input type="hidden" /> tags
* @since 3.0.0
*/
public function GetForUIForm()
{
$aContextInputBlocks = [];
foreach ($this->aValues as $sName => $sValue) {
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", htmlentities($sValue, ENT_QUOTES, 'UTF-8'));
}
return $aContextInputBlocks;
}
/**
* Returns the context as sequence of input tags to be inserted inside a <form> tag
@@ -321,7 +336,7 @@ class ApplicationContext
$sPrevious = self::GetUrlMakerClass();
self::$m_sUrlMakerClass = $sClass;
$_SESSION['UrlMakerClass'] = $sClass;
Session::Set('UrlMakerClass', $sClass);
return $sPrevious;
}
@@ -334,9 +349,9 @@ class ApplicationContext
{
if (is_null(self::$m_sUrlMakerClass))
{
if (isset($_SESSION['UrlMakerClass']))
if (Session::IsSet('UrlMakerClass'))
{
self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
self::$m_sUrlMakerClass = Session::Get('UrlMakerClass');
}
else
{
@@ -389,9 +404,9 @@ class ApplicationContext
*/
protected static function LoadPluginProperties()
{
if (isset($_SESSION['PluginProperties']))
if (Session::IsSet('PluginProperties'))
{
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
self::$m_aPluginProperties = Session::Get('PluginProperties');
}
else
{
@@ -411,7 +426,7 @@ class ApplicationContext
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
Session::Set(['PluginProperties', $sPluginClass, $sProperty], $value);
}
/**

View File

@@ -909,6 +909,8 @@ abstract class ApplicationPopupMenuItem
/**
* 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 Extensibility
* @since 2.0
@@ -963,6 +965,8 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/**
* 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 Extensibility
* @since 2.0

View File

@@ -4,10 +4,13 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\Search\SearchForm;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\Button;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\StaticTable;
@@ -18,10 +21,12 @@ use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\Title;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanel;
@@ -94,6 +99,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
/** @var string */
public const ENUM_INPUT_TYPE_RADIO = 'radio';
/** @var string */
public const ENUM_INPUT_TYPE_CHECKBOX = 'checkbox';
/** @var string */
public const ENUM_INPUT_TYPE_DROPDOWN_RAW = 'dropdown_raw';
/** @var string */
public const ENUM_INPUT_TYPE_DROPDOWN_DECORATED = 'dropdown_decorated'; // now with the JQuery Selectize plugin
@@ -202,8 +209,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sParams .= $sName.'='.urlencode($value).'&'; // Always add a trailing &
}
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1';
$oPage->add_script(
<<<EOF
$oPage->add_early_script(<<<JS
if (!sessionStorage.getItem('$sSessionStorageKey'))
{
sessionStorage.setItem('$sSessionStorageKey', 1);
@@ -213,7 +219,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
sessionStorage.removeItem('$sSessionStorageKey');
}
EOF
JS
);
$oObj->Reload();
@@ -259,15 +265,15 @@ EOF
public static function SetSessionMessage($sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false)
{
$sMessageKey = $sClass.'::'.$iKey;
if (!isset($_SESSION['obj_messages'][$sMessageKey])) {
$_SESSION['obj_messages'][$sMessageKey] = array();
if (!Session::IsSet(['obj_messages', $sMessageKey])) {
Session::Set(['obj_messages', $sMessageKey], []);
}
if (!$bMustNotExist || !array_key_exists($sMessageId, $_SESSION['obj_messages'][$sMessageKey])) {
$_SESSION['obj_messages'][$sMessageKey][$sMessageId] = array(
if (!$bMustNotExist || !Session::IsSet(['obj_messages', $sMessageKey, $sMessageId])) {
Session::Set(['obj_messages', $sMessageKey, $sMessageId], [
'rank' => $fRank,
'severity' => $sSeverity,
'message' => $sMessage,
);
]);
}
}
@@ -575,12 +581,14 @@ HTML
foreach($aList as $sAttCode) {
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef instanceof AttributeDashboard) {
$sHostContainerInEditionUrlParam = ($bEditMode) ? '&host_container_in_edition=true' : '';
$oPage->AddAjaxTab($oAttDef->GetLabel(),
utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode().$sHostContainerInEditionUrlParam,
true,
'Class:'.$sClass.'/Attribute:'.$sAttCode,
AjaxTab::ENUM_TAB_PLACEHOLDER_DASHBOARD);
if (!$this->IsNew()) {
$sHostContainerInEditionUrlParam = ($bEditMode) ? '&host_container_in_edition=true' : '';
$oPage->AddAjaxTab($oAttDef->GetLabel(),
utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode().$sHostContainerInEditionUrlParam,
true,
'Class:'.$sClass.'/Attribute:'.$sAttCode,
AjaxTab::ENUM_TAB_PLACEHOLDER_DASHBOARD);
}
continue;
}
@@ -917,7 +925,7 @@ HTML
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
}
$sTip = utils::HtmlEntities($sTip);
$sSynchroIcon = '<img id="synchro_'.$sInputId.'" src="../images/transp-lock.png" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true" />';
$sSynchroIcon = '<div id="synchro_'.$sInputId.'" class="ibo-field--comments--synchro ibo-pill ibo-is-frozen" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true"><i class="fas fa-lock"></i></div>';
$sComments = $sSynchroIcon;
}
@@ -1036,9 +1044,32 @@ HTML
*/
public function DisplayDetails(WebPage $oPage, $bEditMode = false, $sMode = self::ENUM_OBJECT_MODE_VIEW)
{
// N°3786: As this can now be call recursively from the self::ReloadAndDisplay(), we need to make sure we don't fall into an infinite loop
static $bBlockReentrance = false;
$sClass = get_class($this);
$iKey = $this->GetKey();
if ($sMode === static::ENUM_OBJECT_MODE_VIEW)
{
// The concurrent access lock makes sense only for already existing objects
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
if ($LockEnabled)
{
$aLockInfo = iTopOwnershipLock::IsLocked($sClass, $iKey);
if ($aLockInfo['locked'] === true && $aLockInfo['owner']->GetKey() == UserRights::GetUserId() && $bBlockReentrance === false)
{
// If the object is locked by the current user, it's worth trying again, since
// the lock may be released by 'onunload' which is called AFTER loading the current page.
//$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId();
$bBlockReentrance = true;
self::ReloadAndDisplay($oPage, $this, array('operation' => 'details'));
return;
}
}
}
// Object's details
$oObjectDetails = ObjectFactory::MakeDetails($this);
@@ -1123,25 +1154,11 @@ HTML
*/
public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
{
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null;
$bViewLink = true;
$sSelectMode = 'none';
$iListId = $sTableId;
$sClassAlias = $oSet->GetClassAlias();
$sClassName = $oSet->GetClass();
$sZListName = 'list';
$aClassAliases = array($sClassAlias => $sClassName);
$aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
$oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId);
$oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList));
$oSettings->iDefaultPageSize = 0;
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink,
$aExtraParams);
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : utils::GetUniqueId();;
$aExtraParams['view_link'] = true;
$aExtraParams['select_mode'] = 'none';
return DataTableUIBlockFactory::MakeForObject($oPage, $sTableId, $oSet, $aExtraParams);
}
/**
@@ -2228,6 +2245,7 @@ EOF
// - Final config
$sConfigJS = json_encode($aConfig);
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
$oPage->add_ready_script(
@@ -2403,6 +2421,7 @@ HTML;
case 'CustomFields':
$sHTMLValue .= '<div id="'.$iId.'_console_form">';
$sHTMLValue .= '<div id="'.$iId.'_field_set">';
$sHTMLValue .= '</div></div>';
$sHTMLValue .= '<div>'.$sReloadSpan.'</div>'; // No validation span for this one: it does handle its own validation!
$sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" value=\"\"/>\n";
@@ -2765,7 +2784,7 @@ JS
case UR_ALLOWED_YES:
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_NEUTRAL);
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton);
break;
@@ -2824,11 +2843,10 @@ EOF
);
if (isset($aExtraParams['nbBulkObj'])) {
$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$sTitle = Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', $aExtraParams['nbBulkObj'], $sClass, $aExtraParams['nbBulkObj']);
$oTitle = TitleUIBlockFactory::MakeForPageWithIcon($sTitle, $sClassIcon, Title::DEFAULT_ICON_COVER_METHOD, false);
$oObjectDetails = PanelUIBlockFactory::MakeForClass(get_class($this), '');
$oObjectDetails->SetTitleBlock($oTitle);
$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$oObjectDetails = PanelUIBlockFactory::MakeForClass($sClass, $sTitle);
$oObjectDetails->SetIcon($sClassIcon);
$oToolbarButtons->AddCSSClass('ibo-toolbar--button');
} else {
$oObjectDetails = ObjectFactory::MakeDetails($this, $sMode);
@@ -2885,7 +2903,7 @@ EOF
$oPage->add($oAppContext->GetForForm());
// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=cancel&'.$oAppContext->GetForLink();
$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_form&class='.$sClass.'&'.$oAppContext->GetForLink();
$oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').on('click', function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)} );");
$iFieldsCount = count($aFieldsMap);
@@ -2936,6 +2954,93 @@ EOF
}
}
/**
* Select the derived class to create
* @param string $sClass
* @param \WebPage $oP
* @param \ApplicationContext $oAppContext
* @param array $aPossibleClasses
* @param array $aHiddenFields
*
* @return void
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0
*/
public static function DisplaySelectClassToCreate(string $sClass, WebPage $oP, ApplicationContext $oAppContext, array $aPossibleClasses, array $aHiddenFields)
{
$sClassLabel = MetaModel::GetName($sClass);
$sTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);
$oP->set_title($sTitle);
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, $sTitle)
->SetIcon($sClassIconUrl);
$oClassForm = FormUIBlockFactory::MakeStandard();
$oPanel->AddMainBlock($oClassForm);
$oClassForm->AddHtml($oAppContext->GetForForm())
->AddSubBlock(InputUIBlockFactory::MakeForHidden('checkSubclass', '0'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'new'));
foreach ($aHiddenFields as $sKey => $sValue) {
if (is_scalar($sValue)) {
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($sKey, $sValue));
}
}
$aDefaults = utils::ReadParam('default', array(), false, 'raw_data');
foreach ($aDefaults as $key => $value) {
if (is_array($value)) {
foreach ($value as $key2 => $value2) {
if (is_array($value2)) {
foreach ($value2 as $key3 => $value3) {
$sValue = utils::EscapeHtml($value3);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2][$key3]", $sValue));
}
} else {
$sValue = utils::EscapeHtml($value2);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2]", $sValue));
}
}
} else {
$sValue = utils::EscapeHtml($value);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key]", $sValue));
}
}
$oClassForm->AddSubBlock(self::DisplayBlockSelectClassToCreate($sClass, $sClassLabel, $aPossibleClasses));
$oP->AddSubBlock($oPanel);
}
/**
* @param string $sClassLabel
* @param array $aPossibleClasses
* @param string $sClass
*
* @return UIContentBlock
* @throws \CoreException
*/
public static function DisplayBlockSelectClassToCreate( string $sClass, string $sClassLabel,array $aPossibleClasses): UIContentBlock
{
$oBlock= UIContentBlockUIBlockFactory::MakeStandard();
$oBlock->AddSubBlock(HtmlFactory::MakeRaw(Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)));
$oSelect = SelectUIBlockFactory::MakeForSelect('class');
$oBlock->AddSubBlock($oSelect);
asort($aPossibleClasses);
foreach ($aPossibleClasses as $sClassName => $sClassLabel) {
$oSelect->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sClassLabel, ($sClassName == $sClass)));
}
$oToolbar = ToolbarUIBlockFactory::MakeForAction();
$oBlock->AddSubBlock($oToolbar);
$oToolbar->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), null, null, true));
return $oBlock;
}
/**
* @param \WebPage $oPage
* @param string $sClass
@@ -3095,56 +3200,54 @@ EOF
// The list of candidate fields is made of the ordered list of "details" attributes + other attributes
$aAttributes = array();
foreach($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode)
{
foreach ($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) {
$aAttributes[$sAttCode] = true;
}
foreach(MetaModel::GetAttributesList($sClass) as $sAttCode)
{
if (!array_key_exists($sAttCode, $aAttributes))
{
foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) {
if (!array_key_exists($sAttCode, $aAttributes)) {
$aAttributes[$sAttCode] = true;
}
}
// Order the fields based on their dependencies, set the fields for which there is only one possible value
// and perform this in the order of dependencies to avoid dead-ends
$aDeps = array();
foreach($aAttributes as $sAttCode => $trash)
{
foreach ($aAttributes as $sAttCode => $trash) {
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
}
$aList = $this->OrderDependentFields($aDeps);
foreach($aList as $sAttCode)
{
$bExistFieldToDisplay = false;
foreach ($aList as $sAttCode) {
// Consider only the "expected" fields for the target state
if (array_key_exists($sAttCode, $aExpectedAttributes))
{
if (array_key_exists($sAttCode, $aExpectedAttributes)) {
$iExpectCode = $aExpectedAttributes[$sAttCode];
// Prompt for an attribute if
// - the attribute must be changed or must be displayed to the user for confirmation
// - or the field is mandatory and currently empty
if (($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ||
(($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == '')))
{
(($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == ''))) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aArgs = array('this' => $this);
// If the field is mandatory, set it to the only possible value
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY))
{
if ($oAttDef->IsExternalKey())
{
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY)) {
if ($oAttDef->IsExternalKey()) {
/** @var DBObjectSet $oAllowedValues */
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '',
$this->Get($sAttCode));
if ($oAllowedValues->CountWithLimit(2) == 1)
{
if ($oAllowedValues->CountWithLimit(2) == 1) {
$oRemoteObj = $oAllowedValues->Fetch();
$this->Set($sAttCode, $oRemoteObj->GetKey());
}
}
else
} else
{
if ($oAttDef instanceof \AttributeCaseLog) {
// Add JS files for display caselog
// Dummy collapsible section created in order to get JS files
$oCollapsibleSection = new CollapsibleSection('');
foreach ($oCollapsibleSection->GetJsFilesUrlRecursively(true) as $sJSFile) {
$oPage->add_linked_script($sJSFile);
}
}
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (is_array($aAllowedValues) && count($aAllowedValues) == 1)
{
@@ -3179,8 +3282,7 @@ EOF
$bExcludeRawValue = false;
foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude)
{
if (is_a($sAttDefClass, $sAttDefClassToExclude, true))
{
if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) {
$bExcludeRawValue = true;
break;
}
@@ -3190,88 +3292,106 @@ EOF
$aDetails[] = $aAttrib;
$aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex;
$iFieldIndex++;
$bExistFieldToDisplay = true;
}
}
}
$oPage->set_title($sActionLabel);
$oPage->add(<<<HTML
<!-- Beginning of object-transition -->
<div class="object-transition" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode" data-object-current-state="$sCurrentState" data-object-target-state="$sTargetState">
if ($bExistFieldToDisplay) {
$oPage->set_title($sActionLabel);
$oPage->add(<<<HTML
<!-- Beginning of object-transition -->
<div class="object-transition" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode" data-object-current-state="$sCurrentState" data-object-target-state="$sTargetState">
HTML
);
);
// Page title and subtitles
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetRawName()));
if (!empty($sActionDetails)) {
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
}
// Page title and subtitles
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetRawName()));
if (!empty($sActionDetails)) {
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
}
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
$oPage->AddUiBlock($oFormContainer);
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
$oFormContainer->AddSubBlock($oForm);
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
$oPage->AddUiBlock($oFormContainer);
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
$oFormContainer->AddSubBlock($oForm);
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
// Note: Remove the table is we want fields to occupy the whole width of the container
$oForm->AddHtml('<table><tr><td>');
$oForm->AddHtml($oPage->GetDetails($aDetails));
$oForm->AddHtml('</td></tr></table>');
// Note: Remove the table if we want fields to occupy the whole width of the container
$sHtml = '<table><tr><td>';
$sHtml .= $oPage->GetDetails($aDetails);
$sHtml .= '</td></tr></table>';
$oAppContext = new ApplicationContext();
$oForm->AddHtml($oAppContext->GetForForm());
$oAppContext = new ApplicationContext();
$sHtml .= $oAppContext->GetForForm();
$oForm->AddHtml($sHtml);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel');
$oCancelButton->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
$oForm->AddSubBlock($oCancelButton);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel');
$oCancelButton->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
$oForm->AddSubBlock($oCancelButton);
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
$oForm->AddSubBlock($oSubmitButton);
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
$oForm->AddSubBlock($oSubmitButton);
$oPage->add(<<<HTML
<!-- End of object-transition -->
</div>
$oPage->add(<<<HTML
<!-- End of object-transition -->
</div>
HTML
);
);
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oPage->add_script(
<<<EOF
// Initializes the object once at the beginning of the page...
var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus');
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
oWizardHelper.SetFieldsCount($iFieldsCount);
$oPage->add_script(
<<<EOF
// Initializes the object once at the beginning of the page...
var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus');
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
oWizardHelper.SetFieldsCount($iFieldsCount);
EOF
);
$sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(
<<<EOF
// Starts the validation when the page is ready
CheckFields('apply_stimulus', false);
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
);
$sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(
<<<EOF
// Starts the validation when the page is ready
CheckFields('apply_stimulus', false);
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
EOF
);
);
if ($sOwnershipToken !== null)
{
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
if ($sOwnershipToken !== null) {
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
}
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
} else {
//we can directly apply the stimuli
$bApplyStimulus = $this->ApplyStimulus($sStimulus); // will write the object in the DB
if (!$bApplyStimulus) {
throw new ApplicationException(Dict::S('UI:FailedToApplyStimuli'));
} else {
if ($sOwnershipToken !== null) {
// Release the concurrent lock, if any
iTopOwnershipLock::ReleaseLock($sClass, $iKey, $sOwnershipToken);
}
return true;
}
}
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
return false;
}
public static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet)
@@ -3586,7 +3706,7 @@ HTML;
* @api
* @overwritable-hook
*
* @param $sFinalClass string The actual class of the objects for which to display the menu
* @param string $sFinalClass The actual class of the objects for which to display the menu
*
* @return array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the
* actions menu
@@ -4120,6 +4240,25 @@ HTML;
/**
* Updates the object from a given page argument
*
* The values are read from parameters (GET or POST, using {@see utils::ReadParam()}).
*
* To pass the arg, either add in HTML :
*
* ```html
* <input type="hidden" name="sArgName[attCode1]" value="...">
* <input type="hidden" name="sArgName[attCode2]" value="...">
* ```
*
* Or directly in the URL :
*
* ```php
* $aObjectArgs = ['attCode1' => ..., 'attCode2' => ...];
* $sQueryString = http_build_query(['sArgName' => $aObjectArgs]);
* ```
*
* @uses utils::ReadParam()
* @uses self::UpdateObjectFromArray
*/
public function UpdateObjectFromArg($sArgName, $aAttList = null, $aAttFlags = array())
{
@@ -4749,8 +4888,8 @@ HTML
$sTip = Dict::S('UI:Component:Field:BulkModify:UnknownValues:Tooltip');
$oDummyObj->Set($sAttCode, null);
$aComments[$sAttCode] = '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'"> ? </div>';
$aComments[$sAttCode] = '<div class="multi_values ibo-field--enable-bulk ibo-pill ibo-is-failure" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'">?';
$aComments[$sAttCode] .= '<input type="checkbox" class="ibo-field--enable-bulk--checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/></div>';
$sReadyScript .= 'ToggleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n";
} else {
$iCount = count($aValues[$sAttCode]);
@@ -4759,13 +4898,13 @@ HTML
reset($aValues[$sAttCode]);
$aKeys = array_keys($aValues[$sAttCode]);
$currValue = $aKeys[0]; // The only value is the first key
//echo "<p>current value for $sAttCode : $currValue</p>";
$oDummyObj->Set($sAttCode, $currValue);
$aComments[$sAttCode] = '';
$sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
$aComments[$sAttCode] .= '<input type="checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$sValueCheckbox .= '<input type="checkbox" class="ibo-field--enable-bulk--checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
}
$aComments[$sAttCode] .= '<div class="mono_value">1</div>';
$aComments[$sAttCode] .= '<div class="mono_value ibo-field--enable-bulk ibo-pill ibo-is-success">1'.$sValueCheckbox.'</div>';
} else {
// Non-homogeneous value
$aMultiValues = $aValues[$sAttCode];
@@ -4811,10 +4950,12 @@ HTML
$oDummyObj->Set($sAttCode, null);
}
$aComments[$sAttCode] = '';
$sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
$aComments[$sAttCode] .= '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$sValueCheckbox = '<input type="checkbox" class="ibo-field--enable-bulk--checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
}
$aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true">'.$iCount.'</div>';
$aComments[$sAttCode] .= '<div class="multi_values ibo-field--enable-bulk ibo-pill ibo-is-failure" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true">'.$iCount.$sValueCheckbox.'</div>';
}
$sReadyScript .= 'ToggleField('.(($iCount == 1) ? 'true' : 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n";
}
@@ -4834,10 +4975,6 @@ HTML
//$oStateAtt = MetaModel::GetAttributeDef($sClass, $sStateAttCode);
//$oDummyObj->Set($sStateAttCode, $oStateAtt->GetDefaultValue());
}
/*$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$sTitle = Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', $iAllowedCount, $sClass, $iAllowedCount);
$oTitle = TitleUIBlockFactory::MakeForPageWithIcon($sTitle, $sClassIcon, Title::DEFAULT_ICON_COVER_METHOD, false);
$oP->AddSubBlock($oTitle);*/
$oP->add("<div class=\"wizContainer\">\n");
$sDisableFields = json_encode($aExcludeAttributes);
@@ -4909,7 +5046,6 @@ EOF
$sHeaderTitle = Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), MetaModel::GetName($sClass));
$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$oTitle = TitleUIBlockFactory::MakeForPageWithIcon($sHeaderTitle, $sClassIcon, Title::DEFAULT_ICON_COVER_METHOD, false);
$oP->set_title(Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), $sClass));
@@ -4952,6 +5088,8 @@ EOF
$oTable->AddOption("bFullscreen", true);
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, '');
$oPanel->SetIcon($sClassIcon);
$oPanel->SetTitle($sHeaderTitle);
$oPanel->AddCSSClass('ibo-datatable-panel');
$oPanel->AddSubBlock($oTable);
@@ -4962,7 +5100,6 @@ EOF
$oForm = FormUIBlockFactory::MakeStandard('')->SetAction($sFormAction);
$oP->AddSubBlock($oForm);
$oForm->AddSubBlock($oPanel);
$oPanel->SetTitleBlock($oTitle);
$oAppContext = new ApplicationContext();
$oP->add($oAppContext->GetForForm());
@@ -4993,7 +5130,6 @@ EOF
}
}
} else {
$oP->AddUiBlock($oTitle);
$oP->AddUiBlock($oPanel);
$oP->AddSubBlock(ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:Button:Done')))->SetOnClickJsCode("window.location.href='$sCancelUrl'")->AddCSSClass('mt-5');
}
@@ -5111,19 +5247,21 @@ EOF
}
$iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects);
$sImpactedTableTitle = '';
$sImpactedTableSubtitle = '';
if ($iImpactedIndirectly > 0)
{
if (count($aObjects) == 1)
{
$oObj = $aObjects[0];
$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly,
$oObj->GetName()));
$sImpactedTableTitle = Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly,
$oObj->GetName());
}
else
{
$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iImpactedIndirectly));
$sImpactedTableTitle = Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iImpactedIndirectly);
}
$oP->p(Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity'));
$sImpactedTableSubtitle = Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity');
}
if (($iImpactedIndirectly > 0) || $oDeletionPlan->FoundStopper())
@@ -5135,7 +5273,11 @@ EOF
'label' => 'Consequence',
'description' => Dict::S('UI:Delete:Consequence+'),
);
$oP->AddSubBlock(DataTableUIBlockFactory::MakeForForm(preg_replace('/[^a-zA-Z0-9_-]/', '', uniqid('form_', true)), $aDisplayConfig, $aDisplayData));
$oBlock = PanelUIBlockFactory::MakeNeutral($sImpactedTableTitle, $sImpactedTableSubtitle);
$oDataTable = DataTableUIBlockFactory::MakeForForm(utils::Sanitize(uniqid('form_', true), '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER), $aDisplayConfig, $aDisplayData);
$oBlock->AddSubBlock($oDataTable);
$oP->AddUiBlock($oBlock);
}
if ($oDeletionPlan->FoundStopper()) {
@@ -5166,7 +5308,6 @@ EOF
$sSubtitle = Dict::Format('UI:Delect:Confirm_Count_ObjectsOf_Class', count($aObjects),
MetaModel::GetName($sClass));
}
$oP->AddUiBlock(TitleUIBlockFactory::MakeStandard(new Html($sSubtitle)));
foreach ($aObjects as $oObj) {
$aKeys[] = $oObj->GetKey();
@@ -5176,7 +5317,14 @@ EOF
$oSet = new CMDBobjectSet($oFilter);
$oDisplaySet = UIContentBlockUIBlockFactory::MakeStandard("0");
$oP->AddSubBlock($oDisplaySet);
$oDisplaySet->AddSubBlock(CMDBAbstractObject::GetDisplaySetBlock($oP, $oSet, array('display_limit' => false, 'menu' => false)));
$oDisplaySet->AddSubBlock(CMDBAbstractObject::GetDisplaySetBlock($oP, $oSet, array(
'display_limit' => false,
'menu' => false,
'surround_with_panel' => true,
'panel_title' => $sSubtitle,
'panel_icon' => MetaModel::GetClassIcon($sClass, false),
'panel_class' => $sClass,
)));
$oForm = FormUIBlockFactory::MakeStandard('');
$oP->AddSubBlock($oForm);
@@ -5275,18 +5423,16 @@ EOF
$sSubtitle = Dict::Format('UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class', count($aObjects),
MetaModel::GetName($sClass));
}
$oP->AddUiBlock(TitleUIBlockFactory::MakeForPage($sSubtitle));
$aDisplayConfig = array();
$aDisplayConfig['class'] = array('label' => 'Class', 'description' => '');
$aDisplayConfig['object'] = array('label' => 'Object', 'description' => '');
$aDisplayConfig['consequence'] = array('label' => 'Done', 'description' => Dict::S('UI:Delete:Done+'));
$oResultsPanel = PanelUIBlockFactory::MakeForInformation('');
$oResultsPanel = PanelUIBlockFactory::MakeForInformation($sSubtitle);
$oP->AddUiBlock($oResultsPanel);
$oResultsPanel->AddSubBlock(
DataTableUIBlockFactory::MakeForStaticData('', $aDisplayConfig, $aDisplayData)
);
$oDatatable = DataTableUIBlockFactory::MakeForStaticData('', $aDisplayConfig, $aDisplayData);
$oResultsPanel->AddSubBlock($oDatatable);
}
}
}

View File

@@ -543,7 +543,7 @@ EOF
if ($bFromDasboardPage) {
$sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle));
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\">{$sTitleForHTML}</div>";
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\" title=\"{$sTitleForHTML}\">{$sTitleForHTML}</div>";
if ($oPage instanceof iTopWebPage) {
$oTopBar = $oPage->GetTopBarLayout();
$oToolbar = ToolbarUIBlockFactory::MakeStandard();
@@ -552,7 +552,7 @@ EOF
$oToolbar->AddHtml($sHtml);
} else {
$oPage->add_script(<<<JS
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML");
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML").attr("title", "$sTitleForHTML");
JS
);
}

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletContainer;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
@@ -202,6 +203,24 @@ abstract class Dashlet
$this->OnUpdate();
}
/**
* @return array Rel. path to the app. root of the JS files required by the dashlet
* @since 3.0.0
*/
public function GetJSFilesRelPaths(): array
{
return [];
}
/**
* @return array Rel. path to the app. root of the CSS files required by the dashlet
* @since 3.0.0
*/
public function GetCSSFilesRelPaths(): array
{
return [];
}
/**
* @param \WebPage $oPage
* @param bool $bEditMode
@@ -224,6 +243,9 @@ abstract class Dashlet
$oDashletContainer->AddCSSClasses($this->aCSSClasses);
}
$oDashletContainer->AddMultipleJsFilesRelPaths($this->GetJSFilesRelPaths());
$oDashletContainer->AddMultipleCssFilesRelPaths($this->GetCSSFilesRelPaths());
try {
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime') {
$oBlock = $this->Render($oPage, $bEditMode, $aExtraParams);
@@ -604,8 +626,7 @@ class DashletUnknown extends Dashlet
$oDashletContainer = new DashletContainer(null, ['dashlet-content']);
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div><div class="dashlet-ukn-text">'.$sExplainText.'</div>');
return $oDashletContainer;
}
@@ -624,8 +645,7 @@ class DashletUnknown extends Dashlet
$oDashletContainer = new DashletContainer(null, ['dashlet-content']);
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div><div class="dashlet-ukn-text">'.$sExplainText.'</div>');
return $oDashletContainer;
}
@@ -1272,9 +1292,9 @@ abstract class DashletGroupBy extends Dashlet
$aExtraParams["surround_with_panel"] = true;
$aExtraParams["panel_title"] = Dict::S($sTitle);
$aExtraParams["panel_class"] = $sClass;
$oPanel = $oBlock->GetDisplay($oPage, $sType, array_merge($aExtraParams, $aParams));
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
if ($bEditMode) {
$oPanel->AddHtml('<div class="dashlet-blocker"></div>');
$oPanel->AddHtml('<div class="ibo-dashlet-blocker dashlet-blocker"></div>');
}
return $oPanel;
@@ -1676,6 +1696,28 @@ class DashletGroupByPie extends DashletGroupBy
);
}
/**
* @inheritDoc
*/
public function GetJSFilesRelPaths(): array
{
return array_merge(
parent::GetJSFilesRelPaths(),
WebResourcesHelper::GetJSFilesRelPathsForC3JS()
);
}
/**
* @inheritDoc
*/
public function GetCSSFilesRelPaths(): array
{
return array_merge(
parent::GetCSSFilesRelPaths(),
WebResourcesHelper::GetCSSFilesRelPathsForC3JS()
);
}
/**
* @inheritdoc
*/

View File

@@ -18,7 +18,6 @@ use Combodo\iTop\Application\UI\Base\Component\Toolbar\Separator\ToolbarSeparato
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockWithJSRefreshCallback;
use Combodo\iTop\Application\UI\DisplayBlock\BlockChart\BlockChart;
use Combodo\iTop\Application\UI\DisplayBlock\BlockChartAjaxBars\BlockChartAjaxBars;
@@ -173,6 +172,8 @@ class DisplayBlock
/**positive or negative*/
'max_height',
/** string Max. height of the list, if not specified will occupy all the available height no matter the pagination */
'localize_values',
/** param for export.php */
], DataTableUIBlockFactory::GetAllowedParams()),
'list_search' => array_merge([
'update_history',
@@ -191,6 +192,8 @@ class DisplayBlock
/** string */
'open',
/** bool open by default the search */
'submit_on_load',
/** bool submit the search on loading page */
'class', /** class name */
'search_header_force_dropdown', /** Html for <select> to choose the class to search */
'this',
@@ -200,6 +203,8 @@ class DisplayBlock
/** string search root class */
'open',
/** bool open the search panel by default */
'submit_on_load',
/** bool submit the search on loading page */
'result_list_outer_selector',
/** string js selector of the search result display */
'search_header_force_dropdown',
@@ -265,6 +270,8 @@ class DisplayBlock
'panel_title',
/** string class for panel block style */
'panel_class',
/** string class for panel block style */
'panel_icon',
];
if (isset($aAllowedParams[$sStyle])) {
@@ -1005,9 +1012,10 @@ JS
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aValues = $oAttDef->GetAllowedValues();
foreach ($aStates as $sStateValue) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aStateLabels[$sStateValue] = $oAttDef->GetAsPlainText($sStateValue);
$aStateLabels[$sStateValue] = $aValues[$sStateValue];
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
? $aCountsQueryResults[$sStateValue]
: 0;
@@ -1035,8 +1043,7 @@ JS
$sCountLabel = $aCount['label'];
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
->SetTooltip($sStateLabel)
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span>")
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">$sStateLabel</span>");
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span><span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">$sStateLabel</span>");
if ($sHyperlink != '-') {
$oPill->SetUrl($sHyperlink);
}
@@ -1191,6 +1198,9 @@ JS
$sTitle = Dict::Format($sFormat, $iTotalCount);
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
$oBlock->AddSubTitleBlock(new Html($sTitle));
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oBlock->SetIcon($aExtraParams["panel_icon"]);
}
$oDataTable = DataTableUIBlockFactory::MakeForStaticData("", $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
$oBlock->AddSubBlock($oDataTable);
} else {
@@ -1207,6 +1217,9 @@ JS
}
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oBlock->SetIcon($aExtraParams["panel_icon"]);
}
$oBlock->AddSubBlock(new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>'));
} else {
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
@@ -1342,6 +1355,9 @@ JS
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
@@ -1539,6 +1555,9 @@ JS
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
@@ -1627,6 +1646,9 @@ JS
}
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
@@ -2203,9 +2225,9 @@ class MenuBlock extends DisplayBlock
// Extract favorite actions from their menus
$aFavoriteRegularActions = [];
$aFavoriteTransitionActions = [];
$aCallSpec = [$sClass, 'GetShortcutActions'];
if (is_callable($aCallSpec)) {
$aShortcutActions = call_user_func($aCallSpec, $sClass);
if (is_callable([$sClass, 'GetShortcutActions'])) {
/** @var cmdbAbstractObject $sClass */
$aShortcutActions = $sClass::GetShortcutActions($sClass);
foreach ($aShortcutActions as $key) {
// Regular actions
if (isset($aRegularActions[$key])) {
@@ -2300,6 +2322,12 @@ class MenuBlock extends DisplayBlock
$sIconClass = 'fas fa-share-alt';
$sLabel = '';
break;
default:
if (isset($aAction['icon_class']) && (strlen($aAction['icon_class']) > 0)) {
$sIconClass = $aAction['icon_class'];
$sLabel = '';
}
}
$sTarget = isset($aAction['target']) ? $aAction['target'] : '';
@@ -2314,7 +2342,7 @@ class MenuBlock extends DisplayBlock
// - Refresh
if ($sRefreshAction != '') {
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
$oActionButton->SetIconClass('fas fa-sync')
$oActionButton->SetIconClass('fas fa-sync-alt')
->SetOnClickJsCode($sRefreshAction)
->SetTooltip(Dict::S('UI:Button:Refresh'))
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);

View File

@@ -102,12 +102,20 @@ class DesignerForm
$sReturn .= '<fieldset>';
$sReturn .= '<legend>'.$sLabel.'</legend>';
}
/** @var \DesignerFormField $oField */
foreach($aFields as $oField) {
$aRow = $oField->Render($oP, $sFormId);
if ($oField->IsVisible()) {
$sValidation = '<span class="prop_apply ibo-prop--apply">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
$sValidation = '<span class="prop_apply ibo-prop--apply ibo-button ibo-is-alternative">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
$sField = $aRow['value'].$sValidation;
$aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
$aDetails[] = array(
'label' => $aRow['label'],
'value' => $sField,
'attcode' => $oField->GetCode(),
'attlabel' => $aRow['label'],
'inputid' => $this->GetFieldId($oField->GetCode()),
'inputtype' => $oField->GetInputType(),
);
} else {
$sHiddenFields .= $aRow['value'];
}
@@ -221,8 +229,8 @@ class DesignerForm
$aRow = $oField->Render($oP, $sFormId, 'property');
if ($oField->IsVisible()) {
$sFieldId = $this->GetFieldId($oField->GetCode());
$sValidation = $this->GetValidationArea($sFieldId, '<span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></span>');
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></span></td>'
$sValidation = $this->GetValidationArea($sFieldId, '<div class="ibo-button ibo-is-alternative ibo-is-success" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></div>');
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><div class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-undo"></i></div></span></td>'
.$this->EndRow();
if (is_null($aRow['label'])) {
@@ -707,6 +715,19 @@ class DesignerFormField
$this->aWidgetExtraParams = array();
}
/**
* Important, for now we use constants from the \cmdbAbstractObject class, introducing a coupling that should not exist.
* This has been traced under N°4241 and will be discussed during the next modernization batch.
*
* @return string|null Return the input type of the field
* @see \cmdbAbstractObject::ENUM_INPUT_TYPE_XXX
* @since 3.0.0
*/
public function GetInputType(): ?string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_SINGLE_INPUT;
}
/**
* @return string
*/
@@ -1047,6 +1068,14 @@ class DesignerLongTextField extends DesignerTextField
$this->aCSSClasses[] = 'ibo-input-text';
}
/**
* @inheritDoc
*/
public function GetInputType(): string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_TEXTAREA;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
@@ -1175,9 +1204,27 @@ class DesignerComboField extends DesignerFormField
$this->bAutoApply = true;
$this->bSorted = true; // Sorted by default
}
public function SetAllowedValues($aAllowedValues)
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
if ($this->bMultipleSelection) {
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_MULTIPLE_CHOICES;
}
else {
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW;
}
}
public function SetAllowedValues(?array $aAllowedValues)
{
// Make sure to have an actual array for values
if (is_null($aAllowedValues)) {
$aAllowedValues = [];
}
$this->aAllowedValues = $aAllowedValues;
}
@@ -1254,11 +1301,11 @@ class DesignerComboField extends DesignerFormField
{
if ($this->bMultipleSelection)
{
$sHtml = "<select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
$sHtml = "<span><select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
}
else
{
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\">";
$sHtml = "<span class=\"ibo-input-select-wrapper\"><select $sCSSClasses id=\"$sId\" name=\"$sName\">";
if ($this->sNullLabel != '')
{
$sHtml .= "<option value=\"\">".$this->sNullLabel."</option>";
@@ -1278,7 +1325,7 @@ class DesignerComboField extends DesignerFormField
$sHtmlValue = str_replace(' ', '&nbsp;', htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8'));
$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>$sHtmlValue</option>";
}
$sHtml .= "</select>";
$sHtml .= "</select></span>";
if ($this->bOtherChoices)
{
$sHtml .= '<br/><input type="checkbox" id="other_chk_'.$sId.'"><label for="other_chk_'.$sId.'">&nbsp;Other:</label>&nbsp;<input type="text" id="other_'.$sId.'" name="other_'.$sName.'" size="30"/>';
@@ -1311,6 +1358,14 @@ class DesignerBooleanField extends DesignerFormField
$this->bAutoApply = true;
$this->aCSSClasses[] = 'ibo-input-checkbox';
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_CHECKBOX;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
@@ -1370,6 +1425,14 @@ class DesignerHiddenField extends DesignerFormField
{
parent::__construct($sCode, $sLabel, $defaultValue);
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function IsVisible()
{
@@ -1397,6 +1460,14 @@ class DesignerIconSelectionField extends DesignerFormField
$this->bAutoApply = true;
$this->sUploadUrl = null;
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
}
public function SetAllowedValues($aAllowedValues)
{
@@ -1432,7 +1503,7 @@ class DesignerIconSelectionField extends DesignerFormField
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
if (!$this->IsReadOnly()) {
$sDefaultValue = ($this->defaultValue !== '') ? $this->defaultValue : $this->aAllowedValues[$idx]['value'];
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/>";
$sValue = "<span class=\"ibo-input-select-wrapper\"><input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/></span>";
$oP->add_ready_script(
<<<EOF
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
@@ -1566,6 +1637,14 @@ class DesignerSortableField extends DesignerFormField
parent::__construct($sCode, $sLabel, $defaultValue);
$this->aAllowedValues = array();
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function SetAllowedValues($aAllowedValues)
{
@@ -1605,6 +1684,14 @@ class DesignerFormSelectorField extends DesignerFormField
$this->aCSSClasses[] = 'ibo-input-select';
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function IsSorted()
{
return $this->bSorted;
@@ -1671,18 +1758,18 @@ class DesignerFormSelectorField extends DesignerFormField
}
$sHtml = "<span $sCSSClasses>".$sDisplayValue.$sHiddenValue."</span>";
} else {
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
$sHtml = "<span class=\"ibo-input-select-wrapper\"><select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
foreach ($this->aSubForms as $iKey => $aFormData) {
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
$sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
$sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
}
$sHtml .= "</select>";
$sHtml .= "</select></span>";
}
if ($sRenderMode == 'property') {
$sHtml .= '</td><td class="prop_icon prop_apply ibo-prop--apply"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></span></td><td class="prop_icon prop_cancel ibo-prop--cancel"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></span></td></tr>';
$sHtml .= '</td><td class="prop_icon prop_apply ibo-prop--apply"><span><button class="ibo-button ibo-is-alternative ibo-is-success" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></button></span></td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><button class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></button></span></td></tr>';
}
foreach ($this->aSubForms as $sKey => $aFormData) {
$sId = $this->oForm->GetFieldId($this->sCode);
@@ -1790,6 +1877,14 @@ class DesignerSubFormField extends DesignerFormField
parent::__construct('', $sLabel, '');
$this->oSubForm = $oSubForm;
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
@@ -1834,6 +1929,14 @@ class DesignerStaticTextField extends DesignerFormField
parent::__construct($sCode, $sLabel, $defaultValue);
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
return array('label' => $this->sLabel, 'value' => $this->defaultValue);

View File

@@ -1,4 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginBasic
*
@@ -20,19 +23,19 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnModeDetection(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']))
if (!Session::IsSet('login_mode'))
{
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
{
$_SESSION['login_mode'] = 'basic';
Session::Set('login_mode', 'basic');
}
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
{
$_SESSION['login_mode'] = 'basic';
Session::Set('login_mode', 'basic');
}
elseif (isset($_SERVER['PHP_AUTH_USER']))
{
$_SESSION['login_mode'] = 'basic';
Session::Set('login_mode', 'basic');
}
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -40,10 +43,10 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) || $_SESSION['login_mode'] == 'basic')
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'basic')
{
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
$_SESSION['login_temp_auth_user'] = $sAuthUser;
list($sAuthUser) = $this->GetAuthUserAndPassword();
Session::Set('login_temp_auth_user', $sAuthUser);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -51,10 +54,10 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -65,17 +68,17 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
list($sAuthUser) = $this->GetAuthUserAndPassword();
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
LoginWebPage::HTTP401Error();
}
@@ -84,9 +87,9 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
$_SESSION['can_logoff'] = true;
Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -4,6 +4,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginDefaultBefore
*/
@@ -23,7 +25,7 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
{
$iErrorCode = LoginWebPage::EXIT_CODE_OK;
unset($_SESSION['login_temp_auth_user']);
Session::Unset('login_temp_auth_user');
// Check if proposed login mode is present and allowed
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
@@ -32,11 +34,11 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
if ($index !== false)
{
// Force login mode
$_SESSION['login_mode'] = $sProposedLoginMode;
Session::Set('login_mode', $sProposedLoginMode);
}
else
{
unset($_SESSION['login_mode']);
Session::Unset('login_mode');
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -91,7 +93,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnCredentialsOk(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']))
if (!Session::IsSet('login_mode'))
{
// If no plugin validated the user, exit
self::ResetLoginSession();
@@ -110,7 +112,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnConnected(&$iErrorCode)
{
unset($_SESSION['login_temp_auth_user']);
Session::Unset('login_temp_auth_user');
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -118,11 +120,11 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
private static function ResetLoginSession()
{
LoginWebPage::ResetSession();
foreach (array_keys($_SESSION) as $sKey)
foreach (Session::ListVariables() as $sKey)
{
if (utils::StartsWith($sKey, 'login_'))
{
unset($_SESSION[$sKey]);
Session::Unset($sKey);
}
}
}

View File

@@ -1,5 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginExternal
*
@@ -22,12 +24,12 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnModeDetection(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']))
if (!Session::IsSet('login_mode'))
{
$sAuthUser = $this->GetAuthUser();
if ($sAuthUser && (strlen($sAuthUser) > 0))
{
$_SESSION['login_mode'] = 'external';
Session::Set('login_mode', 'external');
}
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -35,10 +37,10 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
$sAuthUser = $this->GetAuthUser();
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -49,19 +51,19 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
$sAuthUser = $this->GetAuthUser();
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
$_SESSION['can_logoff'] = false;
Session::Set('can_logoff', false);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -69,7 +71,7 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
LoginWebPage::HTTP401Error();
}

View File

@@ -5,6 +5,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginForm
*
@@ -29,8 +31,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnReadCredentials(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'form'))
{
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') {
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd))
@@ -50,9 +51,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$this->bForceFormOnError = false;
exit;
}
$_SESSION['login_temp_auth_user'] = $sAuthUser;
$_SESSION['login_mode'] = 'form';
Session::Set('login_temp_auth_user', $sAuthUser);
Session::Set('login_mode', 'form');
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -62,11 +62,11 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -80,19 +80,19 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
}
else
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
}
// Store 'auth_user' in session for further use
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -102,7 +102,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
$this->bForceFormOnError = true;
}
@@ -114,9 +114,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
$_SESSION['can_logoff'] = true;
Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -7,6 +7,7 @@
*/
use Combodo\iTop\Application\Branding;
use Combodo\iTop\TwigExtension;
/**
@@ -238,16 +239,9 @@ class LoginTwigRenderer
public function GetDefaultVars()
{
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
}
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$aVars = array(
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),

View File

@@ -1,5 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginURL
*
@@ -26,13 +28,13 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnModeDetection(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) && !$this->bErrorOccurred)
if (!Session::IsSet('login_mode') && !$this->bErrorOccurred)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!empty($sAuthUser) && !empty($sAuthPwd))
{
$_SESSION['login_mode'] = 'url';
Session::Set('login_mode', 'url');
}
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -40,20 +42,20 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$_SESSION['login_temp_auth_user'] = utils::ReadParam('auth_user', '', false, 'raw_data');
Session::Set('login_temp_auth_user', utils::ReadParam('auth_user', '', false, 'raw_data'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -64,17 +66,17 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$this->bErrorOccurred = true;
}
@@ -83,9 +85,9 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$_SESSION['can_logoff'] = true;
Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -24,6 +24,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\Helper\Session;
/**
* Web page used for displaying the login form
*/
@@ -139,16 +142,9 @@ class LoginWebPage extends NiceWebPage
public function DisplayLoginHeader($bMainAppLogo = false)
{
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
}
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES,
self::PAGES_CHARSET)."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
}
@@ -392,11 +388,11 @@ class LoginWebPage extends NiceWebPage
public static function ResetSession()
{
// Unset all of the session variables.
unset($_SESSION['auth_user']);
unset($_SESSION['login_state']);
unset($_SESSION['can_logoff']);
unset($_SESSION['archive_mode']);
unset($_SESSION['impersonate_user']);
Session::Unset('auth_user');
Session::Unset('login_state');
Session::Unset('can_logoff');
Session::Unset('archive_mode');
Session::Unset('impersonate_user');
UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
@@ -442,11 +438,11 @@ class LoginWebPage extends NiceWebPage
}
$bLoginDebug = MetaModel::GetConfig()->Get('login_debug');
if (!isset($_SESSION['login_state']) || ($_SESSION['login_state'] == self::LOGIN_STATE_ERROR))
if (Session::Get('login_state') == self::LOGIN_STATE_ERROR)
{
$_SESSION['login_state'] = self::LOGIN_STATE_START;
Session::Set('login_state', self::LOGIN_STATE_START);
}
$sLoginState = $_SESSION['login_state'];
$sLoginState = Session::Get('login_state');
$sSessionLog = '';
if ($bLoginDebug)
@@ -500,7 +496,7 @@ class LoginWebPage extends NiceWebPage
// Every plugin has nothing else to do in this state, go forward
$sLoginState = self::AdvanceLoginFSMState($sLoginState);
$_SESSION['login_state'] = $sLoginState;
Session::Set('login_state', $sLoginState);
}
catch (Exception $e)
{
@@ -526,7 +522,7 @@ class LoginWebPage extends NiceWebPage
if ($bFilterWithMode)
{
$sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : '';
$sCurrentLoginMode = Session::Get('login_mode', '');
}
else
{
@@ -665,8 +661,8 @@ class LoginWebPage extends NiceWebPage
$oLog->DBInsertNoReload();
}
$_SESSION['auth_user'] = $sAuthUser;
$_SESSION['login_mode'] = $sLoginMode;
Session::Set('auth_user', $sAuthUser);
Session::Set('login_mode', $sLoginMode);
UserRights::_InitSessionCache();
}
@@ -681,10 +677,10 @@ class LoginWebPage extends NiceWebPage
*/
public static function CheckLoggedUser(&$iErrorCode)
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
// Already authenticated
$bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
$bRet = UserRights::Login(Session::Get('auth_user')); // Login & set the user's language
if ($bRet)
{
$iErrorCode = self::EXIT_CODE_OK;
@@ -712,11 +708,11 @@ class LoginWebPage extends NiceWebPage
public static function SetLoginModeAndReload($sNewLoginMode)
{
if (isset($_SESSION['login_mode']) && ($_SESSION['login_mode'] == $sNewLoginMode))
if (Session::Get('login_mode') == $sNewLoginMode)
{
return;
}
$_SESSION['login_mode'] = $sNewLoginMode;
Session::Set('login_mode', $sNewLoginMode);
self::HTTPReload();
}
@@ -829,9 +825,9 @@ class LoginWebPage extends NiceWebPage
{
CMDBObject::SetTrackOrigin('custom-extension');
$sInfo = 'External User provisioning';
if (isset($_SESSION['login_mode']))
if (Session::IsSet('login_mode'))
{
$sInfo .= " ({$_SESSION['login_mode']})";
$sInfo .= " (".Session::Get('login_mode').")";
}
CMDBObject::SetTrackInfo($sInfo);
@@ -883,9 +879,9 @@ class LoginWebPage extends NiceWebPage
{
CMDBObject::SetTrackOrigin('custom-extension');
$sInfo = 'External User provisioning';
if (isset($_SESSION['login_mode']))
if (Session::IsSet('login_mode'))
{
$sInfo .= " ({$_SESSION['login_mode']})";
$sInfo .= " (".Session::Get('login_mode').")";
}
CMDBObject::SetTrackInfo($sInfo);
@@ -924,9 +920,9 @@ class LoginWebPage extends NiceWebPage
// Now synchronize the profiles
$sOrigin = 'External User provisioning';
if (isset($_SESSION['login_mode']))
if (Session::IsSet('login_mode'))
{
$sOrigin .= " ({$_SESSION['login_mode']})";
$sOrigin .= " (".Session::Get('login_mode').")";
}
$aExistingProfiles = self::SynchronizeProfiles($oUser, $aProfiles, $sOrigin);
if ($oUser->IsModified())
@@ -1011,7 +1007,6 @@ class LoginWebPage extends NiceWebPage
$sMessage = self::HandleOperations($operation); // May exit directly
$iRet = self::Login($iOnExit);
if ($iRet == self::EXIT_CODE_OK)
{
if ($bMustBeAdmin && !UserRights::IsAdministrator())
@@ -1091,19 +1086,23 @@ class LoginWebPage extends NiceWebPage
}
else if ($operation == 'change_pwd')
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
$sIssue = Session::Get('pwd_issue');
Session::Unset('pwd_issue');
$bFailedLogin = ($sIssue != null); // Force the "failed login" flag to display the "issue" message
UserRights::Login($sAuthUser); // Set the user's language
$oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm();
$oPage->DisplayChangePwdForm($bFailedLogin, $sIssue);
$oPage->output();
exit;
}
}
else if ($operation == 'check_pwd_policy')
{
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
UserRights::Login($sAuthUser); // Set the user's language
$aPwdMap = array();
@@ -1121,9 +1120,9 @@ class LoginWebPage extends NiceWebPage
}
if ($operation == 'do_change_pwd')
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
UserRights::Login($sAuthUser); // Set the user's language
$sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');

View File

@@ -4,6 +4,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
require_once(APPROOT.'/application/utils.inc.php');
@@ -58,6 +59,10 @@ class ApplicationMenu
* @var array
*/
static $aMenusIndex = array();
/**
* @var array
*/
static $aMenusById = [];
/**
* @var string
*/
@@ -166,6 +171,7 @@ class ApplicationMenu
$aBacktrace = debug_backtrace();
$sFile = isset($aBacktrace[2]["file"]) ? $aBacktrace[2]["file"] : $aBacktrace[1]["file"];
self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
self::$aMenusById[$oMenuNode->GetMenuId()] = $index;
}
else
{
@@ -506,17 +512,11 @@ EOF
*/
public static function GetMenuIndexById($sTitle)
{
$index = -1;
/** @var MenuNode[] $aMenu */
foreach(self::$aMenusIndex as $aMenu)
{
if ($aMenu['node']->GetMenuId() == $sTitle)
{
$index = $aMenu['node']->GetIndex();
break;
}
if (isset(self::$aMenusById[$sTitle])) {
return self::$aMenusById[$sTitle];
}
return $index;
return -1;
}
/**
@@ -715,9 +715,10 @@ abstract class MenuNode
{
// Count the entries up to 99
$oSearch = DBSearch::FromOQL($sOQL);
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
DBSearchHelper::AddContextFilter($oSearch);
$oSet = new DBObjectSet($oSearch);
$iCount = $oSet->CountWithLimit(99);
if ($iCount > 99) {
@@ -1125,15 +1126,13 @@ class OQLMenuNode extends MenuNode
{
$sUsageId = utils::GetSafeId($sUsageId);
$oSearch = DBObjectSearch::FromOQL($sOql);
//$sIcon = MetaModel::GetClassIcon($oSearch->GetClass(), false);
if ($bSearchPane) {
$aParams = array_merge(array('open' => $bSearchOpen, 'table_id' => $sUsageId), $aExtraParams);
$aParams = array_merge(['open' => $bSearchOpen, 'table_id' => $sUsageId, 'submit_on_load' => false], $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0);
}
//$oPage->add("<p class=\"page-header\">$sIcon ".utils::HtmlEntities(Dict::S($sTitle))."</p>");
$oPage->add("<div class='sf_results_area' data-target='search_results'>");
$oTitle = TitleUIBlockFactory::MakeForPage($sTitle);
$oPage->AddUiBlock($oTitle);
@@ -1209,7 +1208,8 @@ class SearchMenuNode extends MenuNode
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
$oSearch = new DBObjectSearch($this->sClass);
$aParams = array_merge(array('table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
$sUsageId = 'Menu_'.utils::GetSafeId($this->GetMenuId());
$aParams = array_merge(array('table_id' =>$sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0);
}
@@ -1415,6 +1415,8 @@ class DashboardMenuNode extends MenuNode
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
WebResourcesHelper::EnableC3JSToWebPage($oPage);
$sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier');
$oPage->add('<div id="'.$sDivId.'" class="ibo-dashboard" data-role="ibo-dashboard">');
$aExtraParams['dashboard_div_id'] = $sDivId;

View File

@@ -15,9 +15,12 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\Session;
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/contexttag.class.inc.php');
require_once(APPROOT.'/core/kpi.class.inc.php');
/**
@@ -27,6 +30,9 @@ require_once(APPROOT.'/core/contexttag.class.inc.php');
* @license http://opensource.org/licenses/AGPL-3.0
*/
ExecutionKPI::EnableDuration(1);
ExecutionKPI::EnableMemory(1);
// This storage is freed on error (case of allowed memory exhausted)
$sReservedMemory = str_repeat('*', 1024 * 1024);
register_shutdown_function(function()
@@ -62,14 +68,16 @@ register_shutdown_function(function()
}
}
});
$oKPI = new ExecutionKPI();
Session::Start();
Session::WriteClose();
$oKPI->ComputeAndReport("Session Start");
session_name('itop-'.md5(APPROOT));
session_start();
$sSwitchEnv = utils::ReadParam('switch_env', null);
$bAllowCache = true;
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)) && isset($_SESSION['itop_env']) && ($_SESSION['itop_env'] !== $sSwitchEnv))
if (($sSwitchEnv != null) && file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE) &&( Session::Get('itop_env') !== $sSwitchEnv))
{
$_SESSION['itop_env'] = $sSwitchEnv;
Session::Set('itop_env', $sSwitchEnv);
$sEnv = $sSwitchEnv;
$bAllowCache = false;
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
@@ -85,14 +93,14 @@ if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FI
}
// TODO: reset the credentials as well ??
}
else if (isset($_SESSION['itop_env']))
else if (Session::IsSet('itop_env'))
{
$sEnv = $_SESSION['itop_env'];
$sEnv = Session::Get('itop_env');
}
else
{
$sEnv = ITOP_DEFAULT_ENV;
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
Session::Set('itop_env', ITOP_DEFAULT_ENV);
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);

View File

@@ -27,6 +27,7 @@ class ThemeHandler
{
const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
/** @var \CompileCSSService */
private static $oCompileCSSService;
public static function GetAppRootWithSlashes()
@@ -905,7 +906,8 @@ CSS;
foreach($aImportsPaths as $sPath)
{
$sFilePath = $sPath.'/'.$sFileURI;
$sAlterableFileURI = $sFileURI;
$sFilePath = $sPath.'/'.$sAlterableFileURI;
$sImportedFile = realpath($sFilePath);
if ($sImportedFile === false){
// Handle shortcut syntax : @import "typo" ;
@@ -913,7 +915,7 @@ CSS;
$sFilePath2 = "$sFilePath.scss";
$sImportedFile = realpath($sFilePath2);
if ($sImportedFile){
self::FindStylesheetFile("$sFileURI.scss", [ $sPath ], $oFindStylesheetObject, $bImports);
self::FindStylesheetFile("$sAlterableFileURI.scss", [ $sPath ], $oFindStylesheetObject, $bImports);
$sImportedFile = false;
}
}
@@ -923,7 +925,7 @@ CSS;
// file matched: _typo.scss
$sShortCut = substr($sFilePath, strrpos($sFilePath, '/') + 1);
$sFilePath = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sFilePath);
$sFileURI = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sFileURI);
$sAlterableFileURI = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sAlterableFileURI);
$sImportedFile = realpath($sFilePath);
}
@@ -931,14 +933,14 @@ CSS;
&& (!$oFindStylesheetObject->AlreadyFetched($sImportedFile)))
{
if ($bImports){
$oFindStylesheetObject->AddImport($sFileURI, $sImportedFile);
$oFindStylesheetObject->AddImport($sAlterableFileURI, $sImportedFile);
}else{
$oFindStylesheetObject->AddStylesheet($sFileURI, $sImportedFile);
$oFindStylesheetObject->AddStylesheet($sAlterableFileURI, $sImportedFile);
}
$oFindStylesheetObject->UpdateLastModified($sImportedFile);
//Regexp matching on all included scss files : @import 'XXX.scss';
$sDirUri = dirname($sFileURI);
$sDirUri = dirname($sAlterableFileURI);
preg_match_all('/@import \s*[\"\']([^\"\']*)\s*[\"\']\s*;/', file_get_contents($sImportedFile), $aMatches);
if ( (is_array($aMatches)) && (count($aMatches)!==0) ){
foreach ($aMatches[1] as $sImportedFile){

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\Session;
/**
* This class records the pending "transactions" corresponding to forms that have not been
@@ -100,7 +101,8 @@ class privUITransaction
/**
* The original (and by default) mechanism for storing transaction information
* as an array in the $_SESSION variable
* as an array in the _SESSION variable
* @see \Combodo\iTop\Application\Helper\Session
*
*/
class privUITransactionSession
@@ -112,15 +114,15 @@ class privUITransactionSession
*/
public static function GetNewTransactionId()
{
if (!isset($_SESSION['transactions']))
if (!Session::IsSet('transactions'))
{
$_SESSION['transactions'] = array();
Session::Set('transactions', []);
}
// Strictly speaking, the two lines below should be grouped together
// by a critical section
// sem_acquire($rSemIdentified);
$id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime()); //1 + count($_SESSION['transactions']);
$_SESSION['transactions'][$id] = true;
$id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime());
Session::Set(['transactions', $id], true);
// sem_release($rSemIdentified);
return (string)$id;
@@ -137,17 +139,17 @@ class privUITransactionSession
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$bResult = false;
if (isset($_SESSION['transactions']))
if (Session::IsSet('transactions'))
{
// Strictly speaking, the eight lines below should be grouped together
// inside the same critical section as above
// sem_acquire($rSemIdentified);
if (isset($_SESSION['transactions'][$id]))
if (Session::IsSet(['transactions', $id]))
{
$bResult = true;
if ($bRemoveTransaction)
{
unset($_SESSION['transactions'][$id]);
Session::Unset(['transactions', $id]);
}
}
// sem_release($rSemIdentified);
@@ -162,14 +164,14 @@ class privUITransactionSession
*/
public static function RemoveTransaction($id)
{
if (isset($_SESSION['transactions']))
if (Session::IsSet('transactions'))
{
// Strictly speaking, the three lines below should be grouped together
// inside the same critical section as above
// sem_acquire($rSemIdentified);
if (isset($_SESSION['transactions'][$id]))
if (Session::IsSet(['transactions', $id]))
{
unset($_SESSION['transactions'][$id]);
Session::Unset(['transactions', $id]);
}
// sem_release($rSemIdentified);
}

View File

@@ -80,30 +80,14 @@ class TwigExtension
// Filter to add itopversion to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) {
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl."?itopversion=".ITOP_VERSION;
}
else
{
$sUrl = $sUrl."&itopversion=".ITOP_VERSION;
}
$sUrl = utils::AddParameterToUrl($sUrl, 'itopversion', ITOP_VERSION);
return $sUrl;
}));
// Filter to add a module's version to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) {
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl."?moduleversion=".$sModuleVersion;
}
else
{
$sUrl = $sUrl."&moduleversion=".$sModuleVersion;
}
$sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion);
return $sUrl;
}));
@@ -137,4 +121,5 @@ class TwigExtension
return utils::GetAbsoluteUrlModulePage($sModuleName, $sPage);
}));
}
}

View File

@@ -4,6 +4,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once(APPROOT.'/application/displayblock.class.inc.php');
/**
@@ -82,6 +86,12 @@ class UIExtKeyWidget
$aArgs = [], $bSearchMode = false, &$sInputType = ''
)
{
// we will only use key & name, so let's reduce fields loaded !
$aAttToLoad = [
$sClass => [], // nothing, id and friendlyname are automatically added by the API
];
$oAllowedValues->OptimizeColumnLoad($aAttToLoad);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sTargetClass = $oAttDef->GetTargetClass();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
@@ -194,42 +204,35 @@ class UIExtKeyWidget
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
//$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
$aOptions = [];
$sDisplayValue = "";
$aOption = [];
$aOption['value'] = "";
$aOption['label'] = Dict::S('UI:SelectOne');
array_push($aOptions,$aOption);
array_push($aOptions, $aOption);
$oAllowedValues->Rewind();
$bAddingValue=false;
$sClassAllowed = $oAllowedValues->GetClass();
$bAddingValue = false;
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($oAllowedValues->GetClass());
$aComplementAttributeSpec = MetaModel::GetNameSpec($oAllowedValues->GetClass(), FriendlyNameType::COMPLEMENTARY);
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField)>0)
{
$bAddingValue=true;
if (count($aAdditionalField) > 0) {
$bAddingValue = true;
}
while($oObj = $oAllowedValues->Fetch())
{
$aOption=[];
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed);
$bInitValue = false;
while ($oObj = $oAllowedValues->Fetch()) {
$aOption = [];
$aOption['value'] = $oObj->GetKey();
$aOption['label'] = $oObj->GetName();//.'<span class=\"object-ref-icon fas fa-eye-slash object-obsolete fa-1x fa-fw\"></span>';
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
{
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true')) {
// When there is only once choice, select it by default
$sDisplayValue=$oObj->GetName();
if($value != $oObj->GetKey())
{
$value=$oObj->GetKey();
}
}
else {
if ((is_array($value) && in_array($oObj->GetKey(), $value)) || ($value == $oObj->GetKey())) {
$sDisplayValue = $oObj->GetName();
if ($value != $oObj->GetKey()) {
$value = $oObj->GetKey();
$bInitValue = true;
}
}
if ($oObj->IsObsolete()) {
@@ -242,21 +245,34 @@ class UIExtKeyWidget
}
$aOption['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
}
if (!empty($sObjectImageAttCode)) {
// Try to retrieve image for contact
/** @var \ormDocument $oImage */
$oImage = $oObj->Get($sObjectImageAttCode);
if (!$oImage->IsEmpty()) {
$aOption['picture_url'] = $oImage->GetDisplayURL($sClassAllowed, $oObj->GetKey(), $sObjectImageAttCode);
$aOption['initials'] = '';
} else {
$aOption['initials'] = utils::ToAcronym($oObj->Get('friendlyname'));
}
}
array_push($aOptions, $aOption);
}
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\" tabindex=\"0\"></select>";
$sJsonOptions = json_encode($aOptions);
$sJsonOptions = str_replace('\\', '\\\\', json_encode($aOptions));
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
oACWidget_{$this->iId}.AddSelectize('$sJsonOptions','$value');
$('#$this->iId').on('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange') } );
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange'); } );
EOF
);
if ($bInitValue) {
$oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );");
}
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
}
else
@@ -593,7 +609,7 @@ EOF
// the input for the auto-complete
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input-select\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span>";
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\"><span class=\"field_input_btn\"><div class=\"mini_button ibo-input-select--action-button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span></div>";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
@@ -805,21 +821,24 @@ JS
{
case static::ENUM_OUTPUT_FORMAT_JSON:
$aJsonMap = array();
foreach ($aValues as $sKey => $aValue)
{
if ($aValue['additional_field'] != '')
{
$aJsonMap[] = array('value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag'], 'additional_field' => $aValue['additional_field']);
}
else
{
$aJsonMap[] = array('value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag']);
}
}
$aJsonMap = array();
foreach ($aValues as $sKey => $aValue) {
$aElt = ['value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag']];
if ($aValue['additional_field'] != '') {
$aElt['additional_field'] = $aValue['additional_field'];
}
$oP->SetContentType('application/json');
$oP->add(json_encode($aJsonMap));
if (array_key_exists('initials', $aValue)) {
$aElt['initials'] = $aValue['initials'];
if (array_key_exists('picture_url', $aValue)) {
$aElt['picture_url'] = $aValue['picture_url'];
}
}
$aJsonMap[] = $aElt;
}
$oP->SetContentType('application/json');
$oP->add(json_encode($aJsonMap));
break;
case static::ENUM_OUTPUT_FORMAT_CSV:
@@ -886,26 +905,17 @@ JS
}
}
$sDialogTitle = '';
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
$oPage->add('<form>');
$sClassLabel = MetaModel::GetName($this->sTargetClass);
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
$oPage->add('<nobr><select name="class">');
asort($aPossibleClasses);
foreach($aPossibleClasses as $sClassName => $sClassLabel)
{
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
}
$oPage->add('</select>');
$oPage->add('&nbsp; <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
$sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);;
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']);
$oPage->AddSubBlock($oBlock);
$oClassForm = FormUIBlockFactory::MakeStandard();
$oBlock->AddSubBlock($oClassForm);
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sClassLabel, $this->sTargetClass, $aPossibleClasses));
$oPage->add('</form>');
$oPage->add('</div></div></div>');
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
$oPage->add_ready_script("$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
}
/**
@@ -976,7 +986,7 @@ JS
public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
{
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="overflow:auto;background:#fff;margin-bottom:5px;" id="tree_'.$this->iId.'">');
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">');
$oPage->add('<table style="width:100%"><tr><td>');
if (is_null($sFilter))
{

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper;
/**
* Class UIHTMLEditorWidget
@@ -83,6 +84,7 @@ class UIHTMLEditorWidget
}
$sConfigJS = json_encode($aConfig);
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
// Please read...

View File

@@ -49,6 +49,8 @@ class UIPasswordWidget
*/
public function Display(WebPage $oPage, $aArgs = array())
{
$oPage->add_dict_entry('UI:Component:Input:Password:DoesNotMatch');
$sCode = $this->sAttCode.$this->sNameSuffix;
$iWidgetIndex = self::$iWidgetIndex;
@@ -57,11 +59,12 @@ class UIPasswordWidget
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword">';
$sHtmlValue .= '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
$sHtmlValue .= '<span>'.Dict::S('UI:PasswordConfirm').'</span>';
$sHtmlValue .= '<input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword ibo-input-wrapper">';
$sHtmlValue .= '<input class="ibo-input" type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
$sHtmlValue .= '<div class="ibo-input-wrapper ibo-input-wrapper--with-buttons"><input class="ibo-input" type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
$sHtmlValue .= '<div class="ibo-input-select--action-buttons"><div class="ibo-input-select--action-button ibo-input-select--action-button--create" data-tooltip-content="'.Dict::S('UI:PasswordConfirm').'"><i class="fas fa-question-circle"></i></div></div></div>';
$sHtmlValue .= '<button id="'.$this->iId.'_reset" class="ibo-button ibo-is-regular ibo-is-neutral" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<span class="ibo-button--icon fas fa-undo"></span><span class="ibo-button--label">'.Dict::S('UI:Button:ResetPassword').'</span></button>';
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
$sHtmlValue .= '</div>';

View File

@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use ScssPhp\ScssPhp\Compiler;
@@ -239,9 +240,9 @@ class utils
public static function InitArchiveMode()
{
if (isset($_SESSION['archive_mode']))
if (Session::IsSet('archive_mode'))
{
$iDefault = $_SESSION['archive_mode'];
$iDefault = Session::Get('archive_mode');
}
else
{
@@ -249,9 +250,9 @@ class utils
}
// Read and record the value for switching the archive mode
$iCurrent = self::ReadParam('with-archive', $iDefault);
if (isset($_SESSION))
if (Session::IsInitialized())
{
$_SESSION['archive_mode'] = $iCurrent;
Session::Set('archive_mode', $iCurrent);
}
// Read and use the value for the current page (web services)
$iCurrent = self::ReadParam('with_archive', $iCurrent, true);
@@ -860,6 +861,8 @@ class utils
}
/**
* @param boolean $bForceGetFromDisk if true then will always read from disk without using instances in memory
*
* @return \Config Get object in the following order :
* <ol>
* <li>from {@link MetaModel::GetConfig} if loaded
@@ -872,25 +875,26 @@ class utils
* @throws \CoreException
*
* @since 2.7.0 N°2478 this method will now always call {@link MetaModel::GetConfig} first, and cache in this class is only set when loading from disk
* @since 3.0.0 N°4158 new $bReadFromDisk parameter
*/
public static function GetConfig()
public static function GetConfig($bForceGetFromDisk = false)
{
$oMetaModelConfig = MetaModel::GetConfig();
if ($oMetaModelConfig !== null)
{
return $oMetaModelConfig;
}
if (!$bForceGetFromDisk) {
$oMetaModelConfig = MetaModel::GetConfig();
if ($oMetaModelConfig !== null) {
return $oMetaModelConfig;
}
if (self::$oConfig !== null)
{
return self::$oConfig;
if (self::$oConfig !== null) {
return self::$oConfig;
}
}
$sCurrentEnvConfigPath = self::GetConfigFilePath();
if (file_exists($sCurrentEnvConfigPath))
{
if (file_exists($sCurrentEnvConfigPath)) {
$oCurrentEnvDiskConfig = new Config($sCurrentEnvConfigPath);
self::SetConfig($oCurrentEnvDiskConfig);
return self::$oConfig;
}
@@ -1210,7 +1214,7 @@ class utils
*/
static function CanLogOff()
{
return (isset($_SESSION['can_logoff']) ? $_SESSION['can_logoff'] : false);
return Session::Get('can_logoff', false);
}
/**
@@ -1219,7 +1223,7 @@ class utils
*/
public static function GetSessionLog()
{
return print_r($_SESSION, true);
return Session::GetLog();
}
static function DebugBacktrace($iLimit = 5)
@@ -1312,14 +1316,7 @@ class utils
*/
public static function GetCurrentEnvironment()
{
if (isset($_SESSION['itop_env']))
{
return $_SESSION['itop_env'];
}
else
{
return ITOP_DEFAULT_ENV;
}
return Session::Get('itop_env', ITOP_DEFAULT_ENV);
}
/**
@@ -1922,7 +1919,7 @@ class utils
public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array())
{
$oSass = new Compiler();
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Expanded');
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Compressed');
// Setting our variables
$oSass->setVariables($aVariables);
// Setting our imports paths
@@ -2638,24 +2635,36 @@ class utils
if(!empty($aMentionsAllowedClasses)) {
$aDefaultConf['mentions'] = [];
foreach($aMentionsAllowedClasses as $sMentionChar => $sMentionClass) {
foreach($aMentionsAllowedClasses as $sMentionMarker => $sMentionScope) {
// Retrieve mention class
// - First test if the conf is a simple Datamodel class
if (MetaModel::IsValidClass($sMentionScope)) {
$sMentionClass = $sMentionScope;
}
// - Otherwise it must be a valid OQL
else {
$oTmpSearch = DBSearch::FromOQL($sMentionScope);
$sMentionClass = $oTmpSearch->GetClass();
unset($oTmpSearch);
}
// Note: Endpoints are defaults only and should be overloaded by other GUIs such as the end-users portal
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&target_class='.$sMentionClass.'&needle={encodedQuery}';
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&marker='.$sMentionMarker.'&needle={encodedQuery}';
$sMentionItemUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.$sMentionClass.'&id={id}';
$sMentionItemPictureTemplate = (empty(MetaModel::GetImageAttributeCode($sMentionClass))) ? '' : <<<HTML
<span class="ibo-vendors-ckeditor--autocomplete-item-image" style="background-image: url('{picture_url}');">{initials}</span>
<span class="ibo-vendors-ckeditor--autocomplete-item-image" style="{picture_style}">{initials}</span>
HTML;
$sMentionItemTemplate = <<<HTML
<li class="ibo-vendors-ckeditor--autocomplete-item" data-id="{id}">{$sMentionItemPictureTemplate}<span class="ibo-vendors-ckeditor--autocomplete-item-title">{friendlyname}</span></li>
HTML;
$sMentionOutputTemplate = <<<HTML
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionChar}{friendlyname}</a>
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionMarker}{friendlyname}</a>
HTML;
$aDefaultConf['mentions'][] = [
'feed' => $sMentionEndpoint,
'marker' => $sMentionChar,
'marker' => $sMentionMarker,
'minChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'),
'itemTemplate' => $sMentionItemTemplate,
'outputTemplate' => $sMentionOutputTemplate,
@@ -2811,20 +2820,19 @@ HTML;
//----------------------------------------------
/**
* Check if iTop is in a development environment (VCS vs build number)
* Check if iTop is in a development environment
*
* @return bool
* @return bool true if development environment
*
* @since 2.6.0 method creation
* @since 3.0.0 add the `developer_mode.enabled` config parameter
*
* @use `developer_mode.enabled` config parameter
* @use ITOP_REVISION
* @uses GetDeveloperModeParam
* @uses ITOP_REVISION constant (check 'svn' value)
*/
public static function IsDevelopmentEnvironment()
{
$oConfig = utils::GetConfig();
$bIsDevEnvInConfig = $oConfig->Get('developer_mode.enabled');
$bIsDevEnvInConfig = static::GetDeveloperModeParam();
if ($bIsDevEnvInConfig === true) {
return true;
}
@@ -2842,7 +2850,36 @@ HTML;
}
/**
* @return bool : indicate whether we run under a windows environnement or not
* In the setup there are times when the MetaModel config attribute is loaded but partially (only setup parameters are set, others have the default value)
* So we need to load from disk then !
*
* But in other scenario we want to read from memory : for example when changing the option in a PHPUnit setUp method
*
* This method will first try to get the `developer_mode.enabled` config parameter the standard way (call to GetConfig without modification).
* If we are getting null (not defined parameter), then we will load config from disk only (GetConfig(true))
*
* @return bool|null
* @throws \ConfigException
* @throws \CoreException
*
* @uses developer_mode.enabled config parameter
*/
private static function GetDeveloperModeParam(): ?bool
{
$oConfig = static::GetConfig(false);
$bIsDevEnvInConfig = $oConfig->Get('developer_mode.enabled');
if (!is_null($bIsDevEnvInConfig)) {
return $bIsDevEnvInConfig;
}
$oConfigFromDisk = static::GetConfig(true);
return $oConfigFromDisk->Get('developer_mode.enabled');
}
/**
* @return bool true if we are running under a Windows environment
* @since 2.7.4 : N°3412
*/
public static function IsWindowsEnvironment()
@@ -3008,4 +3045,26 @@ HTML;
return $aMentionedObjects;
}
/**
* @param $sUrl
* @param string $sParamName
* @param string $sParamValue
*
* @return string
* @since 3.0.0
*/
public static function AddParameterToUrl(string $sUrl, string $sParamName, string $sParamValue): string
{
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl.'?'.urlencode($sParamName).'='.urlencode($sParamValue);
}
else
{
$sUrl = $sUrl.'&'.urlencode($sParamName).'='.urlencode($sParamValue);
}
return $sUrl;
}
}

View File

@@ -44,11 +44,7 @@ define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
if (function_exists('microtime')) {
$fItopStarted = microtime(true);
} else {
$fItopStarted = 1000 * time();
}
$fItopStarted = microtime(true);
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
require_once APPROOT.'/lib/autoload.php';

View File

@@ -2,7 +2,7 @@
"type": "project",
"license": "AGPLv3",
"require": {
"php": ">=7.1.3",
"php": ">=7.1.3 <8.0.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -11,9 +11,9 @@
"ext-mysqli": "*",
"ext-soap": "*",
"combodo/tcpdf": "6.3.5",
"nikic/php-parser": "^3.1",
"nikic/php-parser": "^4.12.0",
"pear/archive_tar": "1.4.13",
"pelago/emogrifier": "2.1.0",
"pelago/emogrifier": "3.1.0",
"scssphp/scssphp": "1.0.6",
"swiftmailer/swiftmailer": "5.4.12",
"symfony/console": "3.4.*",
@@ -37,24 +37,21 @@
},
"config": {
"platform": {
"php": "7.2.0"
"php": "7.1.3"
},
"vendor-dir": "lib",
"preferred-install": {
"*": "dist"
},
"sort-packages": true,
"classmap-authoritative": true
"classmap-authoritative": true,
"platform-check": true
},
"autoload": {
"classmap": [
"core",
"application",
"sources/application",
"sources/Composer",
"sources/Controller",
"sources/Form",
"sources/Renderer"
"sources"
],
"exclude-from-classmap": [
"core/dbobjectsearch.class.php",

77
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": "62e394b1ef30b4e716e3e3e519de11dd",
"content-hash": "fb56686981ee4945791fe5b93735b022",
"packages": [
{
"name": "combodo/tcpdf",
@@ -68,24 +68,25 @@
},
{
"name": "nikic/php-parser",
"version": "v3.1.5",
"version": "v4.12.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce"
"reference": "6608f01670c3cc5079e18c1dab1104e002579143"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
"reference": "6608f01670c3cc5079e18c1dab1104e002579143",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.5"
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0|~5.0"
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
@@ -93,7 +94,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "4.9-dev"
}
},
"autoload": {
@@ -115,7 +116,11 @@
"parser",
"php"
],
"time": "2018-02-28T20:30:58+00:00"
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
},
"time": "2021-07-21T10:44:31+00:00"
},
{
"name": "paragonie/random_compat",
@@ -406,34 +411,34 @@
},
{
"name": "pelago/emogrifier",
"version": "v2.1.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/emogrifier.git",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
"symfony/css-selector": "^3.4.0 || ^4.0.0"
"php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4",
"symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.0",
"phpmd/phpmd": "^2.6.0",
"phpunit/phpunit": "^4.8.0",
"squizlabs/php_codesniffer": "^3.3.2"
"friendsofphp/php-cs-fixer": "^2.15.3",
"phpmd/phpmd": "^2.7.0",
"phpunit/phpunit": "^5.7.27",
"squizlabs/php_codesniffer": "^3.5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "4.0.x-dev"
}
},
"autoload": {
@@ -446,16 +451,6 @@
"MIT"
],
"authors": [
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
@@ -464,9 +459,19 @@
"name": "Zoli Szabó",
"email": "zoli.szabo+github@gmail.com"
},
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Jake Hotson",
"email": "jake@qzdesign.co.uk"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
}
],
"description": "Converts CSS styles into inline style attributes in your HTML code",
@@ -476,7 +481,11 @@
"email",
"pre-processing"
],
"time": "2018-12-08T13:55:46+00:00"
"support": {
"issues": "https://github.com/MyIntervals/emogrifier/issues",
"source": "https://github.com/MyIntervals/emogrifier"
},
"time": "2019-12-26T19:37:31+00:00"
},
{
"name": "psr/cache",
@@ -2604,7 +2613,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1.3",
"php": ">=7.1.3 <8.0.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -2615,7 +2624,7 @@
},
"platform-dev": [],
"platform-overrides": {
"php": "7.2.0"
"php": "7.1.3"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core;
use mysqli;
/**
* mysqli object is really hard to mock as it contains lots of attributes & methods ! Thought we need to mock it to test transactions !
*
* To solve this, a new attribute exists and is only used in specific use cases, so there are just few things to mock.
*
* This object adds more readability than previous model with 2 attributes in {@see CMDBSource}.
*
* @used-by \CMDBSource
*
* @since 3.0.0 N°4325 Object creation
* This wrapper handles the 2 {@mysqli myqsli} attributes that were previously in {@see CMDBSource}
* To allow testing we added a second mysqli object (N°3513 in 2.7.5) and code became a bit confusing :/
* With this wrapper everything is in the same place, and we can express the intention more clearly !
*/
class DbConnectionWrapper
{
/** @var mysqli */
protected static $oDbCnxStandard;
/**
* Can contain a genuine mysqli object, or a mock that would emulate {@see mysqli::query()}
*
* @var mysqli
* @used-by \Combodo\iTop\Test\UnitTest\Core\TransactionsTest
*/
protected static $oDbCnxMockableForQuery;
/**
* @param bool $bIsForQuery set to true if using {@see mysqli::query()}
*
* @return \mysqli|null
*/
public static function GetDbConnection(bool $bIsForQuery = false): ?mysqli
{
if ($bIsForQuery) {
return static::$oDbCnxMockableForQuery;
}
return static::$oDbCnxStandard;
}
public static function SetDbConnection(mysqli $oMysqli): void
{
static::$oDbCnxStandard = $oMysqli;
static::SetDbConnectionMockForQuery($oMysqli);
}
/**
* Use this to register a mock that will handle {@see mysqli::query()}
*
* @param \mysqli $oMysqli
*/
public static function SetDbConnectionMockForQuery(mysqli $oMysqli): void
{
static::$oDbCnxMockableForQuery = $oMysqli;
}
}

View File

@@ -66,9 +66,8 @@ class MyHelpers
// getmicrotime()
// format sss.mmmuuupppnnn
public static function getmicrotime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
{
return microtime(true);
}
/*

View File

@@ -388,7 +388,12 @@ class AsyncSendEmail extends AsyncTask
return "Bug - the email should be sent in synchronous mode";
case EMAIL_SEND_ERROR:
return "Failed: ".implode(', ', $aIssues);
if (is_array($aIssues)) {
$sMessage = "Sending eMail failed: ".implode(', ', $aIssues);
} else {
$sMessage = "Sending eMail failed.";
}
throw new Exception($sMessage);
}
return '';
}

View File

@@ -4123,7 +4123,8 @@ class AttributeText extends AttributeString
{
// Propose a std link to the object
$sClassLabel = MetaModel::GetName($sClass);
$sReplacement = "<span class=\"wiki_broken_link\">$sClassLabel:$sName" . (!empty($sLabel) ? " ($sLabel)" : "") . "</span>";
$sToolTipForHtml = utils::EscapeHtml(Dict::Format('Core:UnknownObjectLabel', $sClass, $sName));
$sReplacement = "<span class=\"wiki_broken_link ibo-is-broken-hyperlink\" data-tooltip-content=\"$sToolTipForHtml\">$sClassLabel:$sName" . (!empty($sLabel) ? " ($sLabel)" : "") . "</span>";
$sText = str_replace($aMatches[0], $sReplacement, $sText);
// Later: propose a link to create a new object
// Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name
@@ -4159,13 +4160,13 @@ class AttributeText extends AttributeString
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
$sValue = self::RenderWikiHtml($sValue);
return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
return "<div $sStyle>$sValue</div>";
}
else
{
$sValue = self::RenderWikiHtml($sValue, true /* wiki only */);
return "<div class=\"HTML\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
}
}
@@ -10282,7 +10283,6 @@ abstract class AttributeSet extends AttributeDBFieldVoid
} else {
$sTooltipContent = <<<HTML
<h4>$sLabel</h4>
<br>
<div>$sDescription</div>
HTML;
$sTooltipHtmlEnabled = 'true';
@@ -11573,7 +11573,6 @@ class AttributeTagSet extends AttributeSet
} else {
$sTooltipContent = <<<HTML
<h4>$sTagLabel</h4>
<br>
<div>$sTagDescription</div>
HTML;
$sTooltipHtmlEnabled = 'true';
@@ -12544,10 +12543,12 @@ class AttributeCustomFields extends AttributeDefinition
*/
public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
{
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'),
true);
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
if ($aRawData != null) {
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
} else{
return null;
}
}
public function MakeRealValue($proposedValue, $oHostObject)

View File

@@ -1199,9 +1199,6 @@ EOF
$.get(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data)
{
$('#$sAjaxDivId').html(data);
var table = $('#$sAjaxDivId .listResults');
table.tableHover(); // hover tables
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}
);
}

View File

@@ -1,22 +1,4 @@
<?php
// Copyright (C) 2010-2021 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Persistent class (internal) cmdbChange
*
@@ -24,6 +6,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
/**
* A change as requested/validated at once by user, may groups many atomic changes
@@ -53,7 +36,7 @@ class CMDBChange extends DBObject
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"User", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum('interactive,csv-interactive,csv-import.php,webservice-soap,webservice-rest,synchro-data-source,email-processing,custom-extension'), "sql"=>"origin", "default_value"=>"interactive", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum(implode(',', [CMDBChangeOrigin::INTERACTIVE, CMDBChangeOrigin::CSV_INTERACTIVE, CMDBChangeOrigin::CSV_IMPORT, CMDBChangeOrigin::WEBSERVICE_SOAP, CMDBChangeOrigin::WEBSERVICE_REST, CMDBChangeOrigin::SYNCHRO_DATA_SOURCE, CMDBChangeOrigin::EMAIL_PROCESSING, CMDBChangeOrigin::CUSTOM_EXTENSION])), "sql"=>"origin", "default_value"=>CMDBChangeOrigin::INTERACTIVE, "is_null_allowed"=>true, "depends_on"=>array())));
}
/**

View File

@@ -24,13 +24,15 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\DbConnectionWrapper;
require_once('MyHelpers.class.inc.php');
require_once(APPROOT.'core/kpi.class.inc.php');
/**
* CMDBSource
* database access wrapper
* database access wrapper
*
* @package iTopORM
*/
@@ -66,11 +68,6 @@ class CMDBSource
*/
protected static $m_sDBTlsCA;
/** @var mysqli $m_oMysqli */
protected static $m_oMysqli;
/** @var mysqli or mock used for test purpose, only used in query() method */
protected static $oMySQLiForQuery;
/**
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
* @since 2.7.0 N°679
@@ -135,8 +132,8 @@ class CMDBSource
self::$m_bDBTlsEnabled = empty($bTlsEnabled) ? false : $bTlsEnabled;
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
self::SetMySQLiForQuery(self::$m_oMysqli);
$oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
DbConnectionWrapper::SetDbConnection($oMysqli);
}
/**
@@ -252,7 +249,7 @@ class CMDBSource
* parameters were used.<br>
* This method can be called to ensure that the DB connection really uses TLS.
*
* <p>We're using this object connection : {@link self::$m_oMysqli}
* <p>We're using this object connection : {@see self::$m_oMysqli}
*
* @param \mysqli $oMysqli
*
@@ -345,7 +342,8 @@ class CMDBSource
{
// In case we don't have rights to enumerate the databases
// Let's try to connect directly
return @((bool)self::$m_oMysqli->query("USE `$sSource`"));
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
return @((bool)DbConnectionWrapper::GetDbConnection(true)->query("USE `$sSource`"));
}
}
@@ -361,7 +359,7 @@ class CMDBSource
*/
public static function GetServerInfo()
{
return mysqli_get_server_info ( self::$m_oMysqli );
return mysqli_get_server_info(DbConnectionWrapper::GetDbConnection());
}
/**
@@ -392,9 +390,9 @@ class CMDBSource
*/
public static function SelectDB($sSource)
{
if (!((bool)self::$m_oMysqli->query("USE `$sSource`")))
{
throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
if (!((bool)DbConnectionWrapper::GetDbConnection(true)->query("USE `$sSource`"))) {
throw new MySQLException('Could not select DB', array('db_name' => $sSource));
}
self::$m_sDBName = $sSource;
}
@@ -445,51 +443,29 @@ class CMDBSource
/**
* @return \mysqli
*
* @since 2.5.0 N°1260
*/
public static function GetMysqli()
{
return self::$m_oMysqli;
}
/**
* @return
*/
private static function GetMySQLiForQuery()
{
return self::$oMySQLiForQuery;
}
/**
* Used for test purpose (mysqli mock)
* @param $oMySQLi
*/
private static function SetMySQLiForQuery($oMySQLi)
{
self::$oMySQLiForQuery = $oMySQLi;
return DbConnectionWrapper::GetDbConnection(false);
}
public static function GetErrNo()
{
if (self::$m_oMysqli->errno != 0)
{
return self::$m_oMysqli->errno;
}
else
{
return self::$m_oMysqli->connect_errno;
if (DbConnectionWrapper::GetDbConnection()->errno != 0) {
return DbConnectionWrapper::GetDbConnection()->errno;
} else {
return DbConnectionWrapper::GetDbConnection()->connect_errno;
}
}
public static function GetError()
{
if (self::$m_oMysqli->error != '')
{
return self::$m_oMysqli->error;
}
else
{
return self::$m_oMysqli->connect_error;
if (DbConnectionWrapper::GetDbConnection()->error != '') {
return DbConnectionWrapper::GetDbConnection()->error;
} else {
return DbConnectionWrapper::GetDbConnection()->connect_error;
}
}
@@ -529,7 +505,8 @@ class CMDBSource
// Quote if not a number or a numeric string
if ($bAlways || is_string($value))
{
$value = $cQuoteStyle . self::$m_oMysqli->real_escape_string($value) . $cQuoteStyle;
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$value = $cQuoteStyle.DbConnectionWrapper::GetDbConnection()->real_escape_string($value).$cQuoteStyle;
}
return $value;
}
@@ -590,7 +567,7 @@ class CMDBSource
/**
* Send the query directly to the DB. **Be extra cautious with this !**
*
* Use {@link Query} if you're not sure.
* Use {@see Query} if you're not sure.
*
* @internal
*
@@ -612,26 +589,25 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch (mysqli_sql_exception $e)
{
self::LogDeadLock($e);
self::LogDeadLock($e, true);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
if ($oResult === false)
{
if ($oResult === false) {
$aContext = array('query' => $sSql);
$iMySqlErrorNo = self::$m_oMysqli->errno;
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection(true)->errno;
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
{
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes)) {
throw new MySQLHasGoneAwayException(self::GetError(), $aContext);
}
$e = new MySQLException('Failed to issue SQL query', $aContext);
self::LogDeadLock($e);
self::LogDeadLock($e, true);
throw $e;
}
@@ -640,23 +616,24 @@ class CMDBSource
/**
* @param \Exception $e
* @param bool $bForQuery to get the proper DB connection
*
* @since 2.7.1
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
*/
private static function LogDeadLock(Exception $e)
private static function LogDeadLock(Exception $e, $bForQuery = false)
{
// checks MySQL error code
$iMySqlErrorNo = self::$m_oMysqli->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK)))
{
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
return;
}
// Get error info
$sUser = UserRights::GetUser();
$oError = self::$m_oMysqli->query('SHOW ENGINE INNODB STATUS');
if ($oError !== false)
{
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oError = DbConnectionWrapper::GetDbConnection(true)->query('SHOW ENGINE INNODB STATUS');
if ($oError !== false) {
$aData = $oError->fetch_all(MYSQLI_ASSOC);
$sInnodbStatus = $aData[0];
}
@@ -829,7 +806,7 @@ class CMDBSource
public static function GetInsertId()
{
$iRes = self::$m_oMysqli->insert_id;
$iRes = DbConnectionWrapper::GetDbConnection()->insert_id;
if (is_null($iRes))
{
return 0;
@@ -871,7 +848,8 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -901,17 +879,19 @@ class CMDBSource
/**
* @param string $sSql
* @param int $iMode
*
* @return array
* @throws \MySQLException if query cannot be processed
*/
public static function QueryToArray($sSql)
public static function QueryToArray($sSql, $iMode = MYSQLI_BOTH)
{
$aData = array();
$oKPI = new ExecutionKPI();
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -924,7 +904,7 @@ class CMDBSource
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
while ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
while ($aRow = $oResult->fetch_array($iMode))
{
$aData[] = $aRow;
}
@@ -961,7 +941,8 @@ class CMDBSource
$aData = array();
try
{
$oResult = self::$m_oMysqli->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -993,7 +974,8 @@ class CMDBSource
{
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -1018,7 +1000,7 @@ class CMDBSource
public static function AffectedRows()
{
return self::$m_oMysqli->affected_rows;
return DbConnectionWrapper::GetDbConnection()->affected_rows;
}
public static function FetchArray($oResult)
@@ -1136,7 +1118,7 @@ class CMDBSource
*
* We still need to do a case sensitive comparison for enum values !
*
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
* A better solution would be to generate SQL field definitions ({@see GetFieldSpec} method) based on the DB used... But for
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
*
* @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
@@ -1481,7 +1463,8 @@ class CMDBSource
$sSql = "SELECT * FROM `$sTable`";
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{

View File

@@ -113,6 +113,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'log_level_min.write_in_db' => [
'type' => 'array',
'description' => 'Additional configuration that enable "in DB" logs for Exception on compatible code.',
'default' => [ 'Exception' => 'Error', ],
'value' => [ 'Exception' => 'Error', ],
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'app_env_label' => [
'type' => 'string',
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
@@ -1141,6 +1149,38 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'compatibility.include_moved_js_files' => [
'type' => 'bool',
'description' => 'Include back JS files which are now only included when necessary to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'compatibility.include_deprecated_js_files' => [
'type' => 'bool',
'description' => 'Include the deprecated JS files to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'compatibility.include_moved_css_files' => [
'type' => 'bool',
'description' => 'Include back CSS files which are now only included when necessary to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'compatibility.include_deprecated_css_files' => [
'type' => 'bool',
'description' => 'Include the deprecated CSS files to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'navigation_menu.show_menus_count' => [
'type' => 'bool',
'description' => 'Display count badges for OQL menu entries',
@@ -1287,9 +1327,9 @@ class Config
],
'mentions.allowed_classes' => [
'type' => 'array',
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete (eg. "@" => "Person")',
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value can be either a DM class or a valid OQL (eg. "@" => "Person", "?" => "SELECT FAQ WHERE status = \'published\'")',
'default' => [
'@' => 'Person',
'@' => 'SELECT Person WHERE status = \'active\'',
],
'value' => false,
'source_of_value' => '',
@@ -1415,6 +1455,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.disable_inline_documents_sandbox' => [
'type' => 'bool',
'description' => 'If true then the sandbox for documents displayed in a browser tab will be disabled; enabling scripts and other interactive content. Note that setting this to true will open the application to potential XSS attacks!',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',

View File

@@ -4,6 +4,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
/**
* All objects to be displayed in the application (either as a list or as details)
* must implement this interface.
@@ -946,10 +948,9 @@ abstract class DBObject implements iDisplay
*/
protected function ComputeHighlightCode()
{
// First if the state defines a HiglightCode, apply it
$sState = $this->GetState();
if ($sState != '')
if (MetaModel::HasLifecycle(get_class($this)))
{
$sState = $this->GetState();
$sCode = MetaModel::GetHighlightCode(get_class($this), $sState);
$this->SetHighlightCode($sCode);
}
@@ -1277,13 +1278,20 @@ abstract class DBObject implements iDisplay
{
// If the object if not issued from a query but constructed programmatically
// the label may be empty. In this case run a query to get the object's friendly name
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey, false);
if (is_object($oTmpObj))
{
$sObjOql = 'SELECT '.$sObjClass.' WHERE id='.$sObjKey;
$oObjFilter = DBSearch::FromOQL($sObjOql);
$oSet = new DBObjectSet($oObjFilter);
// we will only use id and friendlyname, so let's remove other fields !
$aAttToLoad = [
$sObjClass => [],
];
$oSet->OptimizeColumnLoad($aAttToLoad);
$oTmpObj = $oSet->Fetch();
if (is_object($oTmpObj)) {
$sHtmlLabel = $oTmpObj->GetName();
}
else
{
} else {
// May happen in case the target object is not in the list of allowed values for this attribute
$sHtmlLabel = "<em>$sObjClass::$sObjKey</em>";
}
@@ -1518,14 +1526,15 @@ abstract class DBObject implements iDisplay
* Helper to get the friendly name in a safe manner for displaying inside a web page
*
* @internal
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
*
* @return string
* @throws \CoreException
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
* @since 3.0.0 N°580 New $sType parameter
*
*/
public function GetName()
public function GetName($sType = FriendlyNameType::SHORT)
{
return htmlentities($this->GetRawName(), ENT_QUOTES, 'UTF-8');
return htmlentities($this->GetRawName($sType), ENT_QUOTES, 'UTF-8');
}
/**
@@ -1539,11 +1548,17 @@ abstract class DBObject implements iDisplay
* @return string
* @throws \CoreException
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
* @since 3.0.0 N°580 New $sType parameter
*
*/
public function GetRawName()
public function GetRawName($sType = FriendlyNameType::SHORT)
{
return $this->Get('friendlyname');
if ($sType == FriendlyNameType::SHORT) {
return $this->Get('friendlyname');
} else {
$oExpression = MetaModel::GetNameExpression(get_class($this), $sType);
$this->EvaluateExpression($oExpression);
}
}
/**
@@ -1859,9 +1874,7 @@ abstract class DBObject implements iDisplay
{
/** @var \AttributeExternalKey $oAtt */
$sTargetClass = $oAtt->GetTargetClass();
$oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/);
if (is_null($oTargetObj))
{
if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) {
return "Target object not found ($sTargetClass::$toCheck)";
}
}
@@ -2129,28 +2142,23 @@ abstract class DBObject implements iDisplay
}
if ($oAttDef->DuplicatesAllowed()) {
return true;
return;
}
// To control duplicates go through all the entries and check if the remote has been seen
/** @var \ormLinkSet $value */
$aModifiedLnk = $value->ListModifiedLinks();
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$aExistingRemotesId = $value->GetColumnAsArray($sExtKeyToRemote, true);
$aExistingRemotesFriendlyName = $value->GetColumnAsArray($sExtKeyToRemote.'_friendlyname', true);
$aCurrentRemoteIds = [];
$aDuplicatesFields = [];
foreach ($aModifiedLnk as $oModifiedLnk) {
$iModifiedLnkId = $oModifiedLnk->GetKey();
$iModifiedLnkRemoteId = $oModifiedLnk->Get($sExtKeyToRemote);
$aExistingRemotesIdToCheck = array_filter($aExistingRemotesId, function ($iLnkKey) use ($iModifiedLnkId) {
return ($iLnkKey != $iModifiedLnkId);
}, ARRAY_FILTER_USE_KEY);
if (!in_array($iModifiedLnkRemoteId, $aExistingRemotesIdToCheck, true)) {
continue;
$value->rewind();
while ($oCurrentLnk = $value->current()) {
$iExtKeyToRemote = $oCurrentLnk->Get($sExtKeyToRemote);
if (isset($aCurrentRemoteIds[$iExtKeyToRemote])) {
$aDuplicatesFields[] = $oCurrentLnk->Get($sExtKeyToRemote.'_friendlyname');
} else {
$aCurrentRemoteIds[$iExtKeyToRemote] = true;
}
$iLnkId = $oModifiedLnk->GetKey();
$aDuplicatesFields[] = $aExistingRemotesFriendlyName[$iLnkId];
$value->next();
}
if (!empty($aDuplicatesFields)) {

View File

@@ -63,22 +63,23 @@ else
abstract class DBSearch
{
/** @internal */
/** @internal */
const JOIN_POINTING_TO = 0;
/** @internal */
/** @internal */
const JOIN_REFERENCED_BY = 1;
protected $m_bNoContextParameters = false;
/** @var array For {@see iQueryModifier} impl */
protected $m_aModifierProperties = array();
protected $m_bArchiveMode = false;
protected $m_bShowObsoleteData = true;
/**
* DBSearch constructor.
*
* @api
* @see DBSearch::FromOQL()
*/
/**
* DBSearch constructor.
*
* @api
* @see DBSearch::FromOQL()
*/
public function __construct()
{
$this->Init();

View File

@@ -242,7 +242,7 @@ class DeletionPlan
public function SetDeletionIssues($oObject, $aIssues, $bSecurityIssue)
{
if (count($aIssues) > 0)
if (count($aIssues ?? []) > 0)
{
$sClass = get_class($oObject);
$iId = $oObject->GetKey();

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
use Combodo\iTop\Renderer\BlockRenderer;
@@ -464,7 +465,7 @@ class DisplayableNode extends GraphNode
{
$aRootCauses[] = $oRootCause->GetHyperlink();
}
$sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'">&nbsp;'.implode(', ', $aRootCauses).'</p>';
$sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" class="ibo-class-icon ibo-is-small" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'">&nbsp;'.implode(', ', $aRootCauses).'</p>';
}
$sHtml .= '<hr/>';
}
@@ -805,7 +806,7 @@ class DisplayableGroupNode extends DisplayableNode
$sHtml .= '<a href="#" onclick="$(\'.itop-simple-graph\').simple_graph(\'show_group\', \'relation_group_'.$iGroupIdx.'\');">'.Dict::Format('UI:RelationGroupNumber_N', (1+$iGroupIdx))."</a>";
$sHtml .= '<hr/>';
$sHtml .= '<table><tbody><tr>';
$sHtml .= '<td style="vertical-align:top;padding-right: 0.5em;"><img src="'.$this->GetProperty('icon_url').'"></td><td style="vertical-align:top">'.MetaModel::GetName($this->GetObjectClass()).'<br/>';
$sHtml .= '<td style="vertical-align:top;padding-right: 0.5em;"><img class="ibo-class-icon ibo-is-small" src="'.$this->GetProperty('icon_url').'"></td><td style="vertical-align:top">'.MetaModel::GetName($this->GetObjectClass()).'<br/>';
$sHtml .= Dict::Format('UI_CountOfObjectsShort', $this->GetObjectCount()).'</td>';
$sHtml .= '</tr></tbody></table>';
return $sHtml;
@@ -1446,9 +1447,9 @@ class DisplayableGraph extends SimpleGraph
$sSftShort = Dict::S('UI:ElementsDisplayed');
$sSearchToggle = Dict::S('UI:Search:Toggle');
$oP->add("<div class=\"not-printable\">\n");
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_CYAN, 'ds_flash');
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_SCHEME_CYAN, 'ds_flash');
$oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]);
$oUiSearchBlock->SetIsCollapsible(true);
$oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html(
<<<EOF
<div id="ds_flash" class="search_box ibo-display-graph--search-box">
@@ -1499,11 +1500,8 @@ EOF
$sDirection = utils::ReadParam('d', 'horizontal');
$iGroupingThreshold = utils::ReadParam('g', 5);
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css');
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js');
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
WebResourcesHelper::EnableSimpleGraphInWebPage($oP);
try
{
$this->InitFromGraphviz();

View File

@@ -24,6 +24,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
Swift_Preferences::getInstance()->setCharset('UTF-8');
@@ -335,8 +339,9 @@ class EMail
{
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
{
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
$oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument();
HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone();
$sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present
}
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
$this->m_oMessage->setBody($sBody, $sMimeType);

View File

@@ -222,6 +222,10 @@ class EventIssue extends Event
//
$this->Set('page', @$GLOBALS['_SERVER']['SCRIPT_NAME']);
if (strlen($this->Get('userinfo')) == 0) {
$this->Set('userinfo', UserRights::GetUserId());
}
if (array_key_exists('_GET', $GLOBALS) && is_array($GLOBALS['_GET']))
{
$this->Set('arguments_get', $GLOBALS['_GET']);

View File

@@ -6,6 +6,8 @@
*/
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
@@ -80,38 +82,49 @@ class iTopConfigParser
* @param \PhpParser\Parser $oParser
* @param $sConfig
*
* @return \Combodo\iTop\Config\Validator\ConfigNodesVisitor
* @return void
*/
private function BrowseFile(\PhpParser\Parser $oParser, $sConfig)
private function BrowseFile(Parser $oParser, $sConfig)
{
$prettyPrinter = new Standard();
try
{
try {
$aNodes = $oParser->parse($sConfig);
}
catch (\Error $e)
{
catch (\Error $e) {
$sMessage = Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
$this->oException = new \Exception($sMessage, 0, $e);
}
foreach ($aNodes as $oAssignation)
{
if (! $oAssignation instanceof Assign)
{
foreach ($aNodes as $sKey => $oNode) {
// With PhpParser 3 we had an Assign node at root
// In PhpParser 4 the root node is now an Expression
if (false === ($oNode instanceof \PhpParser\Node\Stmt\Expression)) {
continue;
}
/** @var \PhpParser\Node\Stmt\Expression $oNode */
if (false === ($oNode->expr instanceof Assign)) {
continue;
}
/** @var Assign $oAssignation */
$oAssignation = $oNode->expr;
if (false === ($oAssignation->var instanceof Variable)) {
continue;
}
if (false === ($oAssignation->expr instanceof PhpParser\Node\Expr\Array_)) {
continue;
}
$sCurrentRootVar = $oAssignation->var->name;
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap))
{
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap)) {
continue;
}
$aCurrentRootVarMap =& $this->aVarsMap[$sCurrentRootVar];
foreach ($oAssignation->expr->items as $oItem)
{
foreach ($oAssignation->expr->items as $oItem) {
$sValue = $prettyPrinter->prettyPrintExpr($oItem->value);
$aCurrentRootVarMap[$oItem->key->value] = $sValue;
}

View File

@@ -229,7 +229,7 @@ class InlineImage extends DBObject
*
* @param string $sTempId
*
* @return void
* @return bool True if cleaning was successful, false if anything aborted it
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
@@ -239,8 +239,19 @@ class InlineImage extends DBObject
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public static function OnFormCancel($sTempId)
public static function OnFormCancel($sTempId): bool
{
// Protection against unfortunate massive delete of inline images when a null temp ID is passed
if (strlen($sTempId) === 0) {
IssueLog::Trace('OnFormCancel "error" $sTempId is null or empty', LogChannels::INLINE_IMAGE, array(
'$sTempId' => $sTempId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
return false;
}
// Delete all "pending" InlineImages for this form
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
@@ -257,6 +268,8 @@ class InlineImage extends DBObject
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
return true;
}
/**
@@ -577,7 +590,7 @@ JS
oEditor.on( 'instanceReady', function() {
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').length == 0)
{
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen" style="background-image:url(\\'$sAbsoluteUrlAppRoot/images/full-screen.png\\')">&nbsp;</span>');
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button editor-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen">&nbsp;</span>');
$('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').on('click', function() {
oEditor.execCommand('maximize');
if ($(this).closest('.cke_maximized').length != 0)

View File

@@ -31,29 +31,38 @@ class ExecutionKPI
static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*';
static protected $m_aStats = array(); // Recurrent operations
static protected $m_aExecData = array(); // One shot operations
static protected $m_aStats = []; // Recurrent operations
static protected $m_aExecData = []; // One shot operations
/**
* @var array[ExecutionKPI]
*/
static protected $m_aExecutionStack = []; // embedded execution stats
protected $m_fStarted = null;
protected $m_fChildrenDuration = 0; // Count embedded
protected $m_iInitialMemory = null;
static public function EnableDuration($iLevel)
{
if ($iLevel > 0)
{
if ($iLevel > 0) {
self::$m_bEnabled_Duration = true;
if ($iLevel > 1)
{
if ($iLevel > 1) {
self::$m_bBlameCaller = true;
} else {
self::$m_bBlameCaller = false;
}
} else {
self::$m_bEnabled_Duration = false;
self::$m_bBlameCaller = false;
}
}
static public function EnableMemory($iLevel)
{
if ($iLevel > 0)
{
if ($iLevel > 0) {
self::$m_bEnabled_Memory = true;
} else {
self::$m_bEnabled_Memory = false;
}
}
@@ -103,16 +112,17 @@ class ExecutionKPI
$sTableStyle = 'background-color: #ccc; margin: 10px;';
self::Report("<hr/>");
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
self::Report("<div>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>");
self::Report("</thead>");
$sHtml = "<hr/>";
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>";
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
$sHtml .= "<p>".$oStarted->format('Y-m-d H:i:s.u')."</p>";
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
$sHtml .= "<div>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>";
$sHtml .= "</thead>";
foreach (self::$m_aExecData as $aOpStats)
{
$sOperation = $aOpStats['op'];
@@ -134,12 +144,12 @@ class ExecutionKPI
}
}
self::Report("<tr>");
self::Report(" <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>");
self::Report("</tr>");
$sHtml .= "<tr>";
$sHtml .= " <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>";
$sHtml .= "</tr>";
}
self::Report("</table>");
self::Report("</div>");
$sHtml .= "</table>";
$sHtml .= "</div>";
$aConsolidatedStats = array();
foreach (self::$m_aStats as $sOperation => $aOpStats)
@@ -175,11 +185,11 @@ class ExecutionKPI
);
}
self::Report("<div>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>");
self::Report("</thead>");
$sHtml .= "<div>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>";
$sHtml .= "</thead>";
foreach ($aConsolidatedStats as $sOperation => $aOpStats)
{
$sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
@@ -189,22 +199,24 @@ class ExecutionKPI
$sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
$sAvg = round($aOpStats['avg'], 3);
self::Report("<tr>");
self::Report(" <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>");
self::Report("</tr>");
$sHtml .= "<tr>";
$sHtml .= " <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>";
$sHtml .= "</tr>";
}
self::Report("</table>");
self::Report("</div>");
$sHtml .= "</table>";
$sHtml .= "</div>";
self::Report("</div>");
$sHtml .= "</div>";
self::Report("<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>");
$sHtml .= "<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>";
$fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
self::Report($sHtml);
// Report operation details
foreach (self::$m_aStats as $sOperation => $aOpStats)
{
$sHtml = '';
$bDisplayHeader = true;
foreach ($aOpStats as $sArguments => $aEvents)
{
@@ -250,31 +262,59 @@ class ExecutionKPI
if ($bDisplayHeader)
{
$sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
self::Report("<h4>$sOperationHtml</h4>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>");
self::Report("</thead>");
$sHtml .= "<h4>$sOperationHtml</h4>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>";
$sHtml .= "</thead>";
$bDisplayHeader = false;
}
self::Report("<tr>");
self::Report(" <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>");
self::Report("</tr>");
$sHtml .= "<tr>";
$sHtml .= " <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>";
$sHtml .= "</tr>";
}
}
if (!$bDisplayHeader)
{
self::Report("</table>");
self::Report("<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>");
$sHtml .= "</table>";
$sHtml .= "<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>";
}
self::Report($sHtml);
}
self::Report('<a name="end-'.md5($sExecId).'">&nbsp;</a>');
$sHtml = '<a name="end-'.md5($sExecId).'">&nbsp;</a>';
self::Report($sHtml);
}
public function __construct()
{
$this->ResetCounters();
self::Push($this);
}
/**
* Stack executions to remove children duration from stats
*
* @param \ExecutionKPI $oExecutionKPI
*/
private static function Push(ExecutionKPI $oExecutionKPI)
{
array_push(self::$m_aExecutionStack, $oExecutionKPI);
}
/**
* Pop current child and count its duration in its parent
*
* @param float|int $fChildDuration
*/
private static function Pop(float $fChildDuration = 0)
{
array_pop(self::$m_aExecutionStack);
// Update the parent's children duration
$oPrevExecutionKPI = end(self::$m_aExecutionStack);
if ($oPrevExecutionKPI) {
$oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
}
}
// Get the duration since startup, and reset the counter for the next measure
@@ -285,13 +325,12 @@ class ExecutionKPI
$aNewEntry = null;
if (self::$m_bEnabled_Duration)
{
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$aNewEntry = array(
'op' => $sOperationDesc,
'op' => $sOperationDesc,
'time_begin' => $this->m_fStarted - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
);
// Reset for the next operation (if the object is recycled)
$this->m_fStarted = $fStopped;
@@ -323,31 +362,30 @@ class ExecutionKPI
public function ComputeStats($sOperation, $sArguments)
{
if (self::$m_bEnabled_Duration)
{
$fDuration = 0;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
if (self::$m_bBlameCaller)
{
$fSelfDuration = $fDuration - $this->m_fChildrenDuration;
if (self::$m_bBlameCaller) {
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration,
'time' => $fSelfDuration,
'callers' => MyHelpers::get_callstack(1),
);
}
else
{
} else {
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration
'time' => $fSelfDuration,
);
}
}
self::Pop($fDuration);
}
protected function ResetCounters()
{
if (self::$m_bEnabled_Duration)
{
$this->m_fStarted = MyHelpers::getmicrotime();
$this->m_fStarted = microtime(true);
}
if (self::$m_bEnabled_Memory)

View File

@@ -587,6 +587,9 @@ abstract class LogAPI
static::$m_oFileLog = new FileLog($sTargetFile);
}
/**
* @internal uses only for testing purpose.
*/
public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null)
{
static::$m_oFileLog = $oFileLog;
@@ -653,9 +656,9 @@ abstract class LogAPI
* @throws \ConfigException if log wrongly configured
* @uses GetMinLogLevel
*/
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel): bool
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel, string $sCode = 'log_level_min'): bool
{
$sMinLogLevel = self::GetMinLogLevel($sChannel);
$sMinLogLevel = self::GetMinLogLevel($sChannel, $sCode);
if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
return false;
@@ -675,22 +678,34 @@ abstract class LogAPI
/**
* @param string $sChannel
* @param string $sCode
*
* @return string one of the LEVEL_* const value : the one configured it if exists, otherwise default log level for this channel
* Config can be done globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
* Or per channel : `'log_level_min' => ['InlineImage' => LogAPI::LEVEL_TRACE, 'UserRequest' => LogAPI::LEVEL_TRACE],`
* Config can be set :
* * globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
* * per channel :
* ```
* 'log_level_min' => [
* '' => LogAPI::LEVEL_ERROR, // default log level for channels not listed below
* 'InlineImage' => LogAPI::LEVEL_TRACE,
* 'UserRequest' => LogAPI::LEVEL_TRACE
* ],
* ```
*
* @uses \LogAPI::GetConfig()
* @uses `log_level_min` config parameter
* @uses \LogAPI::GetLevelDefault
*
* @link https://www.itophub.io/wiki/page?id=3_0_0%3Aadmin%3Alog iTop log reference
*/
protected static function GetMinLogLevel($sChannel)
protected static function GetMinLogLevel($sChannel, $sCode = 'log_level_min')
{
$oConfig = static::GetConfig();
if (!$oConfig instanceof Config) {
return static::GetLevelDefault();
}
$sLogLevelMin = $oConfig->Get('log_level_min');
$sLogLevelMin = $oConfig->Get($sCode);
if (empty($sLogLevelMin)) {
return static::GetLevelDefault();
@@ -705,7 +720,12 @@ abstract class LogAPI
}
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) {
return $sLogLevelMin[$sChannel];
return $sLogLevelMin[static::CHANNEL_DEFAULT];
}
// Even though the *self*::CHANNEL_DEFAULT is set to '' in the current class (LogAPI), the test below is necessary as the CHANNEL_DEFAULT constant can be (and is!) overloaded in derivated classes, don't remove this test to factorize it with the previous one.
if (isset($sLogLevelMin[''])) {
return $sLogLevelMin[''];
}
return static::GetLevelDefault();
@@ -717,7 +737,7 @@ abstract class LogAPI
*/
protected static function GetConfig(): ?Config
{
return static::$m_oMockMetaModelConfig ?? \MetaModel::GetConfig();
return static::$m_oMockMetaModelConfig ?? \utils::GetConfig();
}
/**
@@ -825,6 +845,7 @@ class DeadLockLog extends LogAPI
class DeprecatedCallsLog extends LogAPI
{
public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method';
public const ENUM_CHANNEL_PHP_LIBMETHOD = 'deprecated-php-libmethod';
public const ENUM_CHANNEL_FILE = 'deprecated-file';
public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD;
@@ -833,12 +854,112 @@ class DeprecatedCallsLog extends LogAPI
/** @var \FileLog we want our own instance ! */
protected static $m_oFileLog = null;
/**
* Indirection to {@see \LogAPI::IsLogLevelEnabled()} that is handling possible {@see ConfigException}
*
* @param string $sLevel
* @param string $sChannel
*
* @return bool if exception occurs, then returns false
*
* @uses \LogAPI::IsLogLevelEnabled()
*/
protected static function IsLogLevelEnabledSafe($sLevel, $sChannel): bool
{
try {
$bIsLogLevelEnabled = static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD);
}
catch (ConfigException $e) {
$bIsLogLevelEnabled = false;
}
return $bIsLogLevelEnabled;
}
/**
* @param string|null $sTargetFile
*
* @uses \set_error_handler() to catch deprecated notices
*
* @since 3.0.0 N°3002 logs deprecated notices in called code
*/
public static function Enable($sTargetFile = null): void
{
if (empty($sTargetFile)) {
$sTargetFile = APPROOT.'log/deprecated-calls.log';
}
parent::Enable($sTargetFile);
if (static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler']);
}
}
/**
* This will catch a message for all E_DEPRECATED and E_USER_DEPRECATED errors.
* This handler is set in DeprecatedCallsLog::Enable
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
*
* @return bool
* @since 3.0.0 N°3002
* @noinspection SpellCheckingInspection
*/
public static function DeprecatedNoticesErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool
{
if (
(\E_USER_DEPRECATED !== $errno)
&& (\E_DEPRECATED !== $errno)
) {
return false;
}
if (false === static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
// returns true so that nothing is throwned !
return true;
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
// We are generating a trigger_error ourselves, we don't want to trace them !
return false;
}
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'] ?? null;
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'] ?? null;
$sMessage .= ' (';
if (!is_null($sCallerObject)) {
$sMessage .= "{$sCallerObject}::{$sCallerMethod}";
} else {
$sCallerMethodFile = $aStack[$iStackCallerMethodLevel]['file'];
$sCallerMethodLine = $aStack[$iStackCallerMethodLevel]['line'];
if (!is_null($sCallerMethod)) {
$sMessage .= "call to {$sCallerMethod}() in {$sCallerMethodFile}#L{$sCallerMethodLine}";
} else {
$sMessage .= "{$sCallerMethodFile}#L{$sCallerMethodLine}";
}
}
$sMessage .= ')';
}
if (!empty($errstr)) {
$sMessage .= ' : '.$errstr;
}
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_LIBMETHOD);
return true;
}
protected static function GetLevelDefault(): string
@@ -898,16 +1019,17 @@ class DeprecatedCallsLog extends LogAPI
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$sDeprecatedObject = $aStack[1]['class'];
$sDeprecatedMethod = $aStack[1]['function'];
$sCallerFile = $aStack[1]['file'];
$sCallerLine = $aStack[1]['line'];
$iStackDeprecatedMethodLevel = 1; // level 0 = current method, level 1 = method containing the `NotifyDeprecatedPhpMethod` call
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
if (array_key_exists(2, $aStack)) {
$sCallerObject = $aStack[2]['class'];
$sCallerMethod = $aStack[2]['function'];
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 2 = caller of the deprecated method
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'];
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'];
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
}
@@ -915,12 +1037,12 @@ class DeprecatedCallsLog extends LogAPI
$sMessage .= ' : '.$sAdditionalMessage;
}
static::Warning($sMessage, static::ENUM_CHANNEL_PHP_METHOD);
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
}
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
{
if (utils::IsDevelopmentEnvironment()) {
if (true === utils::IsDevelopmentEnvironment()) {
trigger_error($sMessage, E_USER_DEPRECATED);
}
@@ -1002,3 +1124,160 @@ class LogFileRotationProcess implements iScheduledProcess
throw new ProcessException(self::class.' : The configured filename builder is invalid (log_filename_builder_impl="'.$sLogFileNameBuilder.'")');
}
}
/**
* Log exceptions using dedicated API and logic.
*
* Please use {@see ExceptionLog::LogException()} to log exceptions
*
* @since 3.0.0
*/
class ExceptionLog extends LogAPI
{
const CHANNEL_DEFAULT = 'Exception';
const CONTEXT_EXCEPTION = '__exception';
private static $oLastEventIssue = null;
protected static $m_oFileLog = null;
/**
* This method should be used to write logs.
*
* As it encapsulate the operations performed using the Exception, you should prefer it to the standard API inherited from LogApi `ExceptionLog::Error($oException->getMessage(), get_class($oException), ['__exception' => $oException]);`
* The parameter order is not standard, but in our use case, the resulting API is way more convenient this way.
*/
public static function LogException(Exception $oException, $aContext = array(), $sLevel = self::LEVEL_WARNING)
{
if (empty($aContext[self::CONTEXT_EXCEPTION])) {
$aContext[self::CONTEXT_EXCEPTION] = $oException;
}
if (empty($aContext['exception class'])) {
$aContext['exception class'] = get_class($oException);
}
if (empty($aContext['file'])) {
$aContext['file'] = $oException->getFile();
}
if (empty($aContext['line'])) {
$aContext['line'] =$oException->getLine();
}
self::Log($sLevel, $oException->getMessage(), get_class($oException), $aContext);
}
/**
* @inheritDoc
* @throws \ConfigException if log wrongly configured
*/
public static function Log($sLevel, $sMessage, $sClass = null, $aContext = array())
{
if (!static::$m_oFileLog) {
return;
}
if (!isset(self::$aLevelsPriority[$sLevel])) {
IssueLog::Error("invalid log level '{$sLevel}'");
return;
}
$sChannel = self::FindClassChannel($sClass);
if (static::IsLogLevelEnabled($sLevel, $sChannel)) {
static::$m_oFileLog->$sLevel($sMessage, $sChannel, array_diff_key($aContext, [self::CONTEXT_EXCEPTION => null])); //The exception should not be included in the error.log because of its verbosity.
}
$sDbChannel = self::FindClassChannel($sClass, 'log_level_min.write_in_db');
if (static::IsLogLevelEnabled($sLevel, $sDbChannel, 'log_level_min.write_in_db')) {
self::WriteToDb($aContext);
}
}
protected static function FindClassChannel($sClass, $sCode = 'log_level_min')
{
$oConfig = static::GetConfig();
if (!$oConfig instanceof Config) {
return static::GetLevelDefault();
}
$sLogLevelMin = $oConfig->Get($sCode);
if (empty($sLogLevelMin)) {
return $sClass;
}
if (!is_array($sLogLevelMin)) {
return $sClass;
}
$sParentClass = $sClass;
while (
(!isset($sLogLevelMin[$sParentClass]))
&&
($sParentClass !== false)
)
{
$sParentClass = get_parent_class($sParentClass);
}
if (isset($sLogLevelMin[$sParentClass])) {
return $sParentClass;
}
return $sClass;
}
/**
* @inheritDoc
*/
public static function Enable($sTargetFile = null)
{
if (empty($sTargetFile))
{
$sTargetFile = APPROOT.'log/error.log';
}
parent::Enable($sTargetFile);
}
private static function WriteToDb(array $aContext): void
{
$oContextException = $aContext[self::CONTEXT_EXCEPTION];
unset($aContext[self::CONTEXT_EXCEPTION]);
if (MetaModel::IsLogEnabledIssue()) {
if (MetaModel::IsValidClass('EventIssue')) {
try {
self::$oLastEventIssue = new EventIssue();
$sIssue = ($oContextException instanceof CoreException) ? $oContextException->GetIssue() : 'PHP Exception';
$sErrorStackTrace = ($oContextException instanceof CoreException) ? $oContextException->getFullStackTraceAsString() : $oContextException->getTraceAsString();
$aContextData = ($oContextException instanceof CoreException) ? $oContextException->getContextData() : [];
self::$oLastEventIssue->Set('message', $oContextException->getMessage());
self::$oLastEventIssue->Set('userinfo', '');
self::$oLastEventIssue->Set('issue', $sIssue);
self::$oLastEventIssue->Set('impact', '');
self::$oLastEventIssue->Set('callstack', $sErrorStackTrace);
self::$oLastEventIssue->Set('data', array_merge($aContextData, $aContext));
self::$oLastEventIssue->DBInsertNoReload();
}
catch (Exception $e) {
IssueLog::Error("Failed to log issue into the DB");
}
}
}
}
/**
* @internal Used by the tests
*/
private static function GetLastEventIssue()
{
return self::$oLastEventIssue;
}
}

View File

@@ -17,6 +17,8 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once APPROOT.'core/modulehandler.class.inc.php';
require_once APPROOT.'core/querymodifier.class.inc.php';
require_once APPROOT.'core/metamodelmodifier.inc.php';
@@ -443,10 +445,10 @@ abstract class MetaModel
/**
* @param string $sClass
* @param bool $bImgTag
* @param string $sMoreStyles
* @param bool $bImgTag Whether to surround the icon URL with an HTML IMG tag or not
* @param string $sMoreStyles Additional inline CSS style to add to the IMG tag. Only used if $bImgTag is set to true
*
* @return string
* @return string Absolute URL the class icon
* @throws \CoreException
*/
final public static function GetClassIcon($sClass, $bImgTag = true, $sMoreStyles = '')
@@ -457,7 +459,7 @@ abstract class MetaModel
if (array_key_exists('style', self::$m_aClassParams[$sClass])) {
/** @var ormStyle $oStyle */
$oStyle = self::$m_aClassParams[$sClass]['style'];
$sIcon = $oStyle->GetIcon();
$sIcon = $oStyle->GetIconAsAbsUrl();
}
if (strlen($sIcon) == 0) {
$sParentClass = self::GetParentPersistentClass($sClass);
@@ -492,7 +494,7 @@ abstract class MetaModel
$oStyle = new ormStyle("ibo-class-style--$sClass", "ibo-class-style-alt--$sClass");
}
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIcon()) > 0)) {
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) {
// all the parameters are set, no need to search in the parent classes
return $oStyle;
}
@@ -510,10 +512,10 @@ abstract class MetaModel
$oStyle->SetComplementaryColor($oParentStyle->GetComplementaryColor());
$oStyle->SetAltStyleClass($oParentStyle->GetAltStyleClass());
}
if (strlen($oStyle->GetIcon()) == 0) {
$oStyle->SetIcon($oParentStyle->GetIcon());
if (strlen($oStyle->GetIconAsRelPath()) == 0) {
$oStyle->SetIcon($oParentStyle->GetIconAsRelPath());
}
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIcon()) > 0)) {
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) {
// all the parameters are set, no need to search in the parent classes
return $oStyle;
}
@@ -521,7 +523,7 @@ abstract class MetaModel
$sParentClass = self::GetParentPersistentClass($sParentClass);
}
if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIcon()) == 0)) {
if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIconAsRelPath()) == 0)) {
return null;
}
@@ -752,17 +754,56 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return array
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetNameSpec($sClass)
final public static function GetNameSpec($sClass, $sType = FriendlyNameType::SHORT)
{
self::_check_subclass($sClass);
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
switch ($sType) {
case FriendlyNameType::COMPLEMENTARY:
if (!isset(self::$m_aClassParams[$sClass]["complementary_name_attcode"])) {
return [$sClass, []];
}
$nameRawSpec = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
$sDictName = 'ComplementaryName';
break;
case FriendlyNameType::LONG:
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
if (!isset(self::$m_aClassParams[$sClass]["complementary_name_attcode"])) {
return self::GetNameSpec($sClass, FriendlyNameType::SHORT);
}
$complementaryNameRawSpec = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
if (is_array($nameRawSpec)) {
if (is_array($complementaryNameRawSpec)) {
$nameRawSpec = merge($nameRawSpec, $complementaryNameRawSpec);
} elseif (!empty($nameRawSpec)) {
$nameRawSpec = merge($nameRawSpec, [$complementaryNameRawSpec]);
}
} elseif (empty($nameRawSpec)) {
$nameRawSpec = $complementaryNameRawSpec;
} else {
if (is_array($complementaryNameRawSpec)) {
$nameRawSpec = merge([$nameRawSpec], $complementaryNameRawSpec);
} elseif (!empty($nameRawSpec)) {
$nameRawSpec = [$nameRawSpec, $complementaryNameRawSpec];
}
}
$sDictName = 'LongName';
break;
default:
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
$sDictName = 'Name';
}
if (is_array($nameRawSpec)) {
$sFormat = Dict::S("Class:$sClass/Name", '');
$sFormat = Dict::S("Class:$sClass/$sDictName", '');
if (strlen($sFormat) == 0) {
// Default to "%1$s %2$s..."
for ($i = 1; $i <= count($nameRawSpec); $i++) {
@@ -774,12 +815,12 @@ abstract class MetaModel
}
}
return array($sFormat, $nameRawSpec);
return [$sFormat, $nameRawSpec];
} elseif (empty($nameRawSpec)) {
return array($sClass, array());
return [$sClass, []];
} else {
// string -> attcode
return array('%1$s', array($nameRawSpec));
return ['%1$s', [$nameRawSpec]];
}
}
@@ -787,24 +828,38 @@ abstract class MetaModel
*
* @param string $sClass
* @param bool $bWithAttributeDefinition
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return array of attribute codes used by friendlyname
* @throws \CoreException
* @since 3.0.0
*/
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false): array
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false, $sType = FriendlyNameType::SHORT): array
{
self::_check_subclass($sClass);
$rawNameAttCodes = self::$m_aClassParams[$sClass]["name_attcode"];
$aNameAttCodes = [];
if (!is_array($rawNameAttCodes)) {
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
$aNameAttCodes[] = $rawNameAttCodes;
if ($sType == FriendlyNameType::SHORT || FriendlyNameType::LONG) {
$rawNameAttCodes = self::$m_aClassParams[$sClass]["name_attcode"];
if (!is_array($rawNameAttCodes)) {
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
$aNameAttCodes[] = $rawNameAttCodes;
}
} else {
$aNameAttCodes = $rawNameAttCodes;
}
}
if ($sType == FriendlyNameType::COMPLEMENTARY || FriendlyNameType::LONG) {
$rawNameAttCodes = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
if (!isEmpty($rawNameAttCodes)) {
if (!is_array($rawNameAttCodes)) {
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
$aNameAttCodes[] = array_merge($aNameAttCodes, [$rawNameAttCodes]);
}
} else {
$aNameAttCodes = array_merge($rawNameAttCodes, $rawNameAttCodes);
}
}
} else {
$aNameAttCodes = $rawNameAttCodes;
}
if ($bWithAttributeDefinition) {
$aResults = [];
foreach ($aNameAttCodes as $sAttCode) {
@@ -842,68 +897,21 @@ abstract class MetaModel
return $aResults;
}
/**
* @param string $sClass
*
* @return array
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
final static public function GetComplementAttributeSpec($sClass)
{
self::_check_subclass($sClass);
if (!isset(self::$m_aClassParams[$sClass]["name_complement_for_select"]))
{
$sParentClass = static::GetParentClass($sClass);
if (is_null($sParentClass)) {
return array($sClass, array());
} else {
return static::GetComplementAttributeSpec($sParentClass);
}
}
$nameRawSpec = self::$m_aClassParams[$sClass]["name_complement_for_select"];
if (is_array($nameRawSpec))
{
$sFormat = Dict::S("Class:$sClass/ComplementForSelect", '');
if (strlen($sFormat) == 0)
{
// Default to "%1$s %2$s..."
for($i = 1; $i <= count($nameRawSpec); $i++)
{
if (empty($sFormat))
{
$sFormat .= '%'.$i.'$s';
}
else
{
$sFormat .= ' %'.$i.'$s';
}
}
}
return array($sFormat, $nameRawSpec);
}
elseif (empty($nameRawSpec))
{
return array($sClass, array());
}
else
{
// string -> attcode
return array('%1$s', array($nameRawSpec));
}
}
/**
* Get the friendly name expression for a given class
*
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return Expression
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetNameExpression($sClass)
final public static function GetNameExpression($sClass, $sType = FriendlyNameType::SHORT)
{
$aNameSpec = self::GetNameSpec($sClass);
$aNameSpec = self::GetNameSpec($sClass, $sType);
$sFormat = $aNameSpec[0];
$aAttributes = $aNameSpec[1];
@@ -935,14 +943,17 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return string The friendly name IIF it is equivalent to a single attribute
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetFriendlyNameAttributeCode($sClass)
final public static function GetFriendlyNameAttributeCode($sClass, $sType = FriendlyNameType::SHORT)
{
$aNameSpec = self::GetNameSpec($sClass);
$aNameSpec = self::GetNameSpec($sClass, $sType);
$sFormat = trim($aNameSpec[0]);
$aAttributes = $aNameSpec[1];
if (($sFormat != '') && ($sFormat != '%1$s')) {
@@ -958,13 +969,16 @@ abstract class MetaModel
/**
* Returns the list of attributes composing the friendlyname
*
* @param $sClass
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return array
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetFriendlyNameAttributeCodeList($sClass)
final public static function GetFriendlyNameAttributeCodeList($sClass, $sType = FriendlyNameType::SHORT)
{
$aNameSpec = self::GetNameSpec($sClass);
$aNameSpec = self::GetNameSpec($sClass, $sType);
$aAttributes = $aNameSpec[1];
return $aAttributes;
@@ -1116,7 +1130,6 @@ abstract class MetaModel
*/
final public static function GetFilterCodeOrigin($sClass, $sAttCode)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
self::_check_subclass($sClass);
return self::$m_aFilterOrigins[$sClass][$sAttCode];
@@ -1134,7 +1147,8 @@ abstract class MetaModel
{
self::_check_subclass($sClass);
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
return $oAtt->GetPrerequisiteAttributes();
return $oAtt->GetPrerequisiteAttributes($sClass);
}
/**
@@ -5115,7 +5129,7 @@ abstract class MetaModel
foreach($aErrors as $sClass => $aMessages)
{
echo "<p>Wrong declaration for class <b>$sClass</b></p>\n";
echo "<ul class=\"treeview\">\n";
echo "<ul >\n";
$i = 0;
foreach($aMessages as $sMsg)
{
@@ -6510,6 +6524,7 @@ abstract class MetaModel
*/
public static function LoadConfig($oConfiguration, $bAllowCache = false)
{
$oKPI = new ExecutionKPI();
self::$m_oConfig = $oConfiguration;
// N°2478 utils has his own private attribute
@@ -6530,6 +6545,7 @@ abstract class MetaModel
ToolsLog::Enable(APPROOT.'log/tools.log');
DeadLockLog::Enable();
DeprecatedCallsLog::Enable();
ExceptionLog::Enable();
}
else
{
@@ -6586,6 +6602,7 @@ abstract class MetaModel
$sSource = self::$m_oConfig->Get('db_name');
$sTablePrefix = self::$m_oConfig->Get('db_subname');
$oKPI->ComputeAndReport('Load config');
if (self::$m_bUseAPCCache)
{
@@ -6957,7 +6974,7 @@ abstract class MetaModel
* @param int $iKey id value of the object to retrieve
* @param bool $bMustBeFound see throws ArchivedObjectException
* @param bool $bAllowAllData if true then user rights will be bypassed - use with care!
* @param null $aModifierProperties
* @param array $aModifierProperties properties for {@see iQueryModifier} impl
*
* @return \DBObject null if : (the object is not found) or (archive mode disabled and object is archived and
* $bMustBeFound=false)
@@ -6977,12 +6994,9 @@ abstract class MetaModel
if (!utils::IsArchiveMode() && $oObject->IsArchived())
{
if ($bMustBeFound)
{
if ($bMustBeFound) {
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
}
else
{
} else {
return null;
}
}
@@ -6990,6 +7004,35 @@ abstract class MetaModel
return $oObject;
}
/**
* @param string $sClass
* @param int $iKey
*
* @return bool True if the object of $sClass and $iKey exists in the DB -no matter the current user restrictions-, false otherwise meaning:
* - It could be in memory for now and is not persisted yet
* - It is neither in memory nor DB
*
* @throws \CoreException
* @throws \MySQLException
* @throws \MySQLQueryHasNoResultException
* @since 3.0.0 N°4173
*/
public static function IsObjectInDB(string $sClass, int $iKey): bool
{
// Note: We take the root class to ensure that there is a corresponding table in the DB
// as some intermediate classes can have no table in the DB.
$sRootClass = MetaModel::GetRootClass($sClass);
$sTable = MetaModel::DBGetTable($sRootClass);
$sKeyCol = MetaModel::DBGetKey($sRootClass);
$sEscapedKey = CMDBSource::Quote($iKey);
$sQuery = "SELECT count(*) FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
$iCount = (int) CMDBSource::QueryToScalar($sQuery);
return $iCount === 1;
}
/**
* Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4
* module compatibility, see N.1108)
@@ -7190,21 +7233,6 @@ abstract class MetaModel
return $oRet;
}
/**
* @deprecated 2.7.0 N°1627, use ItopCounter::incRootClass($sClass) instead
*
* @param string $sClass
*
* @return int
* @throws \CoreException
*/
public static function GetNextKey($sClass)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ItopCounter::incRootClass($sClass) instead');
return ItopCounter::IncClass($sClass);
}
/**
* Deletion of records, bypassing {@link DBObject::DBDelete} !!!
* It is NOT recommended to use this shortcut
@@ -7244,7 +7272,6 @@ abstract class MetaModel
*/
public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
// $aValues is an array of $sAttCode => $value
$sSQL = $oFilter->MakeUpdateQuery($aValues);
if (!self::DBIsReadOnly()) {
@@ -7691,14 +7718,18 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sAttCode
* @param string $sValue
* @param string|null $sValue Code of the state value, can be null if allowed by the attribute definition
*
* @return \ormStyle|null
* @throws \Exception
* @throws \CoreException
*/
public static function GetEnumStyle(string $sClass, string $sAttCode, string $sValue = ''): ?ormStyle
public static function GetEnumStyle(string $sClass, string $sAttCode, ?string $sValue = ''): ?ormStyle
{
if (strlen($sAttCode) === 0) {
return null;
}
$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
if (!$oAttDef instanceof AttributeEnum) {
throw new CoreException("MetaModel::GetEnumStyle() Attribute $sAttCode of class $sClass is not an AttributeEnum\n");

View File

@@ -7,41 +7,50 @@
/**
* Class ormStyle
*
* @since 3.0
* @since 3.0.0
*/
class ormStyle
{
/** @var string */
/** @var string|null */
protected $sMainColor;
/** @var string */
/** @var string|null */
protected $sComplementaryColor;
/** @var string CSS class with color and background-color */
/** @var string|null CSS class with color and background-color */
protected $sStyleClass;
/** @var string CSS class with only color */
/** @var string|null CSS class with only color */
protected $sAltStyleClass;
/** @var string */
/** @var string|null */
protected $sDecorationClasses;
/** @var string */
/** @var string|null Relative path (from current environment) to the icon */
protected $sIcon;
/**
* ormStyle constructor.
*
* @param string $sStyleClass
* @param string $sAltStyleClass
* @param string|null $sStyleClass
* @param string|null $sAltStyleClass
* @param string|null $sMainColor
* @param string|null $sComplementaryColor
* @param string|null $sDecorationClasses
* @param string|null $sIcon
*/
public function __construct(string $sStyleClass, string $sAltStyleClass, string $sMainColor = null, string $sComplementaryColor = null, string $sDecorationClasses = null, string $sIcon = null)
public function __construct(?string $sStyleClass = null, ?string $sAltStyleClass = null, ?string $sMainColor = null, ?string $sComplementaryColor = null, ?string $sDecorationClasses = null, ?string $sIcon = null)
{
$this->sMainColor = $sMainColor;
$this->sComplementaryColor = $sComplementaryColor;
$this->sStyleClass = $sStyleClass;
$this->sAltStyleClass = $sAltStyleClass;
$this->sDecorationClasses = $sDecorationClasses;
$this->sIcon = $sIcon;
$this->SetMainColor($sMainColor);
$this->SetComplementaryColor($sComplementaryColor);
$this->SetStyleClass($sStyleClass);
$this->SetAltStyleClass($sAltStyleClass);
$this->SetDecorationClasses($sDecorationClasses);
$this->SetIcon($sIcon);
}
/**
* @see static::$sMainColor
* @return bool
*/
public function HasMainColor(): bool
{
return strlen($this->sMainColor) > 0;
}
/**
@@ -59,10 +68,19 @@ class ormStyle
*/
public function SetMainColor(?string $sMainColor)
{
$this->sMainColor = $sMainColor;
$this->sMainColor = (strlen($sMainColor) === 0) ? null : $sMainColor;
return $this;
}
/**
* @see static::$sComplementaryColor
* @return bool
*/
public function HasComplementaryColor(): bool
{
return strlen($this->sComplementaryColor) > 0;
}
/**
* @return string
*/
@@ -78,14 +96,33 @@ class ormStyle
*/
public function SetComplementaryColor(?string $sComplementaryColor)
{
$this->sComplementaryColor = $sComplementaryColor;
$this->sComplementaryColor = (strlen($sComplementaryColor) === 0) ? null : $sComplementaryColor;
return $this;
}
/**
* @see static::$sMainColor
* @see static::$sComplementaryColor
* @return bool
*/
public function HasAtLeastOneColor(): bool
{
return $this->HasMainColor() || $this->HasComplementaryColor();
}
/**
* @see static::$sStyleClass
* @return bool
*/
public function HasStyleClass(): bool
{
return strlen($this->sStyleClass) > 0;
}
/**
* @return string
*/
public function GetStyleClass(): string
public function GetStyleClass(): ?string
{
return $this->sStyleClass;
}
@@ -95,16 +132,25 @@ class ormStyle
*
* @return $this
*/
public function SetStyleClass(string $sStyleClass)
public function SetStyleClass(?string $sStyleClass)
{
$this->sStyleClass = $sStyleClass;
$this->sStyleClass = (strlen($sStyleClass) === 0) ? null : $sStyleClass;
return $this;
}
/**
* @see static::$sAltStyleClass
* @return bool
*/
public function HasAltStyleClass(): bool
{
return strlen($this->sAltStyleClass) > 0;
}
/**
* @return string
*/
public function GetAltStyleClass(): string
public function GetAltStyleClass(): ?string
{
return $this->sAltStyleClass;
}
@@ -114,12 +160,21 @@ class ormStyle
*
* @return $this
*/
public function SetAltStyleClass(string $sAltStyleClass)
public function SetAltStyleClass(?string $sAltStyleClass)
{
$this->sAltStyleClass = $sAltStyleClass;
$this->sAltStyleClass = (strlen($sAltStyleClass) === 0) ? null : $sAltStyleClass;
return $this;
}
/**
* @see static::$sDecorationClasses
* @return bool
*/
public function HasDecorationClasses(): bool
{
return strlen($this->sDecorationClasses) > 0;
}
/**
* @return string
*/
@@ -135,16 +190,17 @@ class ormStyle
*/
public function SetDecorationClasses(?string $sDecorationClasses)
{
$this->sDecorationClasses = $sDecorationClasses;
$this->sDecorationClasses = (strlen($sDecorationClasses) === 0) ? null : $sDecorationClasses;
return $this;
}
/**
* @return string
* @see static::$sIcon
* @return bool
*/
public function GetIcon(): ?string
public function HasIcon(): bool
{
return $this->sIcon;
return strlen($this->sIcon) > 0;
}
/**
@@ -154,8 +210,30 @@ class ormStyle
*/
public function SetIcon(?string $sIcon)
{
$this->sIcon = $sIcon;
$this->sIcon = (strlen($sIcon) === 0) ? null : $sIcon;
return $this;
}
/**
* @see static::$sIcon
* @return string|null Relative path (from the current environment) of the icon
*/
public function GetIconAsRelPath(): ?string
{
return $this->sIcon;
}
/**
* @see static::$sIcon
* @return string|null Absolute URL of the icon
* @throws \Exception
*/
public function GetIconAsAbsUrl(): ?string
{
if (is_null($this->sIcon)) {
return null;
}
return utils::GetAbsoluteUrlModulesRoot().$this->sIcon;
}
}

View File

@@ -16,6 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSectionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Renderer\BlockRenderer;
define('CASELOG_VISIBLE_ITEMS', 2);
define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
@@ -168,7 +174,6 @@ class ormCaseLog {
return $aEntries;
}
/**
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
* @return string
@@ -201,6 +206,15 @@ class ormCaseLog {
{
return ($this->m_sLog === null);
}
/**
* @return int The number of entries in this log
* @since 3.0.0
*/
public function GetEntryCount(): int
{
return count($this->m_aIndex);
}
public function ClearModifiedFlag()
{
@@ -389,7 +403,7 @@ class ormCaseLog {
{
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
$sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
$oBlock = UIContentBlockUIBlockFactory::MakeStandard(null, ['ibo-caselog-list']);
$iPos = 0;
$aIndex = $this->m_aIndex;
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
@@ -403,20 +417,16 @@ class ormCaseLog {
{
if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
{
$sOpen = '';
$sDisplay = 'style="display:none;"';
$bIsOpen = false;
}
else
{
$sOpen = ' open';
$sDisplay = '';
$bIsOpen = true;
}
$iPos += $aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
$sCSSClass= 'caselog_entry_html';
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
{
$sCSSClass= 'caselog_entry';
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
{
@@ -433,7 +443,6 @@ class ormCaseLog {
}
$iPos += $aIndex[$index]['text_length'];
$sEntry = '<div class="caselog_header'.$sOpen.'">';
// Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
// therefore we have changed the format. To preserve the compatibility with existing
// installations of iTop, both format are allowed:
@@ -456,14 +465,11 @@ class ormCaseLog {
$sDate = '';
}
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
$sEntry .= '</div>';
$sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
$sEntry .= $sTextEntry;
$sEntry .= '</div>';
$sHtml = $sHtml.$sEntry;
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']));
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
$oCollapsibleBlock->SetOpenedByDefault($bIsOpen);
$oBlock->AddSubBlock($oCollapsibleBlock);
}
// Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (strlen($this->m_sLog) - 1))
{
@@ -477,48 +483,71 @@ class ormCaseLog {
if (count($this->m_aIndex) == 0)
{
$sHtml .= '<div class="caselog_entry open">';
$sHtml .= $sTextEntry;
$sHtml .= '</div>';
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( '');
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
$oCollapsibleBlock->SetOpenedByDefault(true);
$oBlock->AddSubBlock($oCollapsibleBlock);
}
else
{
if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
{
$sOpen = '';
$sDisplay = 'style="display:none;"';
$bIsOpen = false;
}
else
{
$sOpen = ' open';
$sDisplay = '';
$bIsOpen = true;
}
$sHtml .= '<div class="caselog_header'.$sOpen.'">';
$sHtml .= Dict::S('UI:CaseLog:InitialValue');
$sHtml .= '</div>';
$sHtml .= '<div class="caselog_entry"'.$sDisplay.'>';
$sHtml .= $sTextEntry;
$sHtml .= '</div>';
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( Dict::S('UI:CaseLog:InitialValue'));
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
$oCollapsibleBlock->SetOpenedByDefault($bIsOpen);
}
}
$sHtml .= '</td></tr></table>';
return $sHtml;
$oBlockRenderer = new BlockRenderer($oBlock);
$sHtml = $oBlockRenderer->RenderHtml();
$sScript = $oBlockRenderer->RenderJsInlineRecursively($oBlock,iUIBlock::ENUM_JS_TYPE_ON_READY);
if ($sScript!=''){
if ($oP == null) {
$sScript = '<script>'.$sScript.'</script>';
$sHtml .= $sScript;
} else {
$oP->add_ready_script($sScript);
}
}
return $sHtml;
}
/**
* Add a new entry to the log or merge the given text into the currently modified entry
* Add a new entry to the log or merge the given text into the currently modified entry
* and updates the internal index
* @param $sText string The text of the new entry
*
* @param string $sText The text of the new entry
* @param string $sOnBehalfOf Display this name instead of current user name
* @param null|int $iOnBehalfOfId Use this UserId to author this Entry. If $sOnBehalfOf equals '', it'll be replaced by this User friendlyname
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
* @since 3.0.0 New $iOnBehalfOfId parameter
* @since 3.0.0 May throw \ArchivedObjectException exception
*/
public function AddLogEntry($sText, $sOnBehalfOf = '')
public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = null)
{
$sText = HTMLSanitizer::Sanitize($sText);
$sDate = date(AttributeDateTime::GetInternalFormat());
if ($sOnBehalfOf == '')
{
if ($sOnBehalfOf == '' && $iOnBehalfOfId === null) {
$sOnBehalfOf = UserRights::GetUserFriendlyName();
$iUserId = UserRights::GetUserId();
}
elseif ($iOnBehalfOfId !== null) {
$iUserId = $iOnBehalfOfId;
/* @var User $oUser */
$oUser = MetaModel::GetObject('User', $iUserId, false, true);
if ($oUser !== null && $sOnBehalfOf === '') {
$sOnBehalfOf = $oUser->GetFriendlyName();
}
}
else
{
$iUserId = null;
@@ -553,7 +582,6 @@ class ormCaseLog {
$this->m_bModified = true;
}
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
if (isset($oJson->user_id))
@@ -630,7 +658,6 @@ class ormCaseLog {
$this->m_bModified = true;
}
public function GetModifiedEntry($sFormat = 'text')
{
$sModifiedEntry = '';
@@ -709,4 +736,3 @@ class ormCaseLog {
return $sText;
}
}
?>

View File

@@ -803,6 +803,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
$oLinkSearch = $this->GetFilter();
if ($oAttDef->IsIndirect())
{
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
/** @var \AttributeExternalKey $oLinkingAttDef */
$oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote);
@@ -810,12 +811,12 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
// N°2334 add pointed class (SELECT L,R) to have all fields (lnk + remote) in display
// the pointed class is always present in the search, as generated by \AttributeLinkedSet::GetDefaultValue
$sTargetClass = $oLinkingAttDef->GetTargetClass();
$oRemoteClassSearch = new DBObjectSearch($sTargetClass);
$oRemoteClassSearch = new DBObjectSearch($sTargetClass, self::REMOTE_ALIAS);
if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass))
{
$oNotObsolete = new BinaryExpression(
new FieldExpression('obsolescence_flag', $sTargetClass),
new FieldExpression('obsolescence_flag', self::REMOTE_ALIAS),
'=',
new ScalarExpression(0)
);
@@ -825,7 +826,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
if (!utils::IsArchiveMode() && MetaModel::IsArchivable($sTargetClass))
{
$oNotArchived = new BinaryExpression(
new FieldExpression('archive_flag', $sTargetClass),
new FieldExpression('archive_flag', self::REMOTE_ALIAS),
'=',
new ScalarExpression(0)
);
@@ -833,9 +834,14 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
$oRemoteClassSearch->AddConditionExpression($oNotArchived);
}
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote);
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
$oLinkSearch->RenameAlias($sTargetClass, self::REMOTE_ALIAS);
$aReAliasingMap = [];
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote, TREE_OPERATOR_EQUALS, $aReAliasingMap);
if (array_key_exists(self::REMOTE_ALIAS, $aReAliasingMap)) {
// If 'Remote' alias has been renamed, change it back.
if ($aReAliasingMap[self::REMOTE_ALIAS][0] != self::REMOTE_ALIAS) {
$oLinkSearch->RenameAlias($aReAliasingMap[self::REMOTE_ALIAS][0], self::REMOTE_ALIAS);
}
}
$oLinkSearch->SetSelectedClasses([self::LINK_ALIAS, self::REMOTE_ALIAS]);
}
$oLinkSet = new DBObjectSet($oLinkSearch);

View File

@@ -388,15 +388,18 @@ class SQLObjectQuery extends SQLQuery
{
if (count($this->__aSelectedIdFields) > 0)
{
$aCountFields = array();
foreach ($this->__aSelectedIdFields as $sFieldExpr)
{
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
$aCountFields = [];
$aCountI = [];
$i = 0;
foreach ($this->__aSelectedIdFields as $sFieldExpr) {
$aCountFields[] = "COALESCE($sFieldExpr, 0) AS idCount$i"; // Null values are excluded from the count
$aCountI[] = 'idCount'.$i++;
}
$sCountFields = implode(', ', $aCountFields);
$sCountI = implode('+ ', $aCountI);
// Count can be limited for performance reason, in this case the total amount is not important,
// we only need to know if the number of entries is greater than a certain amount.
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _alderaan_";
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _alderaan_ WHERE $sCountI>0";
}
else
{

View File

@@ -1,4 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
define('UR_ALLOWED_NO', 0);
define('UR_ALLOWED_YES', 1);
define('UR_ALLOWED_DEPENDS', 2);
@@ -318,7 +321,14 @@ abstract class User extends cmdbAbstractObject
{
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
{
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
$this->oContactObject = null;
// The User Contact is generally a Person, so try it first
if (MetaModel::IsValidClass('Person')) {
$this->oContactObject = MetaModel::GetObject('Person', $this->Get('contactid'), false);
}
if (is_null($this->oContactObject)) {
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
}
}
}
return $this->oContactObject;
@@ -333,77 +343,135 @@ abstract class User extends cmdbAbstractObject
{
parent::DoCheckToWrite();
// Note: This MUST be factorized later: declare unique keys (set of columns) in the data model
$oAddon = UserRights::GetModuleInstance();
$aChanges = $this->ListChanges();
if (array_key_exists('login', $aChanges))
{
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0)
{
if (array_key_exists('login', $aChanges)) {
// Check login uniqueness
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0) {
$sNewLogin = $aChanges['login'];
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
if (!$this->IsNew())
{
if (!$this->IsNew()) {
$oSearch->AddCondition('id', $this->GetKey(), '!=');
}
$oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin));
if ($oSet->Count() > 0)
{
if ($oSet->Count() > 0) {
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin);
}
}
}
// Check that this user has at least one profile assigned when profiles have changed
if (array_key_exists('profile_list', $aChanges))
{
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
// A User cannot disable himself
if ($this->IsCurrentUser()) {
if (isset($aChanges['status']) && ($this->Get('status') == 'disabled')) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:StatusChangeIsNotAllowed');
}
}
// Check that this user has at least one profile assigned when profiles have changed
if (array_key_exists('profile_list', $aChanges)) {
/** @var \DBObjectSet $oSet */
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
}
// A user cannot add to themself a profile denying the access to the backoffice
$aForbiddenProfiles = PortalDispatcherData::GetData('backoffice')['deny'];
if ($this->IsCurrentUser()) {
$oSet->Rewind();
$aProfiles = [];
while ($oUserProfile = $oSet->Fetch()) {
$sProfile = $oUserProfile->Get('profile');
if (in_array($sProfile, $aForbiddenProfiles)) {
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:ProfileNotAllowed', $sProfile);
}
$aProfiles[$oUserProfile->Get('profileid')] = $sProfile;
}
if (!in_array(ADMIN_PROFILE_NAME, $aProfiles)) {
// Check if the user is yet allowed to modify Users
if (method_exists($oAddon, 'ResetCache')) {
$aCurrentProfiles = Session::Get('profile_list');
// Set the current profiles into a session variable (not yet in the database)
Session::Set('profile_list', $aProfiles);
$oAddon->ResetCache();
if (!$oAddon->IsActionAllowed($this, 'User', UR_ACTION_MODIFY, null)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
}
$oAddon->ResetCache();
if (is_null($aCurrentProfiles)) {
Session::IsSet('profile_list');
} else {
Session::Set('profile_list', $aCurrentProfiles);
}
}
}
}
}
// Only administrators can manage administrators
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator())
{
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator()) {
$this->m_aCheckIssues[] = Dict::S('UI:Login:Error:AccessRestricted');
}
if (!UserRights::IsAdministrator())
{
$oUser = UserRights::GetUserObject();
$oAddon = UserRights::GetModuleInstance();
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs'))
{
if ((empty($this->GetOriginal('contactid')) && !($this->IsNew())) || empty($this->Get('contactid')))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
// A contact is mandatory (an administrator can bypass it but not for himself)
if ((!UserRights::IsAdministrator() || $this->IsCurrentUser())
&& !$this->IsNew()
&& isset($aChanges['contactid'])
&& empty($this->Get('contactid'))) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
}
// Allowed orgs must contains the user org (if any)
if (!empty($this->Get('org_id')) && !UserRights::IsAdministrator($this)) {
// Get the user org and all its parent orgs
$aUserOrgs = [$this->Get('org_id')];
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode !== false) {
$sOrgQuery = 'SELECT Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' ABOVE Root.id WHERE Root.id = :id';
$oOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sOrgQuery), [], ['id' => $this->Get('org_id')]);
while ($aRow = $oOrgSet->FetchAssoc()) {
$oOrg = $aRow['Org'];
$aUserOrgs[] = $oOrg->GetKey();
}
else
{
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
if (count($aOrgs) > 0)
{
// Check that the modified User belongs to one of our organization
if (!in_array($this->GetOriginal('org_id'), $aOrgs) && !in_array($this->Get('org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:UserOrganizationNotAllowed');
}
// Check users with restricted organizations when allowed organizations have changed
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges))
{
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
}
else
{
$aModifiedLinks = $oSet->ListModifiedLinks();
foreach ($aModifiedLinks as $oLink)
{
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
}
}
// Check the allowed orgs list
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() > 0) {
$bFound = false;
while ($oOrg = $oSet->Fetch()) {
if (in_array($oOrg->Get('allowed_org_id'), $aUserOrgs)) {
$bFound = true;
break;
}
}
if (!$bFound) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AllowedOrgsMustContainUserOrg');
}
}
}
if (!UserRights::IsAdministrator()) {
$oUser = UserRights::GetUserObject();
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs')) {
$aOrgs = $oAddon->GetUserOrgs($oUser, ''); // Modifier allowed orgs
if (count($aOrgs) > 0) {
// Check that the modified User belongs to one of our organization
if (!in_array($this->GetOriginal('org_id'), $aOrgs) && !in_array($this->Get('org_id'), $aOrgs)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:UserOrganizationNotAllowed');
}
// Check users with restricted organizations when allowed organizations have changed
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges)) {
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() == 0) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
} else {
$aModifiedLinks = $oSet->ListModifiedLinks();
foreach ($aModifiedLinks as $oLink) {
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
}
}
}
@@ -413,14 +481,26 @@ abstract class User extends cmdbAbstractObject
}
}
/**
* @inheritDoc
* @since 3.0.0
*/
public function DoCheckToDelete(&$oDeletionPlan)
{
parent::DoCheckToDelete($oDeletionPlan);
// A user cannot suppress himself
if ($this->IsCurrentUser()) {
$this->m_bSecurityIssue = true;
$this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete');
}
}
function GetGrantAsHtml($sClass, $iAction)
{
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this))
{
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this)) {
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
}
else
{
} else {
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
@@ -528,6 +608,19 @@ abstract class User extends cmdbAbstractObject
}
parent::DBDeleteSingleObject();
}
/**
* @return bool
* @throws \OQLException
* @since 3.0.0
*/
protected function IsCurrentUser(): bool
{
if (is_null(UserRights::GetUserId())) {
return false;
}
return UserRights::GetUserId() == $this->GetKey();
}
}
/**
@@ -757,10 +850,10 @@ class UserRights
}
self::$m_oUser = $oUser;
if (isset($_SESSION['impersonate_user']))
if (Session::IsSet('impersonate_user'))
{
self::$m_oRealUser = self::$m_oUser;
self::$m_oUser = self::FindUser($_SESSION['impersonate_user']);
self::$m_oUser = self::FindUser(Session::Get('impersonate_user'));
}
Dict::SetUserLanguage(self::GetUserLanguage());
@@ -864,15 +957,15 @@ class UserRights
{
$bRet = false;
}
elseif (isset($_SESSION['archive_allowed']))
elseif (Session::IsSet('archive_allowed'))
{
$bRet = $_SESSION['archive_allowed'];
$bRet = Session::Get('archive_allowed');
}
else
{
// As of now, anybody can switch to the archive mode as soon as there is an archivable class
$bRet = (count(MetaModel::EnumArchivableClasses()) > 0);
$_SESSION['archive_allowed'] = $bRet;
Session::Set('archive_allowed', $bRet);
}
return $bRet;
}
@@ -958,7 +1051,7 @@ class UserRights
// Do impersonate!
self::$m_oUser = $oUser;
Dict::SetUserLanguage(self::GetUserLanguage());
$_SESSION['impersonate_user'] = $sLogin;
Session::Set('impersonate_user', $sLogin);
self::_ResetSessionCache();
}
}
@@ -974,7 +1067,7 @@ class UserRights
{
self::$m_oUser = self::$m_oRealUser;
Dict::SetUserLanguage(self::GetUserLanguage());
unset($_SESSION['impersonate_user']);
Session::Unset('impersonate_user');
self::_ResetSessionCache();
}
}
@@ -1011,6 +1104,30 @@ class UserRights
}
}
/**
* @param Person $oPerson Person we try to match against Users contact (also Person objects)
* @param bool $bMustBeUnique If true, return null when 2+ Users matching this Person were found. Otherwise return the first one
*
* @return \DBObject|null
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @since 3.0.0
*/
public static function GetUserFromPerson(Person $oPerson, bool $bMustBeUnique = true): ?DBObject
{
$sUserSearch = 'SELECT User WHERE contactid = :id';
$oUserSearch = DBObjectSearch::FromOQL($sUserSearch);
$oUserSearch->AllowAllData();
$oUserSet = new DBObjectSet($oUserSearch, array(), array('id' => $oPerson->GetKey()));
if($oUserSet->Count() > 0 && !($oUserSet->Count() > 1 && $bMustBeUnique)){
return $oUserSet->Fetch();
}
return null;
}
/**
* @return string
*/
@@ -1086,17 +1203,26 @@ class UserRights
// Then check if the user has a contact attached and if it has an picture defined
$sContactId = UserRights::GetContactId($sLogin);
if (!empty($sContactId)) {
$oContact = MetaModel::GetObject('Contact', $sContactId, false, true);
$oContact = null;
// Picture if generally for Person, so try it first
if (MetaModel::IsValidClass('Person')) {
$oContact = MetaModel::GetObject('Person', $sContactId, false, true);
}
if (is_null($oContact)) {
$oContact = MetaModel::GetObject('Contact', $sContactId, false, true);
}
$sContactClass = get_class($oContact);
// Check that Contact object still exists and that Contact class has a picture attribute
if (!is_null($oContact) && MetaModel::IsValidAttCode($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE)) {
// - Try to get the semantic image attribute, or try to fallback on the default one if none defined
$sContactPictureAttCode = MetaModel::HasImageAttributeCode($sContactClass) ? MetaModel::GetImageAttributeCode($sContactClass) : static::DEFAULT_CONTACT_PICTURE_ATTCODE;
if (!is_null($oContact) && MetaModel::IsValidAttCode($sContactClass, $sContactPictureAttCode)) {
/** @var \ormDocument $oPicture */
$oPicture = $oContact->Get(static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$oPicture = $oContact->Get($sContactPictureAttCode);
if ($oPicture->IsEmpty()) {
if ($bAllowDefaultPicture === true) {
/** @var \AttributeImage $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$oAttDef = MetaModel::GetAttributeDef($sContactClass, $sContactPictureAttCode);
$sPictureUrl = $oAttDef->Get('default_image');
} else {
$sPictureUrl = null;
@@ -1104,9 +1230,9 @@ class UserRights
} else {
if (ContextTag::Check(ContextTag::TAG_PORTAL)) {
$sSignature = $oPicture->GetSignature();
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.static::DEFAULT_CONTACT_PICTURE_ATTCODE.'?cache=86400&s='.$sSignature.'&exec_module=itop-portal-base&exec_page=index.php&portal_id='.PORTAL_ID;
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.$sContactPictureAttCode.'?cache=86400&s='.$sSignature.'&exec_module=itop-portal-base&exec_page=index.php&portal_id='.PORTAL_ID;
} else {
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), $sContactPictureAttCode);
}
}
}
@@ -1648,9 +1774,9 @@ class UserRights
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
{
// Data about the current user can be found into the session data
if (array_key_exists('profile_list', $_SESSION))
if (Session::IsSet('profile_list'))
{
$aProfiles = $_SESSION['profile_list'];
$aProfiles = Session::Get('profile_list');
}
}
@@ -1685,11 +1811,6 @@ class UserRights
self::$m_aAdmins = array();
self::$m_aPortalUsers = array();
}
if (!isset($_SESSION) && !utils::IsModeCLI())
{
session_name('itop-'.md5(APPROOT));
session_start();
}
self::_ResetSessionCache();
if (self::$m_oAddOn)
{
@@ -1724,7 +1845,7 @@ class UserRights
self::$m_aCacheUsers = array('internal' => array(), 'external' => array());
}
if (!isset(self::$m_aCacheUsers[$sAuthentication][$sLogin]))
if (!array_key_exists($sLogin, self::$m_aCacheUsers[$sAuthentication]))
{
switch($sAuthentication)
{
@@ -1774,10 +1895,7 @@ class UserRights
public static function _InitSessionCache()
{
// Cache data about the current user into the session
if (isset($_SESSION))
{
$_SESSION['profile_list'] = self::ListProfiles();
}
Session::Set('profile_list', self::ListProfiles());
$oConfig = MetaModel::GetConfig();
$bSessionIdRegeneration = $oConfig->Get('regenerate_session_id_enabled');
@@ -1799,14 +1917,8 @@ class UserRights
public static function _ResetSessionCache()
{
if (isset($_SESSION['profile_list']))
{
unset($_SESSION['profile_list']);
}
if (isset($_SESSION['archive_allowed']))
{
unset($_SESSION['archive_allowed']);
}
Session::Unset('profile_list');
Session::Unset('archive_allowed');
}
/**

View File

@@ -24,6 +24,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once('MyHelpers.class.inc.php');
/**
@@ -379,41 +381,35 @@ class ValueSetObjects extends ValueSetDefinition
$this->m_aValues = array();
if ($this->m_bAllowAllData)
{
if ($this->m_bAllowAllData) {
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
}
else
{
} else {
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
}
if (!$oFilter) return false;
if (!is_null($this->m_oExtraCondition))
{
if (!$oFilter) {
return false;
}
if (!is_null($this->m_oExtraCondition)) {
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
foreach ($this->m_aModifierProperties as $sPluginClass => $aProperties) {
foreach ($aProperties as $sProperty => $value) {
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
//$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
$sClass = $oFilter->GetClass();
$sClassAlias = $oFilter->GetClassAlias();
switch ($sOperation)
{
switch ($sOperation) {
case 'equals':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains);
foreach($aAttributes as $sAttribute)
{
foreach ($aAttributes as $sAttribute) {
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
@@ -425,7 +421,6 @@ class ValueSetObjects extends ValueSetDefinition
break;
case 'start_with':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains.'%');
foreach($aAttributes as $sAttribute)
@@ -442,63 +437,67 @@ class ValueSetObjects extends ValueSetDefinition
default:
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNameExpr = new FieldExpression('friendlyname', $sClassAlias);
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
break;
}
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
if (empty($this->m_sValueAttCode))
{
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
}
else
{
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
if (empty($this->m_sValueAttCode)) {
$aAttToLoad = ['friendlyname'];
} else {
$aAttToLoad = [$this->m_sValueAttCode];
}
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($sClass);
$sImageAttr = MetaModel::GetImageAttributeCode($sClass);
if (!empty($sImageAttr)) {
$aAttToLoad [] = $sImageAttr;
}
$aComplementAttributeSpec = MetaModel::GetNameSpec($sClass, FriendlyNameType::COMPLEMENTARY);
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField)>0)
{
$aAttToLoad = array_merge ($aAttToLoad, [$oFilter->GetClassAlias() => $aAdditionalField]);
if (count($aAdditionalField) > 0) {
if (is_array($aAdditionalField)) {
$aAttToLoad = array_merge($aAttToLoad, $aAdditionalField);
} else {
$aAttToLoad [] = $aAdditionalField;
}
}
$oObjects->OptimizeColumnLoad($aAttToLoad);
while ($oObject = $oObjects->Fetch())
{
$aData=[];
if (empty($this->m_sValueAttCode))
{
$oObjects->OptimizeColumnLoad([$sClassAlias => $aAttToLoad]);
while ($oObject = $oObjects->Fetch()) {
$aData = [];
if (empty($this->m_sValueAttCode)) {
$aData['label'] = $oObject->GetName();
}
else
{
} else {
$aData['label'] = $oObject->Get($this->m_sValueAttCode);
}
if($oObject->IsObsolete())
{
$aData['obsolescence_flag']='1';
if ($oObject->IsObsolete()) {
$aData['obsolescence_flag'] = '1';
} else {
$aData['obsolescence_flag'] = '0';
}
else
{
$aData['obsolescence_flag']='0';
}
if (count($aAdditionalField)>0)
{
if (count($aAdditionalField) > 0) {
$aArguments = [];
foreach ($aAdditionalField as $sAdditionalField)
{
array_push ($aArguments,$oObject->Get($sAdditionalField));
foreach ($aAdditionalField as $sAdditionalField) {
array_push($aArguments, $oObject->Get($sAdditionalField));
}
$aData['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
} else {
$aData['additional_field'] = '';
}
else
{
$aData['additional_field']='';
if (!empty($sImageAttr)) {
/** @var \ormDocument $oImage */
$oImage = $oObject->Get($sImageAttr);
if (!$oImage->IsEmpty()) {
$aData['picture_url'] = $oImage->GetDisplayURL($sClass, $oObject->GetKey(), $sImageAttr);
$aData['initials'] = '';
} else {
$aData['initials'] = utils::ToAcronym($aData['label']);
}
}
$this->m_aValues[$oObject->GetKey()] = $aData;
}

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
//==========================================================================

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "display-block/all";

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-block-csv--textarea--width: 100% !default;
$ibo-block-csv--textarea--min-height: 10em !default;
$ibo-block-csv--textarea--margin-top: 10px !default;
@@ -8,10 +13,4 @@ $ibo-block-csv--textarea--margin-top: 10px !default;
min-height: $ibo-block-csv--textarea--min-height;
margin-top: $ibo-block-csv--textarea--margin-top;
}
}
.ibo-block-csv--download-link{
@extend .ibo-button;
@extend .ibo-is-alternative;
@extend .ibo-is-primary;
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "tabular-fields-selector";

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "base";

View File

@@ -1,24 +1,12 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-scrollbar--scrollbar-width: 8px !default;
$ibo-scrollbar--scrollbar-height: $ibo-scrollbar--scrollbar-width !default; /* For horizontal scrollbars */
$ibo-scrollbar--scrollbar-track-background-color: $ibo-color-transparent !default;
$ibo-scrollbar--scrollbar-track-border-radius: $ibo-border-radius-500 !default;
$ibo-scrollbar--scrollbar-thumb-background-color: $ibo-color-grey-300 !default;
$ibo-scrollbar--scrollbar-thumb-border: none !default;
$ibo-scrollbar--scrollbar-thumb-border-radius: $ibo-border-radius-500 !default;
@@ -37,6 +25,7 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
--ibo-scrollbar--scrollbar-width: #{$ibo-scrollbar--scrollbar-width};
--ibo-scrollbar--scrollbar-height: #{$ibo-scrollbar--scrollbar-height};
--ibo-scrollbar--scrollbar-track-background-color: #{$ibo-scrollbar--scrollbar-track-background-color};
--ibo-scrollbar--scrollbar-track-border-radius: #{$ibo-scrollbar--scrollbar-track-border-radius};
--ibo-scrollbar--scrollbar-thumb-background-color: #{$ibo-scrollbar--scrollbar-thumb-background-color};
--ibo-scrollbar--scrollbar-thumb-border: #{$ibo-scrollbar--scrollbar-thumb-border};
--ibo-scrollbar--scrollbar-thumb-border-radius: #{$ibo-scrollbar--scrollbar-thumb-border-radius};
@@ -66,6 +55,7 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
}
&::-webkit-scrollbar-track {
background-color: var(--ibo-scrollbar--scrollbar-track-background-color);
border-radius: var(--ibo-scrollbar--scrollbar-track-border-radius);
}
::-webkit-scrollbar-thumb {
background-color: var(--ibo-scrollbar--scrollbar-thumb-background-color);

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* This is an overload of the default lib. stylesheet to use local fonts instead of the CDN */
@@ -21,125 +8,143 @@
font-family: Raleway;
font-weight: 100;
font-style: normal;
font-display: swap;
src: local('Raleway Thin'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Thin.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-100-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 100;
font-style: italic;
font-display: swap;
src: local('Raleway Thin'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Thin-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-100-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 200;
font-style: normal;
font-display: swap;
src: local('Raleway ExtraLight'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraLight.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-200-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 200;
font-style: italic;
font-display: swap;
src: local('Raleway ExtraLight'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraLight-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-200-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 300;
font-style: normal;
font-display: swap;
src: local('Raleway Light'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Light.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-300-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 300;
font-style: italic;
font-display: swap;
src: local('Raleway Light'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Light-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-300-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 400;
font-style: normal;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Regular.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-400-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 400;
font-style: italic;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Regular-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-400-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 500;
font-style: normal;
font-display: swap;
src: local('Raleway Medium'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Medium.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-500-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 500;
font-style: italic;
font-display: swap;
src: local('Raleway Medium'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Medium-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-500-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 600;
font-style: normal;
font-display: swap;
src: local('Raleway SemiBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-SemiBold.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-600-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 600;
font-style: italic;
font-display: swap;
src: local('Raleway SemiBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-SemiBold-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-600-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 700;
font-style: normal;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Bold.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-700-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 700;
font-style: italic;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Bold-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-700-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 800;
font-style: normal;
font-display: swap;
src: local('Raleway ExtraBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraBold.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-800-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 800;
font-style: italic;
font-display: swap;
src: local('Raleway ExtraBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraBold-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-800-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 900;
font-style: normal;
font-display: swap;
src: local('Raleway Black'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Black.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-900-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 900;
font-style: italic;
font-display: swap;
src: local('Raleway Black'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Black-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-900-italic.woff') format('woff'),
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -13,5 +13,7 @@
@import "object-details-with-tab-container";
@import "medallion-with-blocklist";
@import "input-within-datatable";
@import "field-badge-within-datatable";
@import "jquery-blockui-within-dialog";
@import "jquery-blockui-within-datatable";
@import "jquery-blockui-within-datatable";
@import "collapsible-section-within-caselog-list";

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -0,0 +1,29 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-caselog-entry-in-collapsible-section--body--background-color: transparentize($ibo-color-grey-100,0.5) !default;
$ibo-caselog-entry-in-collapsible-section--body--padding: 8px !default;
$ibo-caselog-entry-in-collapsible-section--body--color: $ibo-color-grey-900 !default;
/* - caselog display in ormcaselog */
.ibo-caselog-list {
.ibo-collapsible-section {
margin: 0;
min-width: 22em;
.ibo-collapsible-section--header .ibo-collapsible-section--title {
@extend %ibo-font-size-100;
}
.ibo-collapsible-section--body {
@extend %ibo-font-size-100;
color: $ibo-caselog-entry-in-collapsible-section--body--color;
padding: $ibo-caselog-entry-in-collapsible-section--body--padding;
background-color: $ibo-caselog-entry-in-collapsible-section--body--background-color;
}
}
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -0,0 +1,40 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-field-badge-within-datatable--ibo-field-badge--margin: 0 !default;
$ibo-field-badge-within-datatable--ibo-field-badge--padding: 0 !default;
$ibo-field-badge-within-datatable--ibo-field-badge--text-color: unset !default;
$ibo-field-badge-within-datatable--ibo-field-badge--background-color: unset !default;
$ibo-field-badge-within-datatable--ibo-field-badge-dot--size: 10px !default;
$ibo-field-badge-within-datatable--ibo-field-badge-dot--spacing-x: $ibo-field-badge--label--spacing-x !default;
$ibo-field-badge-within-datatable--ibo-field-badge-dot--background-color: $ibo-color-white-100 !default;
.ibo-datatable {
.ibo-field-badge {
margin: $ibo-field-badge-within-datatable--ibo-field-badge--margin;
padding: $ibo-field-badge-within-datatable--ibo-field-badge--padding;
color: $ibo-field-badge-within-datatable--ibo-field-badge--text-color;
background-color: $ibo-field-badge-within-datatable--ibo-field-badge--background-color;
&::before {
content: "";
display: inline-flex;
margin-right: $ibo-field-badge-within-datatable--ibo-field-badge-dot--spacing-x;
width: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
height: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
min-width: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
min-height: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
background-color: var(--ibo-main-color);
@extend %ibo-border-radius-full;
}
.ibo-field-badge--decoration {
display: none;
+ .ibo-field-badge--label {
margin-left: unset;
}
}
}
}

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-within-datatable--attribute-set-item--padding-x: $ibo-input-set--item--padding-x !default;
$ibo-input-within-datatable--attribute-set-item--box-shadow: $ibo-elevation-100 !default;

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-vendors-blockui--blockoverlay--within--datatable--background-color: $ibo-color-white-100 !default;
.ibo-datatable .blockUI.blockOverlay{

View File

@@ -1,7 +1,8 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-vendors-blockui--blockoverlay--within--dialog--background-color: $ibo-vendors-jqueryui--ui-dialog--background-color !default;
.ui-dialog .blockUI.blockOverlay{

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-block-list--medallion{
flex-direction: column;
align-items: center;

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-panel .ibo-panel--body{
.ibo-datatable{
width: 100%

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,8 +1,10 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-panel-within-main-content--margin-bottom: 200px !default;
$ibo-panel-within-main-content--sticky-sentinel-top--top: -1 * $ibo-main-content--padding-top !default;
$ibo-panel-within-main-content--sticky-sentinel-top--height: $ibo-main-content--padding-top !default;
@@ -17,6 +19,8 @@ $ibo-panel-within-main-content--header--top--is-sticky: -1 * $ibo-main-content--
* - Unlike in JS, there no easy way to find the closest descendant
*/
.ibo-panel.ibo-has-sticky-header {
margin-bottom: $ibo-panel-within-main-content--margin-bottom; /* Add a margin below the panel so the dropdown lists can open without problem (N°4039) */
/* Stickable header rules */
> .ibo-sticky-sentinel-top {
top: $ibo-panel-within-main-content--sticky-sentinel-top--top;

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */

View File

@@ -1,19 +1,19 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "alert";
@import "button";
@import "button-group";
@import "breadcrumbs";
@import "collapsible-section";
@import "quick-create";
@import "global-search";
@import "popover-menu/popover-menu";
@import "popover-menu/popover-menu-item";
@import "newsroom-menu";
@import "panel";
@import "collapsible-section";
@import "modal";
@import "dashlet/all";
@import "input/all";

View File

@@ -1,21 +1,10 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-breadcrumbs--margin-right: 32px !default;
$ibo-breadcrumbs--item--text-color: $ibo-color-grey-800 !default;
$ibo-breadcrumbs--item-icon--margin-x: 8px !default;
@@ -27,8 +16,26 @@ $ibo-breadcrumbs--item-label--max-width: 100px !default;
$ibo-breadcrumbs--item-separator--margin-x: 12px !default;
$ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
$ibo-breadcrumbs--previous-items-list-toggler--text-color: $ibo-color-grey-700 !default;
$ibo-breadcrumbs--previous-items-list-toggler--margin-right: 2 * $ibo-breadcrumbs--item-separator--margin-x !default;
$ibo-breadcrumbs--previous-items-list--top: 37px !default;
$ibo-breadcrumbs--previous-items-list--padding-y: 8px !default;
$ibo-breadcrumbs--previous-items-list--background-color: $ibo-color-white-100 !default;
$ibo-breadcrumbs--previous-item--text-color: $ibo-breadcrumbs--item--text-color !default;
$ibo-breadcrumbs--previous-item--padding-x: 12px !default;
$ibo-breadcrumbs--previous-item--padding-y: 12px !default;
$ibo-breadcrumbs--previous-item-label--max-width: 200px !default;
.ibo-breadcrumbs{
@extend %ibo-full-height-content;
position: relative;
margin-right: $ibo-breadcrumbs--margin-right;
@extend %ibo-full-height-content;
&.ibo-is-overflowing {
justify-content: right;
}
* {
display: flex;
@@ -40,13 +47,6 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
@extend %ibo-font-ral-med-100;
&:not(:last-child){
&::after{
content: '\f054';
margin: 0 $ibo-breadcrumbs--item-separator--margin-x;
color: $ibo-breadcrumbs--item-separator--text-color;
@extend %fa-solid-base;
}
&:hover{
.ibo-breadcrumbs--item-icon{
> *{
@@ -78,3 +78,50 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
max-width: $ibo-breadcrumbs--item-label--max-width;
@extend %ibo-text-truncated-with-ellipsis;
}
.ibo-breadcrumbs--item,
.ibo-breadcrumbs--previous-items-list-toggler {
&:not(:last-child){
&::after{
content: '\f054';
margin: 0 $ibo-breadcrumbs--item-separator--margin-x;
color: $ibo-breadcrumbs--item-separator--text-color;
@extend %fa-solid-base;
}
}
}
.ibo-breadcrumbs--previous-items-list-toggler {
margin-right: $ibo-breadcrumbs--previous-items-list-toggler--margin-right;
color: $ibo-breadcrumbs--previous-items-list-toggler--text-color !important;
@extend %ibo-font-ral-med-100;
&:not(:last-child) {
&::after {
position: absolute; /* To be outside of the button */
right: -1 * $ibo-breadcrumbs--previous-items-list-toggler--margin-right;
}
}
}
.ibo-breadcrumbs--previous-items-list {
display: flex;
flex-direction: column;
align-items: stretch; /* For the items to occupy the full width */
position: fixed;
top: $ibo-breadcrumbs--previous-items-list--top;
padding: $ibo-breadcrumbs--previous-items-list--padding-y 0;
background-color: $ibo-breadcrumbs--previous-items-list--background-color;
@extend %ibo-elevation-300;
}
.ibo-breadcrumbs--previous-item {
color: $ibo-breadcrumbs--previous-item--text-color;
@extend %ibo-font-ral-med-100;
padding: $ibo-breadcrumbs--previous-item--padding-y $ibo-breadcrumbs--previous-item--padding-x;
.ibo-breadcrumbs--item-label {
max-width: 200px;
}
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -13,6 +13,9 @@ $ibo-button-group--elements-separator--width: 1px !default;
$ibo-button-group--elements-separator--border-left: 1px solid transparent !default;
.ibo-button-group {
display: inline-flex; /* Ensure that buttons of a same group always stay on the same line */
flex-wrap: nowrap;
.ibo-button {
position: relative;
}
@@ -45,8 +48,10 @@ $ibo-button-group--elements-separator--border-left: 1px solid transparent !defau
border-left: $ibo-button-group--elements-separator--border-left;
}
/* Note: Selector cannot be simplified in the one above as the "::before" must be after the different classes */
.ibo-button + .ibo-button {
/* Note: Selector cannot be simplified in the one above as the "::before" must be after the different classes.
* A more precise selector could be .ibo-button + .ibo-button, however .ibo-button is extended by multiple element and in a multiple loop it creates heavy selectors.
*/
> * + * {
@each $sType, $aColors in $ibo-button-colors {
@each $sColor, $aPseudoclasses in $aColors {
@each $sPseudoclass, $aAttributes in $aPseudoclasses {

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-button--sibling-spacing: 5px !default;
$ibo-button--padding-y: 6px !default;
$ibo-button--padding-x: 9px !default;
@@ -461,6 +466,7 @@ $ibo-button-colors: (
}
.ibo-button {
position: relative;
display: inline-block; /* Used to allow truncated text on .ibo-button--label */
padding: $ibo-button--padding-y $ibo-button--padding-x;
border: $ibo-button--border;
@@ -470,7 +476,7 @@ $ibo-button-colors: (
white-space: nowrap; /* To force sub elements to be on 1 line */
@extend %ibo-font-ral-sembol-100;
& ~ .ibo-button {
& + .ibo-button {
margin-left: $ibo-button--sibling-spacing;
}

View File

@@ -1,37 +1,24 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-collapsible-section--margin-top: 3rem;
$ibo-collapsible-section--title--color: $ibo-color-grey-900 !default;
$ibo-collapsible-section--body--background-color: $ibo-color-white-100 !default;
$ibo-collapsible-section--margin-top: 3rem !default;
$ibo-collapsible-section--title--color: $ibo-panel--title--color !default;
$ibo-collapsible-section--body--background-color: $ibo-panel--body--background-color !default;
$ibo-collapsible-section--highlight--height: 8px !default;
$ibo-collapsible-section--maximize-minimize-button--right: 5px !default;
$ibo-collapsible-section--maximize-minimize-button--color: $ibo-panel--collapsible-toggler--color !default;
$ibo-collapsible-section--maximize-minimize-button--right: $ibo-panel--collapsible-toggler--margin-right !default;
$ibo-collapsible-section--body--padding-bottom: 16px !default;
$ibo-collapsible-section--body--padding-top: $ibo-collapsible-section--body--padding-bottom + $ibo-collapsible-section--highlight--height !default;
$ibo-collapsible-section--body--padding-x: 16px !default;
$ibo-collapsible-section--body--border-radius: $ibo-border-radius-500 !default;
$ibo-collapsible-section--body--border-radius: $ibo-panel--body--border-radius !default;
$ibo-collapsible-section--body--border-size: 1px !default;
$ibo-collapsible-section--body--border-color: $ibo-color-grey-400 !default;
$ibo-collapsible-section--body--border-color: $ibo-panel--base-border-color !default;
/* Rules */
@@ -78,6 +65,7 @@ $ibo-collapsible-section--body--border-color: $ibo-color-grey-400 !default;
.ibo-collapsible-section--action-button {
&.ibo-collapsible-section--maximize-button, &.ibo-collapsible-section--minimize-button {
color: $ibo-collapsible-section--maximize-minimize-button--color;
margin-right: $ibo-collapsible-section--maximize-minimize-button--right;
}
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -77,11 +77,6 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
overflow-y: auto;
}
.ibo-datatable .ibo-field-badge {
margin: 0;
padding: 0px 4px;
}
// Datatables configure this list dialog
// Could be in a separate component, but is only used in datatables as of now

View File

@@ -1,6 +1,6 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-field-badge--margin: 0 !default;
@@ -10,10 +10,15 @@ $ibo-field-badge--border-radius: $ibo-border-radius-300 !default;
$ibo-field-badge--label--spacing-x: 0.5rem !default;
.ibo-field-badge {
display: inline-flex;
align-items: baseline;
margin: $ibo-field-badge--margin;
padding: $ibo-field-badge--padding;
border-radius: $ibo-field-badge--border-radius;
display: inline;
/* Mind the use of these "generic" variables that allow to automatically use the right colors for a field and its value depending on which class will be append to it (see those defined in the DM) */
background-color: var(--ibo-main-color);
color: var(--ibo-complementary-color);
}
.ibo-field-badge--decoration + .ibo-field-badge--label {

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -23,24 +23,40 @@ $ibo-field--fullscreen-toggler--size: 20px !default;
$ibo-field--fullscreen-toggler--border-radius: $ibo-border-radius-500 !default;
$ibo-field--fullscreen-toggler--background-color--on-hover: $ibo-color-white-200 !default;
$ibo-field--value--scrollbar-track-background-color: $ibo-color-white-200 !default;
$ibo-field--value--padding-x--is-fullscreen: $ibo-field--label--padding-x--is-fullscreen !default;
$ibo-field--value--padding-top--is-fullscreen: $ibo-field--label--padding-y--is-fullscreen + 32px !default;
$ibo-field--value--padding-bottom--is-fullscreen: $ibo-field--label--padding-y--is-fullscreen !default;
$ibo-field--value-decoration--spacing-x: 0.5rem !default;
$ibo-field--enable-bulk--padding-y: 2px !default;
$ibo-field--enable-bulk--padding-x: 5px !default;
$ibo-field--enable-bulk--margin-left: 5px !default;
$ibo-field--enable-bulk--height: calc(100% - #{$ibo-field--enable-bulk--padding-x}) !default;
$ibo-field--enable-bulk--border-radius: $ibo-border-radius-500 !default;
$ibo-field--enable-bulk--checkbox--margin-left: 8px !default;
/* SCSS rules */
.ibo-field {
@extend %ibo-font-size-150;
/* Avoid value to overflow from its container with very long strings (typically URLs) */
/* Note: Some types of attribute must be excluding as it can alter their rendering */
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not(.ibo-input-file-select--container) {
/* We need the rule to apply for the class and all its descendants */
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not([data-attribute-type="AttributeCustomFields"]):not(.ibo-input-file-select--container) {
/* We need the rule to apply for the class and all its descendants, hence the "&, & *" */
.ibo-field--value {
* {
&, & * {
word-break: break-word;
white-space: pre-wrap;
white-space: inherit; /* Here we don't put break-spaces as it would put ".ibo-field-small .ibo-field-value" on a new line due to the spaces/indentation of the HTML templates. For now we rather have this rule than diminish the templates readability/maintenability */
}
}
&.ibo-field-large {
.ibo-field--value {
&, & * {
white-space: break-spaces; /* For large fields we don't have the issue stated above */
}
}
}
}
@@ -57,7 +73,6 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
.ibo-field--label {
position: relative; /* Necessary for fullscreen toggler */
display: flex;
justify-content: space-between;
align-items: center;
max-width: initial;
width: 100%;
@@ -73,6 +88,11 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
width: 30%;
}
/* N°4318 - Visible scrollbar background for large fields overflowing to ease "limits" visualization by the user */
.ibo-field--value > * {
--ibo-scrollbar--scrollbar-track-background-color: $ibo-field--value--scrollbar-track-background-color;
}
/* Fullscreen mode */
&.ibo-is-fullscreen {
background-color: $ibo-field--background-color--is-fullscreen;
@@ -113,7 +133,7 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
@extend %ibo-hyperlink-inherited-colors;
@extend %ibo-fully-centered-content;
@extend %ibo-font-size-100;
@extend %ibo-font-size-50;
&:hover {
background-color: $ibo-field--fullscreen-toggler--background-color--on-hover;
@@ -138,6 +158,9 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
}
}
}
.ibo-field--label-small .ibo-field--label{
width: 20em;
}
.ibo-field--value {
display: table;
width: 100%;
@@ -155,14 +178,13 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
.ibo-field--comments {
display: table-cell;
vertical-align: top;
padding-right: 10px;
> input[type="checkbox"] {
margin-left: 5px;
float: right;
}
> .multi_values, > .mono_value {
> .multi_values, > .mono_value, > .ibo-field--comments--synchro{
float: right;
}
}
@@ -187,21 +209,20 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
}
}
.multi_values, .mono_value {
display: inline-block;
padding: 1px 3px;
margin-left: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
color: #fff;
.ibo-field--enable-bulk, .ibo-field--comments--synchro {
display: inline;
padding: $ibo-field--enable-bulk--padding-y $ibo-field--enable-bulk--padding-x;
margin: 0 0 0 $ibo-field--enable-bulk--margin-left;
height: $ibo-field--enable-bulk--height;
border-radius: $ibo-field--enable-bulk--border-radius;
font-weight: bold;
white-space: nowrap;
}
.mono_value {
background-color: #3c3;
.ibo-field--enable-bulk--checkbox {
margin-left: $ibo-field--enable-bulk--checkbox--margin-left;
}
.multi_values {
background-color: #c33;
}
.form_field ~ .form_field {
margin-top: $ibo-field--sibling-spacing;
}

View File

@@ -1,6 +1,6 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-fieldset--sibling-spacing: 48px !default;

View File

@@ -1,6 +1,6 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-file-select--file-name-margin-left: 10px !default;

View File

@@ -1,7 +1,8 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-prop-header {
@extend %ibo-font-size-150;
padding-bottom: 14px;

View File

@@ -1,4 +1,4 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,4 +1,4 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */

View File

@@ -1,7 +1,8 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-medallion-icon--padding-y: 13px !default;
$ibo-medallion-icon--padding-x: 0 !default;

View File

@@ -0,0 +1,4 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
@@ -32,11 +19,31 @@ $ibo-panel--base-transition: $ibo-panel--base-transition-property $ibo-panel--ba
transition: $ibo-panel--base-transition;
}
$ibo-panel-colors: (
'primary': $ibo-color-primary-600,
'secondary': $ibo-color-secondary-600,
'neutral': $ibo-color-grey-600,
'information': $ibo-color-information-600,
'success': $ibo-color-success-600,
'failure': $ibo-color-danger-600,
'warning': $ibo-color-warning-600,
'danger': $ibo-color-danger-600,
'grey' : $ibo-color-grey-600,
'blue-grey': $ibo-color-blue-grey-600,
'blue': $ibo-color-blue-800,
'cyan': $ibo-color-cyan-600,
'green': $ibo-color-green-600,
'orange' : $ibo-color-orange-600,
'red': $ibo-color-red-600,
'pink': $ibo-color-pink-600,
) !default;
/* - Specific variables for the block */
$ibo-panel--spacing-top: 24px !default;
$ibo-panel--highlight--width: 100% !default;
$ibo-panel--highlight--height: 8px !default;
$ibo-panel--highlight--background-color: 8px !default;
$ibo-panel--highlight--border-radius: $ibo-border-radius-400 $ibo-border-radius-400 0 0 !default;
$ibo-panel--highlight--padding-bottom: $ibo-panel--highlight--height !default;
@@ -81,47 +88,14 @@ $ibo-panel--collapsible-toggler--margin-right: 8px !default;
$ibo-panel--collapsible-toggler--font-size: $ibo-font-size-250 !default;
$ibo-panel--collapsible-toggler--color: $ibo-color-grey-700 !default;
$ibo-panel-colors: (
'primary': $ibo-color-primary-600,
'secondary': $ibo-color-secondary-600,
'neutral': $ibo-color-grey-600,
'information': $ibo-color-information-600,
'success': $ibo-color-success-600,
'failure': $ibo-color-danger-600,
'warning': $ibo-color-warning-600,
'danger': $ibo-color-danger-600,
'grey' : $ibo-color-grey-600,
'blue-grey': $ibo-color-blue-grey-600,
'blue': $ibo-color-blue-800,
'cyan': $ibo-color-cyan-600,
'green': $ibo-color-green-600,
'orange' : $ibo-color-orange-600,
'red': $ibo-color-red-600,
'pink': $ibo-color-pink-600,
) !default;
@each $sColor, $sColorValue in $ibo-panel-colors {
.ibo-panel.ibo-is-#{$sColor} > .ibo-panel--body::before {
position: absolute;
top: 0;
left: 0;
display: block;
background-color: $sColorValue;
content: "";
width: $ibo-panel--highlight--width;
height: $ibo-panel--highlight--height;
padding-bottom: $ibo-panel--highlight--padding-bottom;
}
}
/* Rules */
.ibo-panel + .ibo-panel {
margin-top: $ibo-panel--spacing-top;
}
.ibo-panel {
--ibo-main-color: map-get($ibo-panel-colors, 'neutral'); /* --ibo-main-color is to allow overload from custom dynamic value from the DM. The overload will be done through an additional CSS class of a particular DM class or DM attribute */
position: relative;
&.ibo-has-icon {
@@ -227,6 +201,26 @@ $ibo-panel-colors: (
overflow: hidden; /* To force highlight color to be cropped by the border radius */
@extend %ibo-font-size-150;
&::before {
position: absolute;
top: 0;
left: 0;
display: block;
background-color: var(--ibo-main-color); /* Default value defined in .ibo-panel, can be overloaded by custom CSS classes */
content: "";
width: $ibo-panel--highlight--width;
height: $ibo-panel--highlight--height;
padding-bottom: $ibo-panel--highlight--padding-bottom;
}
}
@each $sColor, $sColorValue in $ibo-panel-colors {
.ibo-panel.ibo-is-#{$sColor} > .ibo-panel--body::before {
background-color: $sColorValue;
}
}
.ibo-panel--collapsible-toggler {
@@ -237,26 +231,28 @@ $ibo-panel-colors: (
cursor: pointer;
}
.ibo-panel--collapsible-toggler--opened {
display: block;
}
.ibo-panel--collapsible-toggler--closed {
display: none;
}
/* Collapsible rules */
.ibo-panel:not(.ibo-is-opened) {
.ibo-panel--collapsible-toggler--closed {
.ibo-panel {
.ibo-panel--collapsible-toggler--opened {
display: block;
}
.ibo-panel--collapsible-toggler--opened {
.ibo-panel--collapsible-toggler--closed {
display: none;
}
&:not(.ibo-is-opened) {
.ibo-panel--collapsible-toggler--closed {
display: block;
}
.ibo-panel--body {
display: none;
.ibo-panel--collapsible-toggler--opened {
display: none;
}
.ibo-panel--body {
display: none;
}
}
}

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