Compare commits

...

53 Commits

Author SHA1 Message Date
jf-cbd
6b448e29f5 PR fix 2023-04-19 10:46:36 +02:00
jf-cbd
7cb6af0a2b fix for 6179 (with description instead of tooltip)
PR fix
2023-04-19 10:46:36 +02:00
jf-cbd
ddc9952ec1 N°6179 - Tooltip attribute in field component (in Twig) 2023-04-13 15:21:11 +02:00
denis.flaven@combodo.com
a1a9ffe192 Merge remote-tracking branch 'origin/feature/6133-add-extra-files-to-backup-and-restore' into saas/3.0 2023-04-06 10:51:29 +02:00
denis.flaven@combodo.com
001194835f Config file may not exist during unattended setup/backup 2023-04-06 10:50:47 +02:00
denis.flaven@combodo.com
7728082c00 Merge remote-tracking branch 'origin/feature/6133-add-extra-files-to-backup-and-restore' into saas/3.0 2023-04-04 15:55:09 +02:00
denis.flaven@combodo.com
955aefc05b Ignore non-existing files. 2023-04-04 14:26:31 +02:00
denis.flaven@combodo.com
970183ef45 Merge remote-tracking branch 'origin/feature/6132-disabling-tabs-dynamically' into saas/3.0 2023-04-03 14:02:19 +02:00
denis.flaven@combodo.com
6c2db1e687 Fixed tab activation afeter re-enabling. 2023-04-03 13:45:32 +02:00
denis.flaven@combodo.com
034ca26d01 Do NOT backup unsafe files. 2023-04-03 11:54:01 +02:00
denis.flaven@combodo.com
32d74fbc8e Merge branch 'feature/6132-disabling-tabs-dynamically' into saas/3.0 2023-03-30 17:08:02 +02:00
Molkobain
477f2f51e9 Update code to match conventions 2023-03-30 16:50:08 +02:00
denis.flaven@combodo.com
94ea8e60e8 Typo! 2023-03-30 14:29:29 +02:00
denis.flaven@combodo.com
b9a00b15f5 Disable tabs by ID instead of index
Disabled tabs are visible (with a 'not-allowed' cursor)  instead of being hidden from the extra tabs menu.
2023-03-30 14:16:26 +02:00
Denis
e87f5af465 Apply suggestions from code review
JS cleanup after review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-03-30 13:25:55 +02:00
denis.flaven@combodo.com
97d717b016 Merge from support/3.0.2 2023-03-29 16:56:42 +02:00
denis.flaven@combodo.com
d03bd706e2 Support of extra files (configurable) in the backup. 2023-03-29 16:53:38 +02:00
denis.flaven@combodo.com
251fd3c67b N°6132 - disable tabs dynamically 2023-03-28 15:07:43 +02:00
odain
7b0a569c64 N°4762 - menu compilation: fix ci (merge issue) 2023-03-07 14:07:17 +01:00
odain
7176bc8686 N°4762 - menu compilation: fix broken menus scenario via AVA6 delta XML
N°4762-enhance test
2023-03-07 09:49:21 +01:00
Denis
9c0b906ded N°5922 - Fix plus button semantic on ext. key widget (#448)
* N°5922 - Enhance plus button on extkeywidget

* Properly reset the target class when closing the dialog

* Make icon buttons as actual clickable links for BeHat

* Apply suggestions from code review

Review by Guillaume. Thanks!

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-03-03 14:12:09 +01:00
odain
0533916dad 4762-menu compilation rework after brainstorming 2023-03-02 08:49:41 +01:00
odain
60b08586c2 N°4762-fix fresh install setup crash 2023-02-24 10:11:17 +01:00
odain
28df2942e4 N°6022 - Make synchro scripts work by http via token authentication with SYNCHRO scopes 2023-02-24 09:11:43 +01:00
odain
cd48d2ad37 N°4762 - Designer customization of menus moved in itop-structure crashs in iTop 3.0 2023-02-24 09:01:10 +01:00
odain
9db2205241 N°5891 - fix and enable tests 2023-02-16 16:59:43 +01:00
odain
045985cd5b 5891-renable badly named tests 2023-02-16 15:56:49 +01:00
odain
809b371520 N°5753 - add config parameter allow_rest_services_via_tokens to bypass rest secure profile option 2022-12-21 15:23:49 +01:00
odain
9bbc7342b8 enhance test framework: let AddProfileToUser work on any User not only UserLocal 2022-12-21 15:22:52 +01:00
odain
973c435138 Revert "N°5753 - exposer l'API Rest dans le SaaS - ugly way of passing API scope to rest.php during login"
This reverts commit 49748a0374.
2022-12-21 14:39:03 +01:00
denis.flaven@combodo.com
1c3dfd6491 Merge branch 5620 - also hide favorites orgs from preferences 2022-12-19 15:57:44 +01:00
denis.flaven@combodo.com
9400b697eb N°5620 Also hide the favorite orgs in preferences 2022-12-19 15:55:52 +01:00
odain
49748a0374 N°5753 - exposer l'API Rest dans le SaaS - ugly way of passing API scope to rest.php during login 2022-12-19 14:22:45 +01:00
odain
163276a6c2 N°5620-merge fix 2022-11-23 09:24:21 +01:00
odain
9b0c2f7324 Merge branch 'feature/5620-hide-org-filter-menu' into saas/3.0 2022-11-22 13:47:33 +01:00
odain
e1807f598f N°5620-fix ci 2022-11-22 13:46:33 +01:00
Molkobain
02e63fff64 Add PHPDoc 2022-11-22 13:28:02 +01:00
odain
0864f05d9f N°5620 - conf param renaming + backward compatibility test 2022-11-22 08:25:43 +01:00
denis.flaven@combodo.com
1bbcd9656a N°5619 - fixed crash when no provider at all! 2022-11-22 08:25:43 +01:00
odain
34d8e52c22 N°5620 - remove debug log 2022-11-22 08:25:27 +01:00
odain
ac7309e48c N°5620 - Hide the organization filter with a conf parameter 2022-11-22 08:25:27 +01:00
odain
c8fade6013 5620-simplify test to avoid regression in other test sections linked to MetaModel use 2022-11-22 07:42:03 +01:00
odain
ad052dd861 N°5620-renaming IsOrgMenuFilterAllowed<-IsSiloSelectionEnabled and made public 2022-11-22 07:31:12 +01:00
odain
a5ea868609 fix test 2022-11-21 10:41:03 +01:00
odain
0b03b3ef4d N°5620 - conf param renaming + backward compatibility test 2022-11-21 09:56:56 +01:00
odain
174cace20a N°5620 - remove debug log 2022-11-15 09:55:12 +01:00
odain
e3f5dbfc80 N°5620 - Hide the organization filter with a conf parameter 2022-11-15 09:49:36 +01:00
denis.flaven@combodo.com
40e24c25a2 N°5619 - hide newsroom menu when no provider 2022-11-15 09:17:27 +01:00
denis.flaven@combodo.com
d3f8e1c472 Revert "N°5619 - Hide newsroom menu when no provider"
This reverts commit 647b669eb9.
2022-11-14 18:28:18 +01:00
denis.flaven@combodo.com
647b669eb9 N°5619 - Hide newsroom menu when no provider 2022-11-14 18:18:10 +01:00
odain
c6e4466c53 ci: fix ItopDataTestCase CreateUser contactid unset 2022-10-26 14:03:11 +02:00
odain
6638eb4adc ci: adapt impersonate test to any friendlyname output 2022-10-26 09:50:56 +02:00
odain
eb40968e34 ci: add CreateContactlessUser method in test framework 2022-10-25 09:26:57 +02:00
33 changed files with 1111 additions and 243 deletions

View File

@@ -9,6 +9,7 @@ use Combodo\iTop\Application\Helper\WebResourcesHelper;
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/template.class.inc.php');
require_once(APPROOT."/application/user.dashboard.class.inc.php");
require_once(APPROOT."/setup/parentmenunodecompiler.class.inc.php");
/**
@@ -103,7 +104,7 @@ class ApplicationMenu
{
self::$sFavoriteSiloQuery = $sOQL;
}
/**
* Get the query used to limit the list of displayed organizations in the drop-down menu
* @return string The OQL query returning a list of Organization objects
@@ -273,12 +274,23 @@ class ApplicationMenu
continue;
}
$aSubMenuNodes = static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams);
if (! ParentMenuNodeCompiler::$bUseLegacyMenuCompilation && !($oMenuNode instanceof ShortcutMenuNode)){
if (is_array($aSubMenuNodes) && 0 === sizeof($aSubMenuNodes)){
IssueLog::Error('Empty menu node not displayed', LogChannels::CONSOLE, [
'menu_node_class' => get_class($oMenuNode),
'menu_node_label' => $oMenuNode->GetLabel(),
]);
continue;
}
}
$aMenuGroups[] = [
'sId' => $oMenuNode->GetMenuID(),
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
'sInitials' => $oMenuNode->GetInitials(),
'sTitle' => $oMenuNode->GetTitle(),
'aSubMenuNodes' => static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams),
'aSubMenuNodes' => $aSubMenuNodes,
];
}
@@ -525,7 +537,7 @@ EOF
return -1;
}
/**
* Retrieves the currently active menu (if any, otherwise the first menu is the default)
* @return string The Id of the currently active menu
@@ -533,7 +545,7 @@ EOF
public static function GetActiveNodeId()
{
$oAppContext = new ApplicationContext();
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
if ($sMenuId === null)
{
$sMenuId = self::GetDefaultMenuId();
@@ -643,7 +655,7 @@ abstract class MenuNode
/**
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
*/
*/
protected $m_aEnableStimuli;
/**
@@ -804,7 +816,7 @@ abstract class MenuNode
{
return false;
}
/**
* Add a limiting display condition for the same menu node. The conditions will be combined with a AND
* @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction
@@ -977,7 +989,7 @@ class TemplateMenuNode extends MenuNode
* @var string
*/
protected $sTemplateFile;
/**
* Create a menu item based on a custom template and inserts it into the application's main menu
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
@@ -1048,7 +1060,7 @@ class OQLMenuNode extends MenuNode
* @var bool|null
*/
protected $bSearchFormOpen;
/**
* Extra parameters to be passed to the display block to fine tune its appearence
*/
@@ -1081,7 +1093,7 @@ class OQLMenuNode extends MenuNode
// Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects
// of the class specified by the OQL...
}
/**
* Set some extra parameters to be passed to the display block to fine tune its appearence
* @param array $aParams paramCode => value. See DisplayBlock::GetDisplay for the meaning of the parameters
@@ -1109,7 +1121,7 @@ class OQLMenuNode extends MenuNode
'Menu_'.$this->GetMenuId(),
$this->bSearch, // Search pane
$this->bSearchFormOpen, // Search open
$oPage,
$oPage,
array_merge($this->m_aParams, $aExtraParams),
true
);
@@ -1343,10 +1355,10 @@ class NewObjectMenuNode extends MenuNode
{
// Enable this menu, only if the current user has enough rights to create such an object, or an object of
// any child class
$aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$bActionIsAllowed = false;
foreach($aSubClasses as $sCandidateClass)
{
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
@@ -1355,7 +1367,7 @@ class NewObjectMenuNode extends MenuNode
break; // Enough for now
}
}
return $bActionIsAllowed;
return $bActionIsAllowed;
}
/**
@@ -1497,7 +1509,7 @@ class DashboardMenuNode extends MenuNode
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
}
}
}
/**
@@ -1538,7 +1550,7 @@ class ShortcutContainerMenuNode extends MenuNode
$sName = $this->GetMenuId().'_'.$oShortcut->GetKey();
new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
}
// Complete the tree
//
parent::PopulateChildMenus();

View File

@@ -5,6 +5,7 @@
*/
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
@@ -323,12 +324,12 @@ EOF
EOF
);
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--clear ibo-is-hidden\" id=\"mini_clear_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Clear();\" data-tooltip-content='".Dict::S('UI:Button:Clear')."'><i class=\"fas fa-times\"></i></div>";
$sHTMLValue .= " <a href=\"#\" class=\"ibo-input-select--action-button ibo-input-select--action-button--clear ibo-is-hidden\" id=\"mini_clear_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Clear();\" data-tooltip-content='".Dict::S('UI:Button:Clear')."'><i class=\"fas fa-times\"></i></a>";
}
if ($bCreate && $bExtensions) {
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--create\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\" data-tooltip-content='".Dict::S('UI:Button:Create')."'><i class=\"fas fa-plus\"></i></div>";
$sHTMLValue .= "<a href=\"#\" class=\"ibo-input-select--action-button ibo-input-select--action-button--create\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\" data-tooltip-content='".Dict::S('UI:Button:Create')."'><i class=\"fas fa-plus\"></i></a>";
$oPage->add_ready_script(
<<<JS
if ($('#ajax_{$this->iId}').length == 0)
@@ -339,7 +340,7 @@ JS
);
}
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) {
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--hierarchy\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\" data-tooltip-content='".Dict::S('UI:Button:SearchInHierarchy')."'><i class=\"fas fa-sitemap\"></i></div>";
$sHTMLValue .= "<a href=\"#\" class=\"ibo-input-select--action-button ibo-input-select--action-button--hierarchy\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\" data-tooltip-content='".Dict::S('UI:Button:SearchInHierarchy')."'><i class=\"fas fa-sitemap\"></i></a>";
$oPage->add_ready_script(
<<<JS
if ($('#ac_tree_{$this->iId}').length == 0)
@@ -350,7 +351,7 @@ JS
);
}
if ($oAllowedValues->CountExceeds($iMaxComboLength)) {
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--search\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\" data-tooltip-content='".Dict::S('UI:Button:Search')."'><i class=\"fas fa-search\"></i></div>";
$sHTMLValue .= " <a href=\"#\" class=\"ibo-input-select--action-button ibo-input-select--action-button--search\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\" data-tooltip-content='".Dict::S('UI:Button:Search')."'><i class=\"fas fa-search\"></i></a>";
}
$sHTMLValue .= "</div>";
$sHTMLValue .= "</div>";
@@ -904,7 +905,7 @@ JS
{
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
// and that the current user is allowed to create objects of this class
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass);
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass, ENUM_CHILD_CLASSES_ALL);
$aPossibleClasses = array();
foreach($aSubClasses as $sCandidateClass)
{
@@ -924,6 +925,7 @@ JS
$sDialogTitleEscaped = addslashes($sDialogTitle);
$oPage->add_ready_script("$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitleEscaped'});\n");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').find('select').attr('id', 'ac_create_{$this->iId}_select');");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
}

View File

@@ -1238,6 +1238,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'navigation_menu.show_organization_filter' => [
'type' => 'bool',
'description' => 'Display organization filter in menu',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'quick_create.enabled' => [
'type' => 'bool',
'description' => 'Whether or not the quick create is enabled',
@@ -1416,6 +1424,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'allow_rest_services_via_tokens' => [
'type' => 'bool',
'description' => 'When set to true, REST endpoint token authorization works even with secure_rest_services set.',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'search_manual_submit' => [
'type' => 'array',
'description' => 'Force manual submit of search all requests',
@@ -1861,7 +1877,7 @@ class Config
}
if (strlen($sNoise) > 0)
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new ConfigException('Syntax error in configuration file',
array('file' => $sConfigFile, 'error' => '<tt>'.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'</tt>'));
}
@@ -2671,7 +2687,7 @@ class ConfigPlaceholdersResolver
}
$sPattern = '/\%(env|server)\((\w+)\)(?:\?:(\w*))?\%/'; //3 capturing groups, ie `%env(HTTP_PORT)?:8080%` produce: `env` `HTTP_PORT` and `8080`.
if (! preg_match_all($sPattern, $rawValue, $aMatchesCollection, PREG_SET_ORDER))
{
return $rawValue;

View File

@@ -253,3 +253,6 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
margin-left: $ibo-field--enable-bulk--checkbox--margin-left;
}
.ibo-input-select--action-buttons a {
@extend %ibo-hyperlink-inherited-colors;
}

View File

@@ -35,6 +35,10 @@ $ibo-sticky-sentinel-bottom--height: $ibo-sticky-sentinel--height !default;
opacity: 1 !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
.ibo-is-disabled {
cursor: not-allowed !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
/****************************/
/* Disposition / alignement */
/****************************/

View File

@@ -187,6 +187,12 @@ class DBRestore extends DBBackup
@chmod($sConfigFile, 0770); // Allow overwriting the file
rename($sDataDir.'/config-itop.php', $sConfigFile);
@chmod($sConfigFile, 0440); // Read-only
$aExtraFiles = $this->ListExtraFiles($sDataDir);
foreach($aExtraFiles as $sSourceFilePath => $sDestinationFilePath) {
SetupUtils::builddir(dirname($sDestinationFilePath));
rename($sSourceFilePath, $sDestinationFilePath);
}
try {
SetupUtils::rrmdir($sDataDir);
@@ -211,4 +217,27 @@ class DBRestore extends DBBackup
$oRestoreMutex->Unlock();
}
}
/**
* List the 'extra files' found in the decompressed archive
* (i.e. files other than config-itop.php, delta.xml, itop-dump.sql or production-modules/*
* @param string $sDataDir
* @return string[]
*/
protected function ListExtraFiles(string $sDataDir)
{
$aExtraFiles = [];
$aStandardFiles = ['config-itop.php', 'itop-dump.sql', 'production-modules', 'delta.xml'];
$oDirectoryIterator = new RecursiveDirectoryIterator($sDataDir, FilesystemIterator::CURRENT_AS_FILEINFO|FilesystemIterator::SKIP_DOTS);
$oIterator = new RecursiveIteratorIterator($oDirectoryIterator);
foreach ($oIterator as $oFileInfo)
{
if (in_array($oFileInfo->getFilename(), $aStandardFiles)) continue;
if (strncmp($oFileInfo->getPathname(), $sDataDir.'/production-modules', strlen($sDataDir.'/production-modules')) == 0) continue;
$aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir));
}
return $aExtraFiles;
}
}

View File

@@ -634,7 +634,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// will force it be of the same class as the previous call)
me.sTargetClass = me.sOriginalTargetClass;
me.CreateObject(oWizHelper);
me.CreateObject();
};
this.DoSelectObjectClass = function () {
@@ -646,13 +646,12 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// Setting new target class
me.sTargetClass = oSelectedClass.val();
// Opening real creation form
me.CreateObject(true);
$('#ac_create_'+me.id).dialog('close');
me.CreateObject();
};
this.CreateObject = function (oWizHelper) {
this.CreateObject = function (bTargetClassSelected) {
if ($('#'+me.id).prop('disabled')) {
return;
} // Disabled, do nothing
@@ -670,7 +669,8 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
sAttCode: me.sAttCode,
'json': me.oWizardHelper.ToJSON(),
operation: 'objectCreationForm',
ajax_promise_id: sPromiseId
ajax_promise_id: sPromiseId,
bTargetClassSelected: bTargetClassSelected
};
// Make sure that we cancel any pending request before issuing another
@@ -678,6 +678,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
me.StopPendingRequest();
// Run the query and get the result back directly in HTML
var sLocalTargetClass = me.sTargetClass; // Remember the target class since it will be reset when closing the dialog
me.ajax_request = $.post(AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function (data) {
$('#ajax_'+me.id).html(data);
@@ -687,6 +688,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// Modify the action of the cancel button
$('#ac_create_'+me.id+' button.cancel').off('click').on('click', me.CloseCreateObject);
me.ajax_request = null;
me.sTargetClass = sLocalTargetClass;
// Adjust the dialog's size to fit into the screen
if ($('#ac_create_'+me.id).width() > ($(window).width()-40)) {
$('#ac_create_'+me.id).width($(window).width()-40);
@@ -714,6 +716,10 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
$('#ac_create_'+me.id).dialog("destroy");
$('#ac_create_'+me.id).remove();
$('#ajax_'+me.id).html('');
// Resetting target class to its original value
// (If not done, closing the dialog and trying to create a object again
// will force it be of the same class as the previous call)
me.sTargetClass = me.sOriginalTargetClass;
};
this.DoCreateObject = function () {

View File

@@ -124,5 +124,16 @@ $.widget( "itop.regulartabs", $.ui.tabs, {
this._off( prevPanels.not( this.panels ) );
}
},
});
// JQuery UI overload
disable: function(index){
const panel = this._getPanelForTab( index );
panel.addClass('ibo-is-hidden'); // Do not use .hide() since it alters the tab state
this._super( index );
},
// JQuery UI overload
enable: function(index) {
const panel = this._getPanelForTab( index );
panel.removeClass('ibo-is-hidden'); // Do not use .show() since it alters the tab state
this._super( index );
},
});

View File

@@ -377,4 +377,16 @@ $.widget( "itop.scrollabletabs", $.ui.tabs, {
setTab : function(tab){
this.active = tab;
},
});
// JQuery UI overload
disable: function(index){
const panel = this._getPanelForTab( this.tabs[index] );
panel.addClass('ibo-is-hidden'); // Do not use .hide() since it alters the tab state
this._super( index );
},
// JQuery UI overload
enable: function(index) {
const panel = this._getPanelForTab( this.tabs[index] );
panel.removeClass('ibo-is-hidden'); // Do not use .show() since it alters the tab state
this._super( index );
},
});

View File

@@ -15,6 +15,7 @@ $(function()
css_classes:
{
is_hidden: 'ibo-is-hidden',
is_disabled: 'ibo-is-disabled',
is_transparent: 'ibo-is-transparent',
is_opaque: 'ibo-is-opaque',
is_scrollable: 'ibo-is-scrollable',
@@ -252,6 +253,11 @@ $(function()
// Prevent anchor default behaviour
oEvent.preventDefault();
if (oExtraTabTogglerElem.attr('aria-disabled') === 'true') {
// Corresponding tab is disabled, do nothing
oEvent.stopPropagation();
return;
}
// Trigger click event on real tab toggler (the hidden one)
const sTargetTabId = oExtraTabTogglerElem.attr('href').replace(/#/, '');
this.element.find(this.js_selectors.tab_header+'[data-tab-id="'+sTargetTabId+'"] '+this.js_selectors.tab_toggler).trigger('click');
@@ -297,17 +303,30 @@ $(function()
const sTabId = oTabHeaderElem.attr('data-tab-id');
const oMatchingExtraTabElem = this.element.find(this.js_selectors.extra_tab_toggler+'[href="#'+sTabId+'"]');
// Disabled tabs should be disabled in the ExtraTabs list as well
let bIsDisabled = false;
if (oTabHeaderElem.attr('aria-disabled') === 'true') {
bIsDisabled = true;
}
// Manually check if the tab header is visible if the info isn't passed
if (bIsVisible === null) {
bIsVisible = CombodoGlobalToolbox.IsElementVisibleToTheUser(oTabHeaderElem[0], true, 2);
}
bIsVisible = CombodoGlobalToolbox.IsElementVisibleToTheUser(oTabHeaderElem[0], true, 2);
}
// Hide/show the corresponding extra tab element
if (bIsVisible) {
oMatchingExtraTabElem.addClass(this.css_classes.is_hidden);
} else {
oMatchingExtraTabElem.removeClass(this.css_classes.is_hidden);
}
if (bIsVisible) {
oMatchingExtraTabElem.addClass(this.css_classes.is_hidden);
} else {
oMatchingExtraTabElem.removeClass(this.css_classes.is_hidden);
}
// Enable/disable the corresponding extra tab element
if (bIsDisabled) {
oMatchingExtraTabElem.attr('aria-disabled', 'true');
oMatchingExtraTabElem.addClass(this.css_classes.is_disabled);
} else {
oMatchingExtraTabElem.attr('aria-disabled', 'false');
oMatchingExtraTabElem.removeClass(this.css_classes.is_disabled);
}
},
// - Update extra tabs list
_updateExtraTabsList: function () {
@@ -326,7 +345,7 @@ $(function()
* @return {string} The [data-tab-id] of the iIdx-th tab (zero based). Can return undefined if it has not [data-tab-id] attribute
* @private
*/
_getTabIdFromTabIndex(iIdx) {
_getTabIdFromTabIndex: function(iIdx) {
return this.element.children(this.js_selectors.tabs_list).children(this.js_selectors.tab_header).eq(iIdx).attr('data-tab-id');
},
/**
@@ -334,10 +353,41 @@ $(function()
* @return {number} The index (zero based) of the tab. If no matching tab, 0 will be returned.
* @private
*/
_getTabIndexFromTabId(sId) {
_getTabIndexFromTabId: function(sId) {
const oTabElem = this.element.children(this.js_selectors.tabs_list).children(this.js_selectors.tab_header+'[data-tab-id="'+sId+'"]');
return oTabElem.length === 0 ? 0 : oTabElem.prevAll().length;
}
},
/**
* @param sId {string} The [data-tab-id] of the tab
* @return {Object} The jQuery object representing the tab element
*
* @private
*/
_getTabElementFromTabId: function(sId) {
return this.element.children(this.js_selectors.tabs_list).children(this.js_selectors.tab_header+'[data-tab-id="'+sId+'"]');
},
/**
* @param sId {string} The [data-tab-id] of the tab
* @return {Object} The jQuery object representing the tab element
*/
disableTab: function(sId){
const tabsWidget = this.GetTabsWidget();
const iIdx = this._getTabIndexFromTabId(sId);
tabsWidget.disable(iIdx);
const tabElement = this._getTabElementFromTabId(sId);
this._updateTabHeaderDisplay(tabElement);
},
/**
* @param sId {string} The [data-tab-id] of the tab
* @return {Object} The jQuery object representing the tab element
*/
enableTab: function(sId){
const tabsWidget = this.GetTabsWidget();
const iIdx = this._getTabIndexFromTabId(sId);
tabsWidget.enable(iIdx);
const tabElement = this._getTabElementFromTabId(sId);
this._updateTabHeaderDisplay(tabElement);
}
});
});

View File

@@ -410,9 +410,10 @@ try
$iInputId = utils::ReadParam('iInputId', '');
$sAttCode = utils::ReadParam('sAttCode', '');
$sJson = utils::ReadParam('json', '', false, 'raw_data');
// Building form, if target class is abstract we ask the user for the desired leaf class
$bTargetClassSelected = utils::ReadParam('bTargetClassSelected', '', false, 'raw_data');
// Building form, if target class has child classes we ask the user for the desired leaf class, unless we've already done just that
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
if(MetaModel::IsAbstract($sTargetClass))
if(!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass))
{
$oWidget->GetClassSelectionForm($oPage);
}

View File

@@ -139,55 +139,58 @@ JS
//
//////////////////////////////////////////////////////////////////////////
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
$oFavoriteOrganizationsForm = new Form();
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
// Favorite organizations: the organizations listed in the drop-down menu
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$bIsSiloSelectionEnabled = MetaModel::GetConfig()->Get('navigation_menu.show_organization_filter');
if ($bIsSiloSelectionEnabled)
{
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
$oFavoriteOrganizationsForm = new Form();
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
// Favorite organizations: the organizations listed in the drop-down menu
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
$sIdFavoriteOrganizations = 1;
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
'menu' => false,
'selection_mode' => true,
'selection_type' => 'multiple',
'table_id' => 'user_prefs',
'surround_with_panel' => false,
'selected_rows' => $aFavoriteOrgs,
]));
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
$sIdFavoriteOrganizations = 1;
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
'menu' => false,
'selection_mode' => true,
'selection_type' => 'multiple',
'table_id' => 'user_prefs',
'surround_with_panel' => false,
'selected_rows' => $aFavoriteOrgs,
]));
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
// Button toolbar
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
// Button toolbar
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
// - Cancel button
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
// - Submit button
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
// - Cancel button
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
// - Submit button
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
// if ($aFavoriteOrgs == null) {
// // All checked
// $oP->add_ready_script(
// <<<JS
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
//JS
// );
//
// }
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
// if ($aFavoriteOrgs == null) {
// // All checked
// $oP->add_ready_script(
// <<<JS
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
//JS
// );
//
// }
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
}
//////////////////////////////////////////////////////////////////////////
//
// Shortcuts

View File

@@ -205,11 +205,12 @@ class DBBackup
*
* @param string $sSourceConfigFile
* @param string $sTmpFolder
* @param bool $bSkipSQLDumpForTesting
*
* @return array list of files to archive
* @throws \Exception
*/
protected function PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder)
protected function PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder, $bSkipSQLDumpForTesting = false)
{
$aRet = array();
if (is_dir($sTmpFolder))
@@ -226,7 +227,7 @@ class DBBackup
{
$sFile = $sTmpFolder.'/config-itop.php';
$this->LogInfo("backup: adding resource '$sSourceConfigFile'");
copy($sSourceConfigFile, $sFile);
@copy($sSourceConfigFile, $sFile); // During unattended install config file may be absent
$aRet[] = $sFile;
}
@@ -247,9 +248,41 @@ class DBBackup
SetupUtils::copydir($sExtraDir, $sFile);
$aRet[] = $sFile;
}
$sDataFile = $sTmpFolder.'/itop-dump.sql';
$this->DoBackup($sDataFile);
$aRet[] = $sDataFile;
if (MetaModel::GetConfig() !== null) // During unattended install config file may be absent
{
$aExtraFiles = MetaModel::GetModuleSetting('itop-backup', 'extra_files', []);
foreach($aExtraFiles as $sExtraFileOrDir)
{
if(!file_exists(APPROOT.'/'.$sExtraFileOrDir)) continue; // Ignore non-existing files
$sExtraFullPath = realpath(APPROOT.'/'.$sExtraFileOrDir);
if (strncmp(APPROOT, $sExtraFullPath, strlen(APPROOT)) !== 0)
{
throw new Exception("Backup: Aborting, resource '$sExtraFileOrDir'. Considered as UNSAFE because not inside the iTop directory.");
}
if (is_dir($sExtraFullPath))
{
$sFile = $sTmpFolder.'/'.$sExtraFileOrDir;
$this->LogInfo("backup: adding directory '$sExtraFileOrDir'");
SetupUtils::copydir($sExtraFullPath, $sFile);
$aRet[] = $sFile;
}
elseif (file_exists($sExtraFullPath))
{
$sFile = $sTmpFolder.'/'.$sExtraFileOrDir;
$this->LogInfo("backup: adding file '$sExtraFileOrDir'");
@mkdir(dirname($sFile), 0755, true);
copy($sExtraFullPath, $sFile);
$aRet[] = $sFile;
}
}
}
if (!$bSkipSQLDumpForTesting)
{
$sDataFile = $sTmpFolder.'/itop-dump.sql';
$this->DoBackup($sDataFile);
$aRet[] = $sDataFile;
}
return $aRet;
}

View File

@@ -23,15 +23,15 @@ use Combodo\iTop\DesignElement;
require_once(APPROOT.'setup/setuputils.class.inc.php');
require_once(APPROOT.'setup/modelfactory.class.inc.php');
require_once(APPROOT.'setup/parentmenunodecompiler.class.inc.php');
require_once(APPROOT.'core/moduledesign.class.inc.php');
class DOMFormatException extends Exception
{
/**
* Overrides the Exception default constructor to automatically add informations about the concerned node (path and
* line number)
*
*
* @param string $message
* @param $code
* @param $previous
@@ -49,7 +49,7 @@ class DOMFormatException extends Exception
/**
* Compiler class
*/
*/
class MFCompiler
{
const DATA_PRECOMPILED_FOLDER = 'data'.DIRECTORY_SEPARATOR.'precompiled_styles'.DIRECTORY_SEPARATOR;
@@ -315,7 +315,7 @@ class MFCompiler
apc_clear_cache();
}
}
/**
* Perform the actual "Compilation" of all modules
@@ -327,21 +327,16 @@ class MFCompiler
*/
protected function DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks = false)
{
$aAllClasses = array(); // flat list of classes
$aModulesInfo = array(); // Hash array of module_name => array('version' => string, 'root_dir' => string)
$aAllClasses = []; // flat list of classes
$aModulesInfo = []; // Hash array of module_name => array('version' => string, 'root_dir' => string)
// Determine the target modules for the MENUS
//
$aMenuNodes = array();
$aMenusByModule = array();
foreach ($this->oFactory->GetNodes('menus/menu') as $oMenuNode)
{
$sMenuId = $oMenuNode->getAttribute('id');
$aMenuNodes[$sMenuId] = $oMenuNode;
$sModuleMenu = $oMenuNode->getAttribute('_created_in');
$aMenusByModule[$sModuleMenu][] = $sMenuId;
}
/**
* @since 3.1 N°4762
*/
$oParentMenuNodeCompiler = new ParentMenuNodeCompiler($this);
$oParentMenuNodeCompiler->LoadXmlMenus($this->oFactory);
// Determine the target module (exactly one!) for USER RIGHTS
// This used to be based solely on the module which created the user_rights node first
@@ -386,6 +381,7 @@ class MFCompiler
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
$oParentMenuNodeCompiler->LoadModuleMenuInfo($aModules);
foreach ($aModules as $foo => $oModule) {
$sModuleName = $oModule->GetName();
$sModuleVersion = $oModule->GetVersion();
@@ -418,7 +414,7 @@ class MFCompiler
$sCompiledCode .= $this->CompileConstant($oConstant)."\n";
}
}
if (array_key_exists($sModuleName, $this->aSnippets))
{
foreach( $this->aSnippets[$sModuleName]['before'] as $aSnippet)
@@ -461,7 +457,7 @@ class MFCompiler
}
}
if (!array_key_exists($sModuleName, $aMenusByModule))
if (is_null($oParentMenuNodeCompiler->GetMenusByModule($sModuleName)))
{
$this->Log("Found module without menus declared: $sModuleName");
}
@@ -481,75 +477,19 @@ class $sMenuCreationClass extends ModuleHandlerAPI
global \$__comp_menus__; // ensure that the global variable is indeed global !
EOF;
// Preliminary: determine parent menus not defined within the current module
$aMenusToLoad = array();
$aParentMenus = array();
foreach($aMenusByModule[$sModuleName] as $sMenuId)
{
$oMenuNode = $aMenuNodes[$sMenuId];
if ($sParent = $oMenuNode->GetChildText('parent', null))
{
$aMenusToLoad[] = $sParent;
$aParentMenus[] = $sParent;
}
// Note: the order matters: the parents must be defined BEFORE
$aMenusToLoad[] = $sMenuId;
}
$aMenusToLoad = array_unique($aMenusToLoad);
$aMenuLinesForAll = array();
$aMenuLinesForAdmins = array();
$aAdminMenus = array();
foreach($aMenusToLoad as $sMenuId)
{
$oMenuNode = $aMenuNodes[$sMenuId];
if (is_null($oMenuNode))
{
throw new Exception("Module '{$oModule->GetId()}' (location : '$sModuleRootDir') contains an unknown menuId : '$sMenuId'");
}
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup')
{
// Note: this algorithm is wrong
// 1 - the module may appear empty in the current module, while children are defined in other modules
// 2 - check recursively that child nodes are not empty themselves
// Future algorithm:
// a- browse the modules and build the menu tree
// b- browse the tree and blacklist empty menus
// c- before compiling, discard if blacklisted
if (!in_array($oMenuNode->getAttribute("id"), $aParentMenus))
{
// Discard empty menu groups
continue;
}
}
try
{
$aMenuLines = $this->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
}
catch (DOMFormatException $e)
{
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
}
$sParent = $oMenuNode->GetChildText('parent', null);
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent]))
{
$aMenuLinesForAdmins = array_merge($aMenuLinesForAdmins, $aMenuLines);
$aAdminMenus[$oMenuNode->getAttribute("id")] = true;
}
else
{
$aMenuLinesForAll = array_merge($aMenuLinesForAll, $aMenuLines);
}
}
$oParentMenuNodeCompiler->CompileModuleMenus($oModule, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
$sIndent = "\t\t";
foreach ($aMenuLinesForAll as $sPHPLine)
foreach ($oParentMenuNodeCompiler->GetMenuLinesForAll() as $sPHPLine)
{
$sCompiledCode .= $sIndent.$sPHPLine."\n";
}
if (count($aMenuLinesForAdmins) > 0)
if (count($oParentMenuNodeCompiler->GetMenuLinesForAdmins()) > 0)
{
$sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n";
$sCompiledCode .= $sIndent."{\n";
foreach ($aMenuLinesForAdmins as $sPHPLine)
foreach ($oParentMenuNodeCompiler->GetMenuLinesForAdmins() as $sPHPLine)
{
$sCompiledCode .= $sIndent."\t".$sPHPLine."\n";
}
@@ -581,7 +521,7 @@ EOF;
$sCompiledCode .= $aSnippet['content']."\n";
}
}
// Create (overwrite if existing) the compiled file
//
if (strlen($sCompiledCode) > 0)
@@ -625,7 +565,7 @@ EOF;
$aWebservicesFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');";
}
} // foreach module
// Compile the dictionaries -out of the modules
//
$sDictDir = $sTempTargetDir.'/dictionaries';
@@ -654,7 +594,7 @@ EOF;
$this->sMainPHPCode .= $aSnippet['content']."\n";
}
}
// Compile the portals
$oPortalsNode = $this->oFactory->GetNodes('/itop_design/portals')->item(0);
$this->CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir);
@@ -666,7 +606,7 @@ EOF;
// Compile the XML parameters
$oParametersNode = $this->oFactory->GetNodes('/itop_design/module_parameters')->item(0);
$this->CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir);
if (array_key_exists('_core_', $this->aSnippets))
{
foreach( $this->aSnippets['_core_']['after'] as $aSnippet)
@@ -700,7 +640,7 @@ EOF;
$sCurrDate = date(DATE_ISO8601);
// Autoload
$sPHPFile = $sTempTargetDir.'/autoload.php';
$sPHPFileContent =
$sPHPFileContent =
<<<EOF
<?php
//
@@ -709,7 +649,7 @@ EOF;
//
EOF
;
$sPHPFileContent .= "\nMetaModel::IncludeModule(MODULESROOT.'/core/main.php');\n";
$sPHPFileContent .= implode("\n", $aDataModelFiles);
$sPHPFileContent .= implode("\n", $aWebservicesFiles);
@@ -717,14 +657,14 @@ EOF
$sModulesInfo = str_replace("'".$sRelFinalTargetDir."/", "\$sCurrEnv.'/", $sModulesInfo);
$sPHPFileContent .= "\nfunction GetModulesInfo()\n{\n\$sCurrEnv = 'env-'.utils::GetCurrentEnvironment();\nreturn ".$sModulesInfo.";\n}\n";
file_put_contents($sPHPFile, $sPHPFileContent);
} // DoCompile()
/**
* Helper to form a valid ZList from the array built by GetNodeAsArrayOfItems()
*
* @param array $aItems
*/
*/
protected function ArrayOfItemsToZList(&$aItems)
{
// Note: $aItems can be null in some cases so we have to protect it otherwise a PHP warning will be thrown during the foreach
@@ -756,7 +696,7 @@ EOF
* Helper to format the flags for an attribute, in a given state
* @param object $oAttNode DOM node containing the information to build the flags
* Returns string PHP flags, based on the OPT_ATT_ constants, or empty (meaning 0, can be omitted)
*/
*/
protected function FlagsToPHP($oAttNode)
{
static $aNodeAttributeToFlag = array(
@@ -766,7 +706,7 @@ EOF
'must_change' => 'OPT_ATT_MUSTCHANGE',
'hidden' => 'OPT_ATT_HIDDEN',
);
$aFlags = array();
foreach ($aNodeAttributeToFlag as $sNodeAttribute => $sFlag)
{
@@ -778,7 +718,7 @@ EOF
}
if (empty($aFlags))
{
$aFlags[] = 'OPT_ATT_NORMAL'; // When no flag is defined, reset the state to "normal"
$aFlags[] = 'OPT_ATT_NORMAL'; // When no flag is defined, reset the state to "normal"
}
$sRes = implode(' | ', $aFlags);
return $sRes;
@@ -800,7 +740,7 @@ EOF
'details' => 'LINKSET_TRACKING_DETAILS',
'all' => 'LINKSET_TRACKING_ALL',
);
static $aXmlToPHP_Others = array(
'none' => 'ATTRIBUTE_TRACKING_NONE',
'all' => 'ATTRIBUTE_TRACKING_ALL',
@@ -841,7 +781,7 @@ EOF
'in_place' => 'LINKSET_EDITMODE_INPLACE',
'add_remove' => 'LINKSET_EDITMODE_ADDREMOVE',
);
if (!array_key_exists($sEditMode, $aXmlToPHP))
{
throw new DOMFormatException("Edit mode: unknown value '$sEditMode'");
@@ -849,10 +789,10 @@ EOF
return $aXmlToPHP[$sEditMode];
}
/**
* Format a path (file or url) as an absolute path or relative to the module or the app
*/
*/
protected function PathToPHP($sPath, $sModuleRelativeDir, $bIsUrl = false)
{
if ($sPath == '')
@@ -945,7 +885,7 @@ EOF
else
{
throw new DOMFormatException("missing (or empty) mandatory tag '$sTag' under the tag '".$oNode->nodeName."'");
}
}
}
/**
@@ -1049,7 +989,7 @@ EOF
/**
* Adds quotes and escape characters
*/
*/
protected function QuoteForPHP($sStr, $bSimpleQuotes = false)
{
if ($bSimpleQuotes)
@@ -1084,7 +1024,7 @@ EOF
$sScalar = (string)(int)$sText;
}
break;
case 'float':
if (is_null($sText))
{
@@ -1096,7 +1036,7 @@ EOF
$sScalar = (string)(float)$sText;
}
break;
case 'bool':
if (is_null($sText))
{
@@ -1328,7 +1268,7 @@ EOF
// $oField
$sAttCode = $oField->getAttribute('id');
$sAttType = $oField->getAttribute('xsi:type');
$aDependencies = array();
$oDependencies = $oField->GetOptionalElement('dependencies');
if (!is_null($oDependencies))
@@ -1340,9 +1280,9 @@ EOF
}
}
$sDependencies = 'array('.implode(', ', $aDependencies).')';
$aParameters = array();
if ($sAttType == 'AttributeLinkedSetIndirect')
{
$aParameters['linked_class'] = $this->GetMandatoryPropString($oField, 'linked_class');
@@ -2416,7 +2356,7 @@ CSS;
* @return array
* @throws \DOMFormatException
*/
protected function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
public function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
{
$this->CompileFiles($oMenu, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, $sModuleRelativeDir);
@@ -2512,11 +2452,11 @@ CSS;
case '1':
$sSearchFormOpen = 'true';
break;
case '0':
$sSearchFormOpen = 'false';
break;
default:
$sSearchFormOpen = 'true';
}
@@ -2605,7 +2545,7 @@ CSS;
foreach($this->oFactory->ListFields($oClass) as $oField)
{
$sAttType = $oField->getAttribute('xsi:type');
if (($sAttType == 'AttributeExternalKey') || ($sAttType == 'AttributeHierarchicalKey'))
{
$sOnTargetDel = $oField->GetChildText('on_target_delete');
@@ -2631,7 +2571,7 @@ CSS;
$oClasses = $oGroup->GetUniqueElement('classes');
foreach($oClasses->getElementsByTagName('class') as $oClass)
{
$sClass = $oClass->getAttribute("id");
$aClasses[] = $sClass;
@@ -2649,7 +2589,7 @@ CSS;
$aProfiles[1] = array(
'name' => 'Administrator',
'description' => 'Has the rights on everything (bypassing any control)',
);
);
$aGrants = array();
$oProfiles = $oUserRightsNode->GetUniqueElement('profiles');
@@ -2681,7 +2621,7 @@ CSS;
}
$sGrant = $oAction->GetText();
$bGrant = ($sGrant == 'allow');
if ($sGroupId == '*')
{
$aGrantClasses = array('*');
@@ -2920,7 +2860,7 @@ Dict::SetLanguagesList(
$sLanguagesDump
);
EOF;
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
}
@@ -2957,7 +2897,7 @@ EOF;
{
throw new DOMFormatException('Could not find the file with ref '.$sFileId);
}
$sName = $oNodes->item(0)->GetChildText('name');
$sData = base64_decode($oNodes->item(0)->GetChildText('data'));
$aPathInfo = pathinfo($sName);
@@ -2971,7 +2911,7 @@ EOF;
}
$oParentNode = $oFileRef->parentNode;
$oParentNode->removeChild($oFileRef);
$oTextNode = $oParentNode->ownerDocument->createTextNode($sRelativePath.'/images/'.$sFile);
$oParentNode->appendChild($oTextNode);
}
@@ -3284,8 +3224,8 @@ EOF;
{
SetupUtils::rrmdir($sTempTargetDir.'/branding/images');
}
// Compile themes
// Compile themes
$this->CompileThemes($oBrandingNode, $sTempTargetDir);
}
}
@@ -3326,11 +3266,11 @@ EOF;
{
$aPortalsConfig[$sPortalId]['deny'][] = $oProfile->getAttribute('id');
}
}
}
}
uasort($aPortalsConfig, array(get_class($this), 'SortOnRank'));
$this->sMainPHPCode .= "\n";
$this->sMainPHPCode .= "/**\n";
$this->sMainPHPCode .= " * Portal(s) definition(s) extracted from the XML definition at compile time\n";
@@ -3373,7 +3313,7 @@ EOF;
$oParamsReader = new MFParameters($oParams);
$aParametersConfig[$sModuleId] = $oParamsReader->GetAll();
}
$this->sMainPHPCode .= "\n";
$this->sMainPHPCode .= "/**\n";
$this->sMainPHPCode .= " * Modules parameters extracted from the XML definition at compile time\n";

View File

@@ -0,0 +1,287 @@
<?php
/**
* @since 3.1 N°4762
*/
class ParentMenuNodeCompiler
{
const COMPILED = 1;
const COMPILING = 2;
public static $bUseLegacyMenuCompilation = false;
/**
* @var MFCompiler
*/
private $oMFCompiler;
/**
* admin menus declaration lines: result of module menu compilation
* @var array
*/
private $aMenuLinesForAdmins = [];
/**
* non-admin menus declaration lines: result of module menu compilation
* @var array
*/
private $aMenuLinesForAll = [];
/**
* use to handle menu group compilation recurring algorithm
* @var array
*/
private $aMenuProcessStatus = [];
/**
* @var array
*/
private $aMenuNodes = [];
/**
* @var array
*/
private $aMenusByModule = [];
/**
* @var array
*/
private $aMenusToLoadByModule = [];
/**
* @var array
*/
private $aParentMenusByModule = [];
/**
* used by overall algo
* @var array
*/
private $aParentMenuNodes = [];
/**
* used by new algo
* @var array
*/
private $aParentAdminMenus = [];
/**
* used by overall algo
* @var array
*/
private $aParentModuleRootDirs = [];
public function __construct(MFCompiler $oMFCompiler) {
$this->oMFCompiler = $oMFCompiler;
}
public static function UseLegacyMenuCompilation(){
self::$bUseLegacyMenuCompilation = true;
}
/**
* @param \ModelFactory $oFactory
* Initialize menu nodes arrays
* @return void
*/
public function LoadXmlMenus(\ModelFactory $oFactory) : void {
foreach ($oFactory->GetNodes('menus/menu') as $oMenuNode) {
$sMenuId = $oMenuNode->getAttribute('id');
$this->aMenuNodes[$sMenuId] = $oMenuNode;
$sModuleMenu = $oMenuNode->getAttribute('_created_in');
$this->aMenusByModule[$sModuleMenu][] = $sMenuId;
}
}
/**
* @param $aModules
* Initialize arrays related to parent/child menus
* @return void
*/
public function LoadModuleMenuInfo($aModules) : void
{
foreach ($aModules as $foo => $oModule) {
$sModuleRootDir = $oModule->GetRootDir();
$sModuleName = $oModule->GetName();
if (array_key_exists($sModuleName, $this->aMenusByModule)) {
$aMenusToLoad = [];
$aParentMenus = [];
foreach ($this->aMenusByModule[$sModuleName] as $sMenuId) {
$oMenuNode = $this->aMenuNodes[$sMenuId];
if (self::$bUseLegacyMenuCompilation){
if ($sParent = $oMenuNode->GetChildText('parent', null)) {
$aMenusToLoad[] = $sParent;
$aParentMenus[] = $sParent;
}
} else {
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup') {
$this->aParentModuleRootDirs[$sMenuId] = $sModuleRootDir;
}
if ($sParent = $oMenuNode->GetChildText('parent', null)) {
$aMenusToLoad[] = $sParent;
$aParentMenus[] = $sParent;
$this->aParentModuleRootDirs[$sParent] = $sModuleRootDir;
}
if (array_key_exists($sMenuId, $this->aParentModuleRootDirs)){
$this->aParentMenuNodes[$sMenuId] = $oMenuNode;
}
}
// Note: the order matters: the parents must be defined BEFORE
$aMenusToLoad[] = $sMenuId;
}
$this->aMenusToLoadByModule[$sModuleName] = array_unique($aMenusToLoad);
$this->aParentMenusByModule[$sModuleName] = array_unique($aParentMenus);
}
}
}
/**
* Perform the actual "Compilation" for one module at a time
* @param \MFModule $oModule
* @param string $sTempTargetDir
* @param string $sFinalTargetDir
* @param string $sRelativeDir
* @param Page $oP
*
* @return void
* @throws \Exception
*/
public function CompileModuleMenus(MFModule $oModule, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP = null) : void
{
$this->aMenuLinesForAdmins = [];
$this->aMenuLinesForAll = [];
$aAdminMenus = [];
$sModuleRootDir = $oModule->GetRootDir();
$sModuleName = $oModule->GetName();
$aParentMenus = $this->aParentMenusByModule[$sModuleName];
foreach($this->aMenusToLoadByModule[$sModuleName] as $sMenuId)
{
$oMenuNode = $this->aMenuNodes[$sMenuId];
if (is_null($oMenuNode))
{
throw new Exception("Module '{$oModule->GetId()}' (location : '$sModuleRootDir') contains an unknown menuId : '$sMenuId'");
}
if (self::$bUseLegacyMenuCompilation) {
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup') {
// Note: this algorithm is wrong
// 1 - the module may appear empty in the current module, while children are defined in other modules
// 2 - check recursively that child nodes are not empty themselves
// Future algorithm:
// a- browse the modules and build the menu tree
// b- browse the tree and blacklist empty menus
// c- before compiling, discard if blacklisted
if (! in_array($oMenuNode->getAttribute("id"), $aParentMenus)) {
// Discard empty menu groups
continue;
}
}
} else {
if (array_key_exists($sMenuId, $this->aParentMenuNodes)) {
// compile parent menus recursively
$this->CompileParentMenuNode($sMenuId, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
continue;
}
}
try
{
//both new/legacy algo: compile leaf menu
$aMenuLines = $this->oMFCompiler->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
}
catch (DOMFormatException $e)
{
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
}
$sParent = $oMenuNode->GetChildText('parent', null);
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent]) || isset($this->aParentAdminMenus[$sParent]))
{
$this->aMenuLinesForAdmins = array_merge($this->aMenuLinesForAdmins, $aMenuLines);
$aAdminMenus[$oMenuNode->getAttribute("id")] = true;
}
else
{
$this->aMenuLinesForAll = array_merge($this->aMenuLinesForAll, $aMenuLines);
}
}
}
/**
* Perform parent menu compilation including its ancestrors (recursively)
* @param string $sMenuId
* @param string $sTempTargetDir
* @param string $sFinalTargetDir
* @param string $sRelativeDir
* @param Page $oP
*
* @return void
* @throws \Exception
*/
public function CompileParentMenuNode(string $sMenuId, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP = null) : void
{
$oMenuNode = $this->aParentMenuNodes[$sMenuId];
$sStatus = array_key_exists($sMenuId, $this->aMenuProcessStatus) ? $this->aMenuProcessStatus[$sMenuId] : null;
if ($sStatus === self::COMPILED){
//node already processed before
return;
} else if ($sStatus === self::COMPILING){
throw new \Exception("Cyclic dependency between parent menus ($sMenuId)");
}
$this->aMenuProcessStatus[$sMenuId] = self::COMPILING;
try {
$sParent = $oMenuNode->GetChildText('parent', null);
if (! empty($sParent)){
//compile parents before (even parent of parents ... recursively)
$this->CompileParentMenuNode($sParent, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
}
if (! array_key_exists($sMenuId, $this->aParentModuleRootDirs)){
throw new Exception("Failed to process parent menu '$sMenuId' that is referenced by a child but not defined");
}
$sModuleRootDir = $this->aParentModuleRootDirs[$sMenuId];
$aMenuLines = $this->oMFCompiler->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
} catch (DOMFormatException $e) {
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
}
$sParent = $oMenuNode->GetChildText('parent', null);
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($this->aParentAdminMenus[$sParent])) {
$this->aMenuLinesForAdmins = array_merge($this->aMenuLinesForAdmins, $aMenuLines);
$this->aParentAdminMenus[$oMenuNode->getAttribute("id")] = true;
} else {
$this->aMenuLinesForAll = array_merge($this->aMenuLinesForAll, $aMenuLines);
}
$this->aMenuProcessStatus[$sMenuId] = self::COMPILED;
}
public function GetMenusByModule(string $sModuleName) : ?array
{
if (array_key_exists($sModuleName, $this->aMenusByModule)) {
return $this->aMenusByModule[$sModuleName];
}
return null;
}
public function GetMenuLinesForAdmins(): array {
return $this->aMenuLinesForAdmins;
}
public function GetMenuLinesForAll(): array {
return $this->aMenuLinesForAll;
}
}

View File

@@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Field;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
use utils;
/**
* @since 3.0.0
@@ -45,6 +46,11 @@ class Field extends UIContentBlock
protected $sValueRaw;
/** @var string */
protected $sLabel;
/**
* @var string
* @since 3.1.0
*/
protected $sDescription = '';
/** @var string */
protected $sValueId;
@@ -354,4 +360,34 @@ class Field extends UIContentBlock
return $this;
}
/**
* @return string
* @since 3.1.0
*/
public function GetDescription(): string
{
return $this->sDescription;
}
/**
* @param string $sDescription
*
* @return $this
* @since 3.1.0
*/
public function SetDescription(string $sDescription)
{
$this->sDescription = $sDescription;
return $this;
}
/*
* @return bool
* @since 3.1.0
*/
public function HasDescription(): bool
{
return utils::IsNotNullOrEmptyString($this->GetDescription());
}
}

View File

@@ -86,23 +86,51 @@ class FieldUIBlockFactory extends AbstractUIBlockFactory
return $oField;
}
public static function MakeLarge(string $sLabel, string $sValueHtml = '')
/**
* @api
* @param string $sLabel
* @param string $sValueHtml
* @param string $sDescription
*
* @return \Combodo\iTop\Application\UI\Base\Component\Field\Field
*/
public static function MakeLarge(string $sLabel, string $sValueHtml = '', string $sDescription = '')
{
$oField = new Field($sLabel, new Html($sValueHtml));
$oField->SetDescription($sDescription);
$oField->SetLayout(Field::ENUM_FIELD_LAYOUT_LARGE);
return $oField;
}
public static function MakeSmall(string $sLabel, string $sValueHtml = '')
/**
* @api
* @param string $sLabel
* @param string $sValueHtml
* @param string $sDescription
*
* @return \Combodo\iTop\Application\UI\Base\Component\Field\Field
*/
public static function MakeSmall(string $sLabel, string $sValueHtml = '', string $sDescription = '')
{
$oField = new Field($sLabel, new Html($sValueHtml));
$oField->SetDescription($sDescription);
$oField->SetLayout(Field::ENUM_FIELD_LAYOUT_SMALL);
return $oField;
}
public static function MakeStandard(string $sLabel = '', string $sLayout = Field::ENUM_FIELD_LAYOUT_SMALL, ?string $sId = null)
/**
* @api
* @param string $sLabel
* @param string $sLayout
* @param string|null $sId
* @param string $sDescription
*
* @return \Combodo\iTop\Application\UI\Base\Component\Field\Field
*/
public static function MakeStandard(string $sLabel = '', string $sLayout = Field::ENUM_FIELD_LAYOUT_SMALL, ?string $sId = null, string $sDescription = '')
{
$oField = new Field($sLabel, null, $sId);
$oField->SetDescription($sDescription);
$oField->SetLayout($sLayout);
return $oField;

View File

@@ -49,6 +49,16 @@ class NewsroomMenuFactory
return $oMenu;
}
/**
* Check if there is any Newsroom provider configured
* @return boolean
*/
public static function HasProviders()
{
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
return count($aProviders) > 0;
}
/**
* Prepare parameters for the newsroom JS widget
*

View File

@@ -19,7 +19,6 @@
namespace Combodo\iTop\Application\UI\Base\Layout\NavigationMenu;
use ApplicationContext;
use ApplicationMenu;
use appUserPreferences;
@@ -35,6 +34,7 @@ use MetaModel;
use UIExtKeyWidget;
use UserRights;
use utils;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\NewsroomMenu\NewsroomMenuFactory;
/**
* Class NavigationMenu
@@ -196,7 +196,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
}
return '';
}
/**
* @return array
*/
@@ -269,7 +269,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
*/
public function IsNewsroomEnabled(): bool
{
return MetaModel::GetConfig()->Get('newsroom_enabled');
return (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders());
}
/**
@@ -290,6 +290,14 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
}
}
/**
* @return True if the silo selection is enabled, false otherwise
* @since 3.1.0
*/
public function IsSiloSelectionEnabled() : bool {
return MetaModel::GetConfig()->Get('navigation_menu.show_organization_filter');
}
/**
* @return void
* @throws \CoreException
@@ -301,6 +309,10 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
$this->bHasSiloSelected = false;
$this->sSiloLabel = null;
if (! $this->IsSiloSelectionEnabled()){
return;
}
//TODO 3.0 Use components if we have the time to build select/autocomplete components before release
// List of visible Organizations
$iCount = 0;
@@ -343,7 +355,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
$this->aSiloSelection['html'] = '<form data-role="ibo-navigation-menu--silo-selection--form" action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php">'; //<select class="org_combo" name="c[org_id]" title="Pick an organization" onChange="this.form.submit();">';
$oPage = new \CaptureWebPage();
$oWidget = new UIExtKeyWidget('Organization', 'org_id', '', true /* search mode */);
$iMaxComboLength = MetaModel::GetConfig()->Get('max_combo_length');
$this->aSiloSelection['html'] .= $oWidget->DisplaySelect($oPage, $iMaxComboLength, false, Dict::S('UI:Layout:NavigationMenu:Silo:Label'), $oSet, $iCurrentOrganization, false, 'c[org_id]', '',
@@ -376,7 +388,7 @@ $sAddClearButton
JS;
}
}
/**
* Compute if the menu is expanded or collapsed
*
@@ -479,4 +491,4 @@ JS;
{
return "[data-role='".static::BLOCK_CODE."']";
}
}
}

View File

@@ -48,7 +48,7 @@ class NavigationMenuFactory
{
$oNewsroomMenu = null;
if (MetaModel::GetConfig()->Get('newsroom_enabled'))
if (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders())
{
$oNewsroomMenu = NewsroomMenuFactory::MakeNewsroomMenuForNavigationMenu();
}

View File

@@ -46,7 +46,7 @@ function UsageAndExit($oP)
}
else
{
$oP->p("The parameter 'data_sources' is mandatory, and must contain a comma separated list of data sources\n");
$oP->p("The parameter 'data_sources' is mandatory, and must contain a comma separated list of data sources\n");
}
$oP->output();
exit -2;
@@ -105,6 +105,9 @@ if (utils::IsModeCLI())
else
{
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
//N°6022 - Make synchro scripts work by http via token authentication with SYNCHRO scopes
$oCtx = new ContextTag(ContextTag::TAG_SYNCHRO);
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
}
@@ -166,7 +169,7 @@ foreach(explode(',', $sDataSourcesList) as $iSDS)
}
catch(Exception $e)
{
$oP->add($e->getMessage());
$oP->add($e->getMessage());
if ($bSimulate)
{
CMDBSource::Query('ROLLBACK');

View File

@@ -255,7 +255,7 @@ if (utils::IsModeCLI())
{
// Next steps:
// specific arguments: 'csvfile'
//
//
$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data');
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data');
$sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data');
@@ -282,6 +282,9 @@ if (utils::IsModeCLI())
else
{
require_once APPROOT.'/application/loginwebpage.class.inc.php';
//N°6022 - Make synchro scripts work by http via token authentication with SYNCHRO scopes
$oCtx = new ContextTag(ContextTag::TAG_SYNCHRO);
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
$sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data');

View File

@@ -23,6 +23,9 @@
{% endif %}
>
<div class="ibo-field--label">{{ oUIBlock.GetLabel()|raw }}
{% if oUIBlock.HasDescription() %}
<span class="ibo-has-description" data-tooltip-content="{{ oUIBlock.GetDescription() }}" data-tooltip-max-width="600px" ></span>
{% endif %}
{% if oUIBlock.GetLayout() == constant("Combodo\\iTop\\Application\\UI\\Base\\Component\\Field\\Field::ENUM_FIELD_LAYOUT_LARGE") %}
{% if oUIBlock.GetComments() %}
<div class="ibo-field--comments">{{ oUIBlock.GetComments()|raw }}</div>

View File

@@ -27,8 +27,8 @@ namespace Combodo\iTop\Test\UnitTest;
*/
use ArchivedObjectException;
use CMDBSource;
use CMDBObject;
use CMDBSource;
use Contact;
use DBObject;
use DBObjectSet;
@@ -410,10 +410,25 @@ class ItopDataTestCase extends ItopTestCase
* @param string $sLogin
* @param int $iProfileId
*
* @return \DBObject
* @return \UserLocal
* @throws Exception
*/
protected function CreateUser($sLogin, $iProfileId, $sPassword=null, $iContactid=2)
{
$oUser = $this->CreateContactlessUser($sLogin, $iProfileId, $sPassword);
$oUser->Set('contactid', $iContactid);
$oUser->DBWrite();
return $oUser;
}
/**
* @param string $sLogin
* @param int $iProfileId
*
* @return \UserLocal
* @throws Exception
*/
protected function CreateContactlessUser($sLogin, $iProfileId, $sPassword=null)
{
if (empty($sPassword)){
$sPassword = $sLogin;
@@ -423,8 +438,8 @@ class ItopDataTestCase extends ItopTestCase
$oUserProfile->Set('profileid', $iProfileId);
$oUserProfile->Set('reason', 'UNIT Tests');
$oSet = DBObjectSet::FromObject($oUserProfile);
/** @var \UserLocal $oUser */
$oUser = $this->createObject('UserLocal', array(
'contactid' => $iContactid,
'login' => $sLogin,
'password' => $sPassword,
'language' => 'EN US',
@@ -450,7 +465,7 @@ class ItopDataTestCase extends ItopTestCase
/** @var DBObjectSet $oSet */
$oSet = $oUser->Get('profile_list');
$oSet->AddItem($oUserProfile);
$oUser = $this->updateObject('UserLocal', $oUser->GetKey(), array(
$oUser = $this->updateObject(\User::class, $oUser->GetKey(), array(
'profile_list' => $oSet,
));
$this->debug("Updated {$oUser->GetName()} ({$oUser->GetKey()})");

View File

@@ -0,0 +1,67 @@
<?php
namespace UI\Base\Layout;
use ApplicationContext;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Layout\NavigationMenu\NavigationMenu;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
/**
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
* Class NavigationMenuTest
*
* @package UI\Base\Layout
*/
class NavigationMenuTest extends ItopDataTestCase {
public function IsAllowedProvider(){
return [
'show menu' => [ true ],
'hide menu' => [ false ],
];
}
/**
* @dataProvider IsAllowedProvider
* test used to make sure backward compatibility is ensured
*/
public function testIsAllowed($bExpectedIsAllowed=true){
\MetaModel::GetConfig()->Set('navigation_menu.show_organization_filter', $bExpectedIsAllowed);
$oNavigationMenu = new NavigationMenu(
$this->createMock(ApplicationContext::class),
$this->createMock(PopoverMenu::class));
$isAllowed = $oNavigationMenu->IsSiloSelectionEnabled();
$this->assertEquals($bExpectedIsAllowed, $isAllowed);
}
public function testIsAllowed_BackwardCompatibility_NoVariableInConfFile(){
\MetaModel::GetConfig()->Set('navigation_menu.show_organization_filter', false);
$sTmpFilePath = tempnam(sys_get_temp_dir(), 'test_');
$oInitConfig = \MetaModel::GetConfig();
$oInitConfig->WriteToFile($sTmpFilePath);
//remove variable for the test
$aLines = file($sTmpFilePath);
$aRows = array();
foreach ($aLines as $key => $sLine) {
if (!preg_match('/navigation_menu.show_organization_filter/', $sLine)) {
$aRows[] = $sLine;
}
}
file_put_contents($sTmpFilePath, implode("\n", $aRows));
$oTempConfig = new \Config($sTmpFilePath);
$isAllowed = $oTempConfig->Get('navigation_menu.show_organization_filter');
$this->assertEquals(true, $isAllowed);
unlink($sTmpFilePath);
}
}

View File

@@ -180,7 +180,7 @@ class BulkChangeExtKeyTest extends ItopDataTestCase {
);
$this->performBulkChangeTest(
"invalid value for attribute",
"Invalid value for attribute",
"Ambiguous: found 2 objects",
null,
$bIsRackReconKey,
@@ -206,7 +206,7 @@ class BulkChangeExtKeyTest extends ItopDataTestCase {
$aCsvData = [["UnexistingRackDescription"]];
$aExtKeys = ["org_id" => ["name" => 0], "rack_id" => ["name" => 1, "description" => 3]];
$sSearchLinkUrl = 'UI.php?operation=search&filter=%5B%22SELECT+%60Rack%60+FROM+Rack+AS+%60Rack%60+WHERE+%28%28%60Rack%60.%60name%60+%3D+%3Aname%29+AND+%28%60Rack%60.%60description%60+%3D+%3Adescription%29%29%22%2C%7B%22name%22%3A%22UnexistingRack%22%2C%22description%22%3A%22UnexistingRackDescription%22%7D%2C%5B%5D%5D'
$sSearchLinkUrl = 'UI.php?operation=search&filter=%255B%2522SELECT%2B%2560Rack%2560%2BFROM%2BRack%2BAS%2B%2560Rack%2560%2BWHERE%2B%2528%2528%2560Rack%2560.%2560name%2560%2B%253D%2B%253Aname%2529%2BAND%2B%2528%2560Rack%2560.%2560description%2560%2B%253D%2B%253Adescription%2529%2529%2522%252C%257B%2522name%2522%253A%2522UnexistingRack%2522%252C%2522description%2522%253A%2522UnexistingRackDescription%2522%257D%252C%255B%255D%255D'
;
$this->performBulkChangeTest(
"No match for value 'UnexistingRack UnexistingRackDescription'",
@@ -238,7 +238,7 @@ class BulkChangeExtKeyTest extends ItopDataTestCase {
public function performBulkChangeTest($sExpectedDisplayableValue, $sExpectedDescription, $oOrg, $bIsRackReconKey,
$aAdditionalCsvData=null, $aExtKeys=null, $sSearchLinkUrl=null, $sError="Object not found") {
if ($sSearchLinkUrl===null){
$sSearchLinkUrl = 'UI.php?operation=search&filter=%5B%22SELECT+%60Rack%60+FROM+Rack+AS+%60Rack%60+WHERE+%28%60Rack%60.%60name%60+%3D+%3Aname%29%22%2C%7B%22name%22%3A%22UnexistingRack%22%7D%2C%5B%5D%5D';
$sSearchLinkUrl = 'UI.php?operation=search&filter=%255B%2522SELECT%2B%2560Rack%2560%2BFROM%2BRack%2BAS%2B%2560Rack%2560%2BWHERE%2B%2528%2560Rack%2560.%2560name%2560%2B%253D%2B%253Aname%2529%2522%252C%257B%2522name%2522%253A%2522UnexistingRack%2522%257D%252C%255B%255D%255D';
}
if (is_null($oOrg)){
$iOrgId = $this->getTestOrgId();

View File

@@ -23,7 +23,7 @@ class BulkChangeTest extends ItopDataTestCase {
//bug 2888: csv import / data synchro issue with password validation
public function testPasswordBulkChangeIssue() {
/** @var Personn $oPerson */
/** @var \Person $oPerson */
$oPerson = $this->createObject('Person', array(
'first_name' => 'isaac',
'name' => 'asimov',
@@ -145,7 +145,7 @@ class BulkChangeTest extends ItopDataTestCase {
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
["org_id" => ["name" => 0]],
["id"],
[ "org_id" => "invalid value for attribute", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
[ "org_id" => "Invalid value for attribute", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
],
"Case 6 : Unexpected value" => [
[["Demo", "Server1", "1", "<svg onclick\"alert(1)\">", ""]],
@@ -351,7 +351,7 @@ class BulkChangeTest extends ItopDataTestCase {
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
["org_id" => ["name" => 0]],
["id"],
[ 0 => "", "org_id" => "invalid value for attribute", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
[ 0 => "", "org_id" => "Invalid value for attribute", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
"__ERRORS__" => "Null not allowed",
],
],

View File

@@ -108,8 +108,8 @@ class CMDBObjectTest extends ItopDataTestCase
$sAdminLogin = "admin-user-".$sUid;
$sImpersonatedLogin = "impersonated-user-".$sUid;
$iAdminUserId = $this->CreateUserForImpersonation($sAdminLogin, 'Administrator', 'AdminName', 'AdminSurName');
$this->CreateUserForImpersonation($sImpersonatedLogin, 'Configuration Manager', 'ImpersonatedName', 'ImpersonatedSurName');
$oAdminUser = $this->CreateUserForImpersonation($sAdminLogin, 'Administrator', 'AdminName', 'AdminSurName');
$oImpersonatedUser = $this->CreateUserForImpersonation($sImpersonatedLogin, 'Configuration Manager', 'ImpersonatedName', 'ImpersonatedSurName');
$_SESSION = [];
\UserRights::Login($sAdminLogin);
@@ -124,28 +124,31 @@ class CMDBObjectTest extends ItopDataTestCase
if (is_null($sTrackInfo)){
CMDBObject::SetTrackInfo(null);
} else {
$sTrackInfo = $this->ReplaceByFriendlyNames($sTrackInfo, $oAdminUser, $oImpersonatedUser);
CMDBObject::SetTrackInfo($sTrackInfo);
}
$this->CreateSimpleObject();
if (is_null($sTrackInfo)){
self::assertEquals("AdminSurName AdminName", CMDBObject::GetCurrentChange()->Get('userinfo'),
self::assertEquals($oAdminUser->GetFriendlyName(), CMDBObject::GetCurrentChange()->Get('userinfo'),
'TrackInfo : no impersonation');
} else {
self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'),
'TrackInfo : no impersonation');
}
self::assertEquals($iAdminUserId, CMDBObject::GetCurrentChange()->Get('user_id'),
self::assertEquals($oAdminUser->GetKey(), CMDBObject::GetCurrentChange()->Get('user_id'),
'TrackInfo : admin userid');
\UserRights::Impersonate($sImpersonatedLogin);
$this->CreateSimpleObject();
if (is_null($sExpectedChangeLogWhenImpersonation)){
self::assertEquals("AdminSurName AdminName on behalf of ImpersonatedSurName ImpersonatedName", CMDBObject::GetCurrentChange()->Get('userinfo'),
$sExpectedMsg = $this->ReplaceByFriendlyNames("AdminSurName AdminName on behalf of ImpersonatedSurName ImpersonatedName", $oAdminUser, $oImpersonatedUser);
self::assertEquals($sExpectedMsg, CMDBObject::GetCurrentChange()->Get('userinfo'),
'TrackInfo : impersonation');
} else {
self::assertEquals($sExpectedChangeLogWhenImpersonation, CMDBObject::GetCurrentChange()->Get('userinfo'),
$sExpectedMsg = $this->ReplaceByFriendlyNames($sExpectedChangeLogWhenImpersonation, $oAdminUser, $oImpersonatedUser);
self::assertEquals($sExpectedMsg, CMDBObject::GetCurrentChange()->Get('userinfo'),
'TrackInfo : impersonation');
}
@@ -155,13 +158,13 @@ class CMDBObjectTest extends ItopDataTestCase
\UserRights::Deimpersonate();
$this->CreateSimpleObject();
if (is_null($sTrackInfo)){
self::assertEquals("AdminSurName AdminName", CMDBObject::GetCurrentChange()->Get('userinfo'),
self::assertEquals($oAdminUser->GetFriendlyName(), CMDBObject::GetCurrentChange()->Get('userinfo'),
'TrackInfo : no impersonation');
} else {
self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'),
'TrackInfo : no impersonation');
}
self::assertEquals($iAdminUserId, CMDBObject::GetCurrentChange()->Get('user_id'),
self::assertEquals($oAdminUser->GetKey(), CMDBObject::GetCurrentChange()->Get('user_id'),
'TrackInfo : admin userid');
// restore initial conditions
@@ -169,6 +172,12 @@ class CMDBObjectTest extends ItopDataTestCase
CMDBObject::SetTrackInfo($sInitialTrackInfo);
}
private function ReplaceByFriendlyNames($sMessage, $oAdminUser, $oImpersonatedUser) : string {
$sNewMessage = str_replace('AdminSurName AdminName', $oAdminUser->GetFriendlyName(), $sMessage);
$sNewMessage = str_replace('ImpersonatedSurName ImpersonatedName', $oImpersonatedUser->GetFriendlyName(), $sNewMessage);
return $sNewMessage;
}
private function CreateSimpleObject(){
/** @var \DocumentWeb $oTestObject */
$oTestObject = MetaModel::NewObject('DocumentWeb');
@@ -178,7 +187,7 @@ class CMDBObjectTest extends ItopDataTestCase
$oTestObject->DBWrite();
}
private function CreateUserForImpersonation($sLogin, $sProfileName, $sName, $sSurname): int {
private function CreateUserForImpersonation($sLogin, $sProfileName, $sName, $sSurname): \UserLocal {
/** @var \Person $oPerson */
$oPerson = $this->createObject('Person', array(
'name' => $sName,
@@ -187,8 +196,9 @@ class CMDBObjectTest extends ItopDataTestCase
));
$oProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => $sProfileName), true);
/** @var \UserLocal $oUser */
$oUser = $this->CreateUser($sLogin, $oProfile->GetKey(), "1234567Azert@", $oPerson->GetKey());
return $oUser->GetKey();
return $oUser;
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBBackup;
use DBRestore;
use MetaModel;
use SetupUtils;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class DBBackupDataTest extends ItopDataTestCase
{
/**
* @dataProvider prepareFilesToBackupProvider
*/
public function testPrepareFilesToBackup(array $aExtraFiles, bool $bUnsafeFileException)
{
$sTmpDir = sys_get_temp_dir().'/testPrepareFilesToBackup-'.time();
$oBackup = new DBBackup(MetaModel::GetConfig());
MetaModel::GetConfig()->SetModuleSetting('itop-backup', 'extra_files', array_keys($aExtraFiles));
foreach($aExtraFiles as $sExtraFile => $bExists)
{
if ($bExists)
{
@mkdir(dirname(APPROOT.'/'.$sExtraFile), 0755, true);
file_put_contents(APPROOT.'/'.$sExtraFile, 'Hello World!');
}
}
if ($bUnsafeFileException)
{
$this->expectExceptionMessage("Backup: Aborting, resource '$sExtraFile'. Considered as UNSAFE because not inside the iTop directory.");
}
$aFiles = $this->InvokeNonPublicMethod('DBBackup', 'PrepareFilesToBackup', $oBackup, [APPROOT.'/conf/production/config-itop.php', $sTmpDir, true]);
SetupUtils::rrmdir($sTmpDir);
$aExpectedFiles = [
$sTmpDir.'/config-itop.php',
];
foreach($aExtraFiles as $sRelFile => $bExists)
{
if ($bExists)
{
$aExpectedFiles[] = $sTmpDir.'/'.$sRelFile;
}
}
sort($aFiles);
sort($aExpectedFiles);
$this->assertEquals($aFiles, $aExpectedFiles);
// Cleanup
foreach($aExtraFiles as $sExtraFile => $bExists)
{
if ($bExists)
{
unlink(APPROOT.'/'.$sExtraFile);
}
}
}
function prepareFilesToBackupProvider()
{
return [
'no_extra_file' => ['aExtraFiles' => [], false],
'one_extra_file' => ['aExtraFiles' => ['foo.txt' => true], false],
'three_extra_file_and_dir' => ['aExtraFiles' => ['foo.txt' => true, 'gabu/zomeu.xml' => true, 'meuh.html' => true], false],
'two_extra_file_but_only_one_exists' => ['aExtraFiles' => ['foo.txt' => true, 'meuh.html' => false], false],
'one_unsafe_file' => ['aExtraFiles' => ['../foo.txt' => true], true],
];
}
/**
* @dataProvider restoreListExtraFilesProvider
*/
function testRestoreListExtraFiles($aFilesToCreate, $aExpectedRelativeExtraFiles)
{
require_once(APPROOT.'/env-production/itop-backup/dbrestore.class.inc.php');
$sTmpDir = sys_get_temp_dir().'/testRestoreListExtraFiles-'.time();
foreach($aFilesToCreate as $sRelativeName)
{
$sDir = $sTmpDir.'/'.dirname($sRelativeName);
if(!is_dir($sDir))
{
mkdir($sDir, 0755, true);
}
file_put_contents($sTmpDir.'/'.$sRelativeName, 'Hello world.');
}
$aExpectedExtraFiles = [];
foreach($aExpectedRelativeExtraFiles as $sRelativeName)
{
$aExpectedExtraFiles[$sTmpDir.'/'.$sRelativeName] = APPROOT.'/'.$sRelativeName;
}
$oRestore = new DBRestore(MetaModel::GetConfig());
$aExtraFiles = $this->InvokeNonPublicMethod('DBRestore', 'ListExtraFiles', $oRestore, [$sTmpDir]);
asort($aExtraFiles);
asort($aExpectedExtraFiles);
$this->assertEquals($aExpectedExtraFiles, $aExtraFiles);
SetupUtils::rrmdir($sTmpDir);
}
function restoreListExtraFilesProvider()
{
return [
'no extra file' => ['aFilesToCreate' => ['config-itop.php', 'itop-dump.sql', 'delta.xml'], 'aExpectedExtraFiles' => []],
'no extra file (2)' => ['aFilesToCreate' => ['config-itop.php', 'itop-dump.sql', 'delta.xml', 'production-modules/test/module.test.php'], 'aExpectedExtraFiles' => []],
'one extra file' => ['aFilesToCreate' => ['config-itop.php', 'itop-dump.sql', 'delta.xml', 'production-modules/test/module.test.php', 'collectors/ldap/conf/params.local.xml'], 'aExpectedExtraFiles' => ['collectors/ldap/conf/params.local.xml']],
];
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Setup;
use ApplicationMenu;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Config;
use MetaModel;
use MFCompiler;
use ParentMenuNodeCompiler;
use RunTimeEnvironment;
/**
* @group menu_compilation
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
* @since 3.1 N°4762
* @covers \MFCompiler::DoCompile
*/
class MFCompilerMenuTest extends ItopTestCase {
private static $aPreviousEnvMenus;
private static $aPreviousEnvMenuCount;
public function setUp(): void {
parent::setUp();
require_once APPROOT . 'setup/compiler.class.inc.php';
require_once APPROOT . 'setup/modelfactory.class.inc.php';
require_once APPROOT . 'application/utils.inc.php';
}
public function tearDown(): void {
parent::tearDown();
}
private function GetCurrentEnvDeltaXmlPath(string $sEnv) : string {
return APPROOT."data/$sEnv.delta.xml";
}
public function CompileMenusProvider(){
return [
'legacy_algo' => [ 'sEnv' => 'legacy_algo', 'bLegacyMenuCompilation' => true ],
'menu_compilation_fix' => [ 'sEnv' => 'menu_compilation_fix', 'bLegacyMenuCompilation' => false ],
];
}
/**
* @dataProvider CompileMenusProvider
*/
public function testCompileMenus($sEnv, $bLegacyMenuCompilation){
$sConfigFilePath = \utils::GetConfigFilePath($sEnv);
//copy conf from production to phpunit context
$sDirPath = dirname($sConfigFilePath);
if (! is_dir($sDirPath)){
mkdir($sDirPath);
}
$oConfig = new Config(\utils::GetConfigFilePath());
$oConfig->WriteToFile($sConfigFilePath);
$oConfig = new Config($sConfigFilePath);
if ($bLegacyMenuCompilation){
ParentMenuNodeCompiler::UseLegacyMenuCompilation();
}
$oConfig->WriteToFile();
$oRunTimeEnvironment = new RunTimeEnvironment($sEnv);
$oRunTimeEnvironment->CompileFrom(\utils::GetCurrentEnvironment());
$oConfig->WriteToFile();
$sConfigFile = APPCONF.\utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
$aMenuGroups = ApplicationMenu::GetMenuGroups();
if (! is_null(static::$aPreviousEnvMenus)){
$this->assertEquals(static::$aPreviousEnvMenus, $aMenuGroups);
} else {
$this->assertNotEquals([], $aMenuGroups);
}
static::$aPreviousEnvMenus = $aMenuGroups;
$aMenuCount = ApplicationMenu::GetMenusCount();
if (! is_null(static::$aPreviousEnvMenuCount)){
$this->assertEquals(static::$aPreviousEnvMenuCount, $aMenuCount);
} else {
$this->assertNotEquals([], $aMenuCount);
}
static::$aPreviousEnvMenuCount = $aMenuCount;
}
public function CompileMenusWithDeltaProvider(){
return [
'Menus are broken with specific delta XML using LEGACY algo' => [ 'sDeltaFile' => 'delta_broken_menus.xml', 'sEnv' => 'broken_menus', 'bLegacyMenuCompilation' => true ],
'Menus repaired using same delta XML with NEW algo' => [ 'sDeltaFile' => 'delta_broken_menus.xml', 'sEnv' => 'fixed_menus', 'bLegacyMenuCompilation' => false ],
];
}
/**
* @dataProvider CompileMenusWithDeltaProvider
*/
public function testCompileMenusWithDelta($sDeltaFile, $sEnv, $bLegacyMenuCompilation){
$sProvidedDeltaPath = __DIR__.'/ressources/datamodels/'.$sDeltaFile;
if (is_file($sProvidedDeltaPath)){
$sDeltaXmlPath = $this->GetCurrentEnvDeltaXmlPath($sEnv);
copy($sProvidedDeltaPath, $sDeltaXmlPath);
}
$sConfigFilePath = \utils::GetConfigFilePath($sEnv);
//copy conf from production to phpunit context
$sDirPath = dirname($sConfigFilePath);
if (! is_dir($sDirPath)){
mkdir($sDirPath);
}
$oConfig = new Config(\utils::GetConfigFilePath());
$oConfig->WriteToFile($sConfigFilePath);
$oConfig = new Config($sConfigFilePath);
if ($bLegacyMenuCompilation){
ParentMenuNodeCompiler::UseLegacyMenuCompilation();
}
$oConfig->WriteToFile();
$oRunTimeEnvironment = new RunTimeEnvironment($sEnv);
$oRunTimeEnvironment->CompileFrom(\utils::GetCurrentEnvironment());
$oConfig->WriteToFile();
if ($bLegacyMenuCompilation){
/**
* PHP Notice: Undefined index: ConfigManagement in /var/www/html/iTop/env-broken_menus/itop-structure/model.itop-structure.php on line 925
*/
error_reporting(E_ALL & ~E_NOTICE);
$this->expectErrorMessage("Call to a member function GetIndex() on null");
}
$sConfigFile = APPCONF.\utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
$this->assertNotEquals([], ApplicationMenu::GetMenuGroups());
$this->assertNotEquals([], ApplicationMenu::GetMenusCount());
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<menus>
<menu id="Contact" xsi:type="DashboardMenuNode" _created_in="itop-config-mgmt" _delta="must_exist">
<parent _delta="redefine">ConfigManagementOverview</parent>
</menu>
<menu id="Location" xsi:type="OQLMenuNode" _created_in="itop-config-mgmt" _delta="delete">
<parent _delta="redefine">ConfigManagementOverview</parent>
</menu>
<menu id="Document" xsi:type="OQLMenuNode" _created_in="itop-config-mgmt" _delta="must_exist">
<parent _delta="redefine">ConfigManagementOverview</parent>
</menu>
</menus>
</itop_design>

View File

@@ -2,7 +2,6 @@
namespace Combodo\iTop\Test\UnitTest\Webservices;
use CMDBSource;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use MetaModel;
@@ -12,11 +11,12 @@ use MetaModel;
* @backupGlobals disabled
*/
class ImportTest extends ItopDataTestCase {
const USE_TRANSACTION = false;
private $sUrl;
private $sUid;
private $sLogin;
private $sPassword = "Iuytrez9876543ç_è-(";
private $sPassword = "abcDEF12345##";
private $sTmpFile = "";
private $oOrg;