mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-01 23:34:13 +02:00
Compare commits
20 Commits
feature/fa
...
faf/cron_m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
063e75b01d | ||
|
|
1a9e4bd5ad | ||
|
|
36891e441b | ||
|
|
a6295f1b14 | ||
|
|
ba6b3da238 | ||
|
|
2674e9c47f | ||
|
|
e467ca83cf | ||
|
|
f7b73717b4 | ||
|
|
0d9b34a879 | ||
|
|
7791585387 | ||
|
|
a4a0b3c18c | ||
|
|
3406ca79de | ||
|
|
91ad01055e | ||
|
|
443fa60459 | ||
|
|
804cdffe42 | ||
|
|
5f4affc896 | ||
|
|
042fee2360 | ||
|
|
7f8ec25977 | ||
|
|
41f8437c23 | ||
|
|
df8b25d4b4 |
@@ -819,6 +819,7 @@ HTML
|
||||
foreach ($aNotificationClasses as $sNotifClass) {
|
||||
$aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev WHERE Ev.object_id = :id AND Ev.object_class = :class");
|
||||
$aNotifSearches[$sNotifClass]->SetInternalParams($aParams);
|
||||
$aNotifSearches[$sNotifClass]->AllowAllData();
|
||||
$oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass], []);
|
||||
$iNotifsCount += $oNotifSet->Count();
|
||||
}
|
||||
@@ -832,6 +833,7 @@ HTML
|
||||
'menu' => false,
|
||||
'panel_title' => MetaModel::GetName($sNotifClass),
|
||||
'panel_icon' => MetaModel::GetClassIcon($sNotifClass, false),
|
||||
'display_unauthorized_objects' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,6 +726,10 @@ class DisplayBlock
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->m_oFilter->IsAllDataAllowed() && ($aExtraParams['display_unauthorized_objects'] ?? false) === true) {
|
||||
$this->m_oFilter->AllowAllData();
|
||||
}
|
||||
|
||||
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
|
||||
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
|
||||
}
|
||||
@@ -1379,7 +1383,10 @@ JS
|
||||
|
||||
// Check the classes that can be read (i.e authorized) by this user...
|
||||
foreach ($aClasses as $sAlias => $sClassName) {
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO) {
|
||||
if (
|
||||
(UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) !== UR_ALLOWED_NO)
|
||||
|| ($aExtraParams['display_unauthorized_objects'] ?? false) === true
|
||||
) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -924,11 +924,6 @@ CSS;
|
||||
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths)
|
||||
{
|
||||
$aThemeParametersVariable = [];
|
||||
if (array_key_exists('variables', $aThemeParameters)) {
|
||||
if (is_array($aThemeParameters['variables'])) {
|
||||
$aThemeParametersVariable = array_merge([], $aThemeParameters['variables']);
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('variable_imports', $aThemeParameters)) {
|
||||
if (is_array($aThemeParameters['variable_imports'])) {
|
||||
@@ -936,6 +931,14 @@ CSS;
|
||||
}
|
||||
}
|
||||
|
||||
// Variables defined in theme XML have the priority over variables defined in XML imports files
|
||||
// They're defined after so they overwrite previous parameters
|
||||
if (array_key_exists('variables', $aThemeParameters)) {
|
||||
if (is_array($aThemeParameters['variables'])) {
|
||||
$aThemeParametersVariable = array_merge($aThemeParametersVariable, $aThemeParameters['variables']);
|
||||
}
|
||||
}
|
||||
|
||||
$aThemeParametersVariable['$version'] = $bSetupCompilationTimestamp;
|
||||
return $aThemeParametersVariable;
|
||||
}
|
||||
|
||||
@@ -1455,6 +1455,12 @@ class utils
|
||||
|
||||
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
|
||||
/** @var \DBObjectSet $param */
|
||||
|
||||
// Check if the user has the right to read the objects of this list, otherwise do not propose any action (eg. configure this list, export, etc.)
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_READ, $param) !== UR_ALLOWED_YES) {
|
||||
break;
|
||||
}
|
||||
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink(true);
|
||||
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
|
||||
|
||||
@@ -234,10 +234,11 @@ abstract class Action extends cmdbAbstractObject
|
||||
}
|
||||
|
||||
$oActionFilter = DBObjectSearch::FromOQL($sActionQueryOql, $aActionQueryParams);
|
||||
$oActionFilter->AllowAllData();
|
||||
$oSet = new DBObjectSet($oActionFilter, ['date' => false]);
|
||||
|
||||
$sPanelTitle = Dict::Format('Action:last_executions_tab_panel_title', $sActionQueryLimit);
|
||||
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle]);
|
||||
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle, 'display_unauthorized_objects' => true]);
|
||||
|
||||
$oPage->AddUiBlock($oExecutionsListBlock);
|
||||
}
|
||||
|
||||
@@ -585,6 +585,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'cron_task_max_execution_time' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Background tasks will use this value (integer) multiplicated by its periodicity (in seconds) as max duration per cron execution. 0 is unlimited time',
|
||||
'default' => 0,
|
||||
'value' => 0,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'cron_sleep' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Duration (seconds) before cron.php checks again if something must be done',
|
||||
@@ -593,14 +601,6 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'cron.max_processes' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of cron processes to run',
|
||||
'default' => 10,
|
||||
'value' => 10,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'async_task_retries' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Automatic retries of asynchronous tasks in case of failure (per class)',
|
||||
@@ -1740,6 +1740,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.disable_joined_classes_filter' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, scope filters aren\'t applied to joined classes or union classes not directly listed in the SELECT clause.',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.hide_administrators' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
|
||||
|
||||
@@ -425,7 +425,7 @@
|
||||
</php_parent>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui</category>
|
||||
<category>core/cmdb,grant_by_profile,silo</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>priv_event_newsroom</db_table>
|
||||
@@ -904,7 +904,7 @@
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>Event</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui</category>
|
||||
<category>core/cmdb,grant_by_profile,silo</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="message" xsi:type="AttributeText"/>
|
||||
|
||||
@@ -1932,4 +1932,37 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBObjectSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBObjectSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
|
||||
$aClassesToFilter = $this->GetJoinedClasses();
|
||||
}
|
||||
|
||||
// Apply filter (this is similar to the one in DBSearch but the factorization could make it less readable)
|
||||
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1048,21 +1048,7 @@ abstract class DBSearch
|
||||
*/
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
|
||||
{
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) {
|
||||
foreach ($this->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
}
|
||||
$oSearch = $this->ApplyDataFilters();
|
||||
|
||||
if (is_array($aGroupByExpr)) {
|
||||
foreach ($aGroupByExpr as $sAlias => $oGroupByExp) {
|
||||
@@ -1524,4 +1510,33 @@ abstract class DBSearch
|
||||
* @return array{\VariableExpression}
|
||||
*/
|
||||
abstract public function GetExpectedArguments(): array;
|
||||
|
||||
/**
|
||||
* Apply data filters to the search, if needed
|
||||
*
|
||||
* @return DBSearch
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false) {
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
|
||||
}
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,4 +673,30 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
return $aVariableCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return DBUnionSearch
|
||||
*/
|
||||
protected function ApplyDataFilters(): DBUnionSearch
|
||||
{
|
||||
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
|
||||
return parent::ApplyDataFilters();
|
||||
}
|
||||
|
||||
// Apply filters per sub-search
|
||||
$aFilteredSearches = [];
|
||||
foreach ($this->GetSearches() as $oSubSearch) {
|
||||
// Recursively call ApplyDataFilters on sub-searches
|
||||
$aFilteredSearches[] = $oSubSearch->ApplyDataFilters();
|
||||
}
|
||||
|
||||
$oSearch = new DBUnionSearch($aFilteredSearches);
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class Event extends DBObject implements iDisplay
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -120,7 +120,7 @@ class EventNotification extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -156,7 +156,7 @@ class EventNotificationEmail extends EventNotification
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -192,7 +192,7 @@ class EventIssue extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -286,7 +286,7 @@ class EventWebService extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -321,7 +321,7 @@ class EventRestService extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -356,7 +356,7 @@ class EventLoginUsage extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -394,7 +394,7 @@ class EventOnObject extends Event
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"category" => "core/cmdb,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -350,15 +350,18 @@ class ormDocument
|
||||
if (!is_object($oObj)) {
|
||||
// If access to the document is not granted, check if the access to the host object is allowed
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, true);
|
||||
$bHasHostRights = false;
|
||||
if ($oObj instanceof Attachment) {
|
||||
$sItemClass = $oObj->Get('item_class');
|
||||
$sItemId = $oObj->Get('item_id');
|
||||
$oHost = MetaModel::GetObject($sItemClass, $sItemId, false, false);
|
||||
if (!is_object($oHost)) {
|
||||
$oObj = null;
|
||||
if (is_object($oHost)) {
|
||||
$bHasHostRights = true;
|
||||
}
|
||||
}
|
||||
if (!is_object($oObj)) {
|
||||
|
||||
// We could neither read the object nor get a host object matching our rights
|
||||
if ($bHasHostRights !== true) {
|
||||
throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,17 +470,6 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
|| ($this->oOriginalSet->GetFilter()->ToOQL() == $oFellow->oOriginalSet->GetFilter()->ToOQL())) {
|
||||
$bUpdateFromDelta = true;
|
||||
}
|
||||
} else {
|
||||
//@since 3.2.2 N°2364 - API : remove old linkedset persistance
|
||||
/* Goo pattern to use:
|
||||
* $oCISet = $oTicket->Get(‘functioncis_list’);
|
||||
* $oCISet->AddItem(MetaModel::NewObject(‘lnkFunctionCIToTicket’, array(‘ci_id’=> 12345));
|
||||
* $oCISet->RemoveItem(123456);
|
||||
* $oTicket->Set(‘functionalcis_list’, $oCISet);
|
||||
*/
|
||||
if (!ContextTag::Check(ContextTag::TAG_SETUP)) {
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('old pattern - please get previous value of the linked set, modify it and set it back to the host object');
|
||||
}
|
||||
}
|
||||
|
||||
if ($bUpdateFromDelta) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -68,47 +68,47 @@ $ibo-color-information-900: #0f172a !default;
|
||||
$ibo-color-information-950: #020617 !default;
|
||||
|
||||
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-yellow-700;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-orange-800;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700;
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-yellow-700 !default;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-orange-800 !default;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200 !default;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700 !default;
|
||||
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-yellow-700;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-yellow-700 !default;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-yellow-700;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-yellow-500;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-yellow-300;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300;
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700 !default;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-yellow-700 !default;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600 !default;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-yellow-500 !default;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500 !default;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-yellow-300 !default;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300 !default;
|
||||
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-warning-700;
|
||||
$ibo-field-validation: $ibo-color-warning-800;
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-warning-700 !default;
|
||||
$ibo-field-validation: $ibo-color-warning-800 !default;
|
||||
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-blue-400;
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-blue-400 !default;
|
||||
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600;
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200 !default;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600 !default;
|
||||
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-danger-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-danger-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-danger-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-danger-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500 !default;
|
||||
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-blue-600;
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-blue-600 !default;
|
||||
@@ -32,47 +32,47 @@ $ibo-color-information-900: #0f172a !default;
|
||||
$ibo-color-information-950: #020617 !default;
|
||||
|
||||
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-red-200;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-red-800;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-red-800;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700;
|
||||
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600 !default;
|
||||
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-red-200 !default;
|
||||
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-red-800 !default;
|
||||
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-failure-state-primary-color: $ibo-color-red-800 !default;
|
||||
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200 !default;
|
||||
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700 !default;
|
||||
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-red-700;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100;
|
||||
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700 !default;
|
||||
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100 !default;
|
||||
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-red-700 !default;
|
||||
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-red-700;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-red-500;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-red-300;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300;
|
||||
$ibo-caselog-highlight-color-1: $ibo-color-blue-700 !default;
|
||||
$ibo-caselog-highlight-color-2: $ibo-color-red-700 !default;
|
||||
$ibo-caselog-highlight-color-3: $ibo-color-information-600 !default;
|
||||
$ibo-caselog-highlight-color-4: $ibo-color-red-500 !default;
|
||||
$ibo-caselog-highlight-color-5: $ibo-color-blue-500 !default;
|
||||
$ibo-caselog-highlight-color-6: $ibo-color-red-300 !default;
|
||||
$ibo-caselog-highlight-color-7: $ibo-color-blue-300 !default;
|
||||
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-pink-700;
|
||||
$ibo-field-validation: $ibo-color-pink-800;
|
||||
$ibo-input-wrapper--is-error--border-color: $ibo-color-pink-700 !default;
|
||||
$ibo-field-validation: $ibo-color-pink-800 !default;
|
||||
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-pink-600;
|
||||
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-pink-600 !default;
|
||||
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600;
|
||||
$ibo-wizard-container--background-color: $ibo-color-information-200 !default;
|
||||
$ibo-wizard-container--border-color: $ibo-color-information-600 !default;
|
||||
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-pink-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-pink-600;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-400;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-pink-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-pink-600 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-400 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100 !default;
|
||||
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500 !default;
|
||||
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-pink-500;
|
||||
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-pink-500 !default;
|
||||
@@ -42,6 +42,12 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:ContainerImage/Attribute:software_id+' => '',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list' => 'Applications conteneurisées',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list+' => 'Les applications qui utilisent cette image',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list/UI:Links:Create:Button+' => 'Créer une %4$s',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list/UI:Links:Create:Modal:Title' => 'Ajouter %2$s à une nouvelle %4$s',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list/UI:Links:Delete:Button+' => 'Supprimer cette %4$s',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list/UI:Links:Delete:Modal:Title' => 'Supprimer une %4$s',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list/UI:Links:Remove:Button+' => 'Retirer %2$s de cette %4$s',
|
||||
'Class:ContainerImage/Attribute:containerapplications_list/UI:Links:Remove:Modal:Title' => 'Retirer %1$s de cette %4$s',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -63,6 +69,12 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:ContainerApplication/Attribute:containertype_id+' => 'Typologie de plateforme de conteneurisation',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list' => 'Images',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list+' => 'Images des conteneurs constitutifs de cette application',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list/UI:Links:Create:Button+' => 'Créer une %4$s',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list/UI:Links:Create:Modal:Title' => 'Ajouter une %4$s à %2$s',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list/UI:Links:Delete:Button+' => 'Supprimer cette %4$s',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list/UI:Links:Delete:Modal:Title' => 'Supprimer une %4$s',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list/UI:Links:Remove:Button+' => 'Retirer cette %4$s',
|
||||
'Class:ContainerApplication/Attribute:containerimages_list/UI:Links:Remove:Modal:Title' => 'Retirer cette %4$s de son %1$s',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -95,6 +107,13 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:ContainerVirtualHost/Attribute:status+' => 'État de la plateforme de conteneurisation',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list' => 'Applications',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list+' => 'Applications qui sont déployées sur cette plateforme',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list/UI:Links:Create:Button+' => 'Créer une %4$s',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list/UI:Links:Create:Modal:Title' => 'Ajouter une %4$s à %2$s',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list/UI:Links:Delete:Button+' => 'Supprimer cette %4$s',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list/UI:Links:Delete:Modal:Title' => 'Supprimer une %4$s',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list/UI:Links:Remove:Button+' => 'Retirer cette %4$s',
|
||||
'Class:ContainerVirtualHost/Attribute:containerapplications_list/UI:Links:Remove:Modal:Title' => 'Retirer cette %4$s de sa %1$s',
|
||||
|
||||
'ContainerVirtualHost:baseinfo' => 'Informations générales',
|
||||
'ContainerVirtualHost:moreinfo' => 'Spécificités de la conteneurisation',
|
||||
]);
|
||||
@@ -165,10 +184,36 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:Cloud/Attribute:containerhosts_list' => 'Hôtes pour conteneurs',
|
||||
'Class:Cloud/Attribute:containerhosts_list+' => 'Liste des hôtes hébergés dans ce nuage',
|
||||
'Class:Cloud/Attribute:containerhosts_list/UI:Links:Create:Button+' => 'Créer un %4$s',
|
||||
'Class:Cloud/Attribute:containerhosts_list/UI:Links:Create:Modal:Title' => 'Ajouter un %4$s à %2$s',
|
||||
'Class:Cloud/Attribute:containerhosts_list/UI:Links:Delete:Button+' => 'Supprimer ce %4$s',
|
||||
'Class:Cloud/Attribute:containerhosts_list/UI:Links:Delete:Modal:Title' => 'Supprimer un %4$s',
|
||||
'Class:Cloud/Attribute:containerhosts_list/UI:Links:Remove:Button+' => 'Retirer ce %4$s',
|
||||
'Class:Cloud/Attribute:containerhosts_list/UI:Links:Remove:Modal:Title' => 'Retirer ce %4$s de son %1$s',
|
||||
|
||||
'Class:Server/Attribute:containerhosts_list' => 'Hôtes pour conteneurs',
|
||||
'Class:Server/Attribute:containerhosts_list+' => 'Liste des hôtes pour conteneurs hébergés sur ce serveur',
|
||||
'Class:Server/Attribute:containerhosts_list/UI:Links:Create:Button+' => 'Créer un %4$s',
|
||||
'Class:Server/Attribute:containerhosts_list/UI:Links:Create:Modal:Title' => 'Ajouter un %4$s à %2$s',
|
||||
'Class:Server/Attribute:containerhosts_list/UI:Links:Delete:Button+' => 'Supprimer ce %4$s',
|
||||
'Class:Server/Attribute:containerhosts_list/UI:Links:Delete:Modal:Title' => 'Supprimer un %4$s',
|
||||
'Class:Server/Attribute:containerhosts_list/UI:Links:Remove:Button+' => 'Retirer ce %4$s',
|
||||
'Class:Server/Attribute:containerhosts_list/UI:Links:Remove:Modal:Title' => 'Retirer ce %4$s de son %1$s',
|
||||
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list' => 'Hôtes pour conteneurs',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list+' => 'Liste des hôtes pour conteneurs hébergés sur cette machine virtuelle',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list/UI:Links:Create:Button+' => 'Créer un %4$s',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list/UI:Links:Create:Modal:Title' => 'Ajouter un %4$s à %2$s',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list/UI:Links:Delete:Button+' => 'Supprimer ce %4$s',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list/UI:Links:Delete:Modal:Title' => 'Supprimer un %4$s',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list/UI:Links:Remove:Button+' => 'Retirer ce %4$s',
|
||||
'Class:VirtualMachine/Attribute:containerhosts_list/UI:Links:Remove:Modal:Title' => 'Retirer ce %4$s de sa %1$s',
|
||||
|
||||
'Class:Software/Attribute:containerimages_list' => 'Images pour conteneurs',
|
||||
'Class:Software/Attribute:containerimages_list+' => 'Liste des images pour conteneurs qui tournent ce Logiciel',
|
||||
'Class:Software/Attribute:containerimages_list/UI:Links:Create:Modal:Title' => 'Ajouter une %4$s à %2$s',
|
||||
'Class:Software/Attribute:containerimages_list/UI:Links:Delete:Button+' => 'Supprimer cette %4$s',
|
||||
'Class:Software/Attribute:containerimages_list/UI:Links:Delete:Modal:Title' => 'Supprimer une %4$s',
|
||||
'Class:Software/Attribute:containerimages_list/UI:Links:Remove:Button+' => 'Retirer cette %4$s',
|
||||
'Class:Software/Attribute:containerimages_list/UI:Links:Remove:Modal:Title' => 'Retirer cette %4$s de son %1$s',
|
||||
]);
|
||||
|
||||
@@ -279,11 +279,24 @@ try {
|
||||
$oRuntimeEnv = new RunTimeEnvironment('production', true);
|
||||
|
||||
try {
|
||||
SetupLog::Info('Move to production starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if (file_exists(APPROOT.'data/hub/compile_authent')) {
|
||||
unlink(APPROOT.'data/hub/compile_authent');
|
||||
}
|
||||
// Note: at this point, the dictionnary is not necessarily loaded
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
SetupLog::Info('Move to production starts...');
|
||||
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
// Load the "production" config file to clone & update it
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
|
||||
@@ -1377,19 +1377,27 @@ class ObjectController extends BrickController
|
||||
if ($oField instanceof DateTimeField) {
|
||||
$oField->SetDateTimePickerWidgetParent($sDateTimePickerWidgetParent);
|
||||
}
|
||||
$sFieldRendererClass = BsLinkedSetFieldRenderer::GetFieldRendererClass($oField);
|
||||
|
||||
// View data
|
||||
$sValue = $oAttDef->GetAsHTML($oNewLink->Get($sAttCode));
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode] = [
|
||||
'object_class' => $sLinkClass,
|
||||
'object_id' => $oNewLink->GetKey(),
|
||||
'prefix' => 'lnk__',
|
||||
'attribute_code' => $sAttCode,
|
||||
'attribute_type' => get_class($oAttDef),
|
||||
'value_html' => $sValue,
|
||||
];
|
||||
|
||||
// If the field has a renderer we adjust view data
|
||||
$sFieldRendererClass = BsLinkedSetFieldRenderer::GetFieldRendererClass($oField);
|
||||
if ($sFieldRendererClass !== null) {
|
||||
$oFieldRenderer = new $sFieldRendererClass($oField);
|
||||
$oFieldOutput = $oFieldRenderer->Render();
|
||||
$sValue = $oFieldOutput->GetHtml();
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode]['value_html'] = $oFieldOutput->GetHtml();
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode]['css_inline'] = $oFieldOutput->GetCss();
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode]['js_inline'] = $oFieldOutput->GetJs();
|
||||
}
|
||||
$aObjectData['attributes']['lnk__'.$sAttCode] = [
|
||||
'att_code' => $sAttCode,
|
||||
'value' => $sValue,
|
||||
'css_inline' => $oFieldOutput->GetCss(),
|
||||
'js_inline' => $oFieldOutput->GetJs(),
|
||||
];
|
||||
}
|
||||
|
||||
$aData['items'][] = $aObjectData;
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<class id="TagSetFieldData"/>
|
||||
<class id="Tape"/>
|
||||
<class id="VLAN"/>
|
||||
<class id="DataFlow"/>
|
||||
<class id="ContainerImage"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Incident" _delta="define">
|
||||
@@ -186,6 +188,7 @@
|
||||
<group id="AdminSysReadOnly" _delta="define">
|
||||
<classes>
|
||||
<class id="ItopFenceLogin"/>
|
||||
<class id="ModuleInstallation"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="AdminSys" _delta="define">
|
||||
@@ -195,6 +198,11 @@
|
||||
<class id="RessourceHybridAuthMenu"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Event" _delta="define">
|
||||
<classes>
|
||||
<class id="Event"/>
|
||||
</classes>
|
||||
</group>
|
||||
</groups>
|
||||
<profiles>
|
||||
<profile id="117" _delta="define">
|
||||
@@ -290,6 +298,16 @@
|
||||
<action id="stimulus:ev_close">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="Event">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
<action id="action:write">allow</action>
|
||||
<action id="action:bulk write">allow</action>
|
||||
<action id="action:delete">allow</action>
|
||||
<action id="action:bulk delete">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="3" _delta="define">
|
||||
|
||||
@@ -1041,6 +1041,29 @@
|
||||
</action>
|
||||
</actions>
|
||||
</transition>
|
||||
<transition id="ev_autoresolve">
|
||||
<target>resolved</target>
|
||||
<actions>
|
||||
<action>
|
||||
<verb>SetCurrentDate</verb>
|
||||
<params>
|
||||
<param xsi:type="attcode">resolution_date</param>
|
||||
</params>
|
||||
</action>
|
||||
<action>
|
||||
<verb>SetElapsedTime</verb>
|
||||
<params>
|
||||
<param xsi:type="attcode">time_spent</param>
|
||||
<param xsi:type="attcode">start_date</param>
|
||||
<param xsi:type="string">DefaultWorkingTimeComputer</param>
|
||||
</params>
|
||||
</action>
|
||||
<action>
|
||||
<verb>ResolveChildTickets</verb>
|
||||
<params/>
|
||||
</action>
|
||||
</actions>
|
||||
</transition>
|
||||
</transitions>
|
||||
</state>
|
||||
<state id="rejected">
|
||||
|
||||
@@ -175,6 +175,7 @@ Other usage: the caller of a User request is a Person as well as the agent assig
|
||||
'Class:Person/Attribute:team_list+' => 'All the teams this person belongs to',
|
||||
'Class:Person/Attribute:tickets_list' => 'Tickets',
|
||||
'Class:Person/Attribute:tickets_list+' => 'All the tickets this person is the caller',
|
||||
'Class:Person/Attribute:tickets_list/UI:Links:Create:Modal:Title' => 'Create a %4$s for %2$s',
|
||||
'Class:Person/Attribute:user_list' => 'Users',
|
||||
'Class:Person/Attribute:user_list+' => 'All the Users associated to this person',
|
||||
'Class:Person/Attribute:manager_id_friendlyname' => 'Manager friendly name',
|
||||
|
||||
@@ -150,6 +150,7 @@ Autre usage : l\'appelant d\'une demande utilisateur est une personne, tout comm
|
||||
'Class:Person/Attribute:team_list/UI:Links:Remove:Modal:Title' => 'Retirer une %4$s',
|
||||
'Class:Person/Attribute:tickets_list' => 'Tickets',
|
||||
'Class:Person/Attribute:tickets_list+' => 'Tous les tickets dont cette personne est le bénéficiaire',
|
||||
'Class:Person/Attribute:tickets_list/UI:Links:Create:Modal:Title' => 'Créer un %4$s pour %2$s',
|
||||
'Class:Person/Attribute:user_list' => 'Utilisateurs',
|
||||
'Class:Person/Attribute:user_list+' => 'Les comptes utilisateurs associés à cette personne',
|
||||
'Class:Person/Attribute:user_list/UI:Links:Create:Button+' => 'Créer un %4$s',
|
||||
|
||||
@@ -103,6 +103,7 @@ if ($sTargetPage === false || $sModule === 'core' || $sModule === 'dictionaries'
|
||||
// force login if needed
|
||||
|
||||
$aModuleDelegatedAuthenticationEndpointsList = GetModuleDelegatedAuthenticationEndpoints($sModule);
|
||||
// If module doesn't have the delegated authentication endpoints list defined, we rely on the conf. param. to decide if we force login or not.
|
||||
if (is_null($aModuleDelegatedAuthenticationEndpointsList)) {
|
||||
$bForceLoginWhenNoDelegatedAuthenticationEndpoints = utils::GetConfig()->Get('security.force_login_when_no_delegated_authentication_endpoints_list');
|
||||
if ($bForceLoginWhenNoDelegatedAuthenticationEndpoints) {
|
||||
@@ -110,14 +111,14 @@ if (is_null($aModuleDelegatedAuthenticationEndpointsList)) {
|
||||
LoginWebPage::DoLoginEx();
|
||||
}
|
||||
}
|
||||
// If module defined a delegated authentication endpoints but not for the current page, we consider that the page is not allowed to be executed without login
|
||||
if (is_array($aModuleDelegatedAuthenticationEndpointsList) && !in_array($sPage, $aModuleDelegatedAuthenticationEndpointsList)) {
|
||||
// if module defined a delegated authentication endpoints but not for the current page, we consider that the page is not allowed to be executed without login
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
LoginWebPage::DoLoginEx();
|
||||
}
|
||||
// If user is not logged in, log a warning in the log file as the page is executed without login, which is not recommended for security reason
|
||||
if (is_null($aModuleDelegatedAuthenticationEndpointsList) && !UserRights::IsLoggedIn()) {
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
// check if user is not logged in, if not log a warning in the log file as the page is executed without login, which is not recommended for security reason
|
||||
IssueLog::Debug("The '$sPage' page is executed without logging in. This call will be blocked in the future and will likely cause unwanted behaviour in the '$sModule' module. Please define a delegated authentication endpoint for the module, as described at https://www.itophub.io/wiki/page?id=latest:customization:new_extension#security.");
|
||||
}
|
||||
|
||||
|
||||
@@ -316,14 +316,9 @@ class MFCompiler
|
||||
}
|
||||
|
||||
try {
|
||||
SetupLog::Info("Compiling $sTempTargetDir...");
|
||||
$this->DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
SetupLog::Info("Compiling error: ".$e->getMessage());
|
||||
if ($sTempTargetDir != $sFinalTargetDir)
|
||||
{
|
||||
} catch (Exception $e) {
|
||||
if ($sTempTargetDir != $sFinalTargetDir) {
|
||||
// Cleanup the temporary directory
|
||||
SetupUtils::rrmdir($sTempTargetDir);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class ModuleInstallation extends DBObject
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
"category" => "core,view_in_gui",
|
||||
"category" => "core,view_in_gui,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
'name_attcode' => ['name', 'version'],
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -1987,47 +1987,33 @@ JS
|
||||
*/
|
||||
private static function WaitCronTermination($oConfig, $sMode)
|
||||
{
|
||||
$iMaxDuration = $oConfig->Get('cron_max_execution_time');
|
||||
// Avoid PHP stopping while waiting the cron
|
||||
set_time_limit($iMaxDuration);
|
||||
try {
|
||||
// Wait for cron to stop
|
||||
if (is_null($oConfig) || ContextTag::Check(ContextTag::TAG_CRON)) {
|
||||
return;
|
||||
}
|
||||
// Limit the number of cron process to run in parallel
|
||||
$iMaxCronProcess = $oConfig->Get('cron.max_processes');
|
||||
// Use mutex to check if cron is running
|
||||
$oMutex = new iTopMutex(
|
||||
'cron'.$oConfig->Get('db_name').$oConfig->Get('db_subname'),
|
||||
$oConfig->Get('db_host'),
|
||||
$oConfig->Get('db_user'),
|
||||
$oConfig->Get('db_pwd'),
|
||||
$oConfig->Get('db_tls.enabled'),
|
||||
$oConfig->Get('db_tls.ca')
|
||||
);
|
||||
$iCount = 1;
|
||||
$iTimeLimit = time() + $iMaxDuration;
|
||||
do {
|
||||
$bIsRunning = false;
|
||||
// Use all mutexes to check if cron is running
|
||||
for ($i = 0; $i < $iMaxCronProcess; $i++) {
|
||||
$sName = "cron#$i";
|
||||
|
||||
$oMutex = new iTopMutex(
|
||||
$sName.$oConfig->Get('db_name').$oConfig->Get('db_subname'),
|
||||
$oConfig->Get('db_host'),
|
||||
$oConfig->Get('db_user'),
|
||||
$oConfig->Get('db_pwd'),
|
||||
$oConfig->Get('db_tls.enabled'),
|
||||
$oConfig->Get('db_tls.ca')
|
||||
);
|
||||
if ($oMutex->IsLocked()) {
|
||||
$bIsRunning = true;
|
||||
SetupLog::Info("Waiting for cron to stop ($iCount)");
|
||||
$iCount++;
|
||||
sleep(1);
|
||||
if (time() > $iTimeLimit) {
|
||||
SetupLog::Error("Cannot enter $sMode mode, consider stopping the cron temporarily");
|
||||
throw new Exception("Cannot enter $sMode mode, consider stopping the cron temporarily");
|
||||
}
|
||||
break;
|
||||
}
|
||||
$iStarted = time();
|
||||
$iMaxDuration = $oConfig->Get('cron_max_execution_time');
|
||||
$iTimeLimit = $iStarted + $iMaxDuration;
|
||||
while ($oMutex->IsLocked()) {
|
||||
SetupLog::Info("Waiting for cron to stop ($iCount)");
|
||||
$iCount++;
|
||||
sleep(1);
|
||||
if (time() > $iTimeLimit) {
|
||||
throw new Exception("Cannot enter $sMode mode, consider stopping the cron temporarily");
|
||||
}
|
||||
} while ($bIsRunning);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\Cron;
|
||||
|
||||
class CronProcessesManagementException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\Cron;
|
||||
|
||||
class CronProcessesNotStoppedException extends CronProcessesManagementException
|
||||
{
|
||||
}
|
||||
63
sources/Application/Cron/iCronProcessesManagement.php
Normal file
63
sources/Application/Cron/iCronProcessesManagement.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\Cron;
|
||||
|
||||
/**
|
||||
* Interface for managing cron processes, providing methods to retrieve and set
|
||||
* the number of allowed processes, as well as enabling or disabling cron functionality.
|
||||
*
|
||||
* @api
|
||||
* @since 3.x.0
|
||||
*/
|
||||
interface iCronProcessesManagement
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function GetRunningProcesses(): array;
|
||||
|
||||
/**
|
||||
* Get the current running cron processes count
|
||||
*
|
||||
* @return int Number of cron processes currently running
|
||||
*/
|
||||
public function GetRunningProcessesCount(): int;
|
||||
|
||||
/**
|
||||
* Get the maximum number of processes allowed in parallel
|
||||
*
|
||||
* @return int The maximum number of processes allowed in parallel
|
||||
*/
|
||||
public function GetMaxRunningProcesses(): int;
|
||||
|
||||
/**
|
||||
* Set the maximum number of processes allowed in parallel
|
||||
*
|
||||
* @param int $iMaxRunningProcesses The max number of parallel cron allowed
|
||||
*/
|
||||
public function SetMaxRunningProcesses(int $iMaxRunningProcesses): void;
|
||||
|
||||
/**
|
||||
* Stop the cron on all servers.
|
||||
* The method only returns when all the processes are stopped even if
|
||||
* multiple calls are done to this method on multiple servers.
|
||||
*
|
||||
* @param int $iWaitLimitInSec Max time in seconds to wait for termination
|
||||
*
|
||||
* @return void
|
||||
* @throws \Combodo\iTop\Application\Cron\CronProcessesNotStoppedException when "wait time limit" is exceeded
|
||||
*/
|
||||
public function StopAndWaitCronTermination(int $iWaitLimitInSec): void;
|
||||
|
||||
/**
|
||||
* Enables the cron processes to run.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function StartCron(): void;
|
||||
}
|
||||
@@ -340,8 +340,10 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$aClassAliases = $oSet->GetFilter()->GetSelectedClasses();
|
||||
$aAuthorizedClasses = [];
|
||||
foreach ($aClassAliases as $sAlias => $sClassName) {
|
||||
if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) &&
|
||||
((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) {
|
||||
if (
|
||||
((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) !== UR_ALLOWED_NO) || ($aExtraParams['display_unauthorized_objects'] ?? false) === true)
|
||||
&& ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))
|
||||
) {
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
@@ -520,6 +522,14 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
if ($aData['checked']) {
|
||||
if ($sAttCode == '_key_') {
|
||||
if ($bViewLink) {
|
||||
$sRenderLink = "return row['".$sClassAlias."/hyperlink'];";
|
||||
if (
|
||||
($aExtraParams['display_unauthorized_objects'] ?? false) === true
|
||||
&& UserRights::IsActionAllowed($sClassName, UR_ACTION_READ) !== UR_ALLOWED_YES
|
||||
) {
|
||||
$sRenderLink = "return row['".$sClassAlias."/friendlyname'];";
|
||||
}
|
||||
|
||||
$aColumnDefinition[] = [
|
||||
'description' => $aData['label'],
|
||||
'object_class' => $sClassName,
|
||||
@@ -527,7 +537,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
'attribute_code' => $sAttCode,
|
||||
'attribute_type' => '_key_',
|
||||
'attribute_label' => MetaModel::GetName($sClassName),
|
||||
'render' => "return row['".$sClassAlias."/hyperlink'];",
|
||||
'render' => $sRenderLink,
|
||||
];
|
||||
|
||||
}
|
||||
@@ -952,6 +962,8 @@ JS;
|
||||
/** Handler to call when trying to create a new object in modal */
|
||||
'creation_disallowed',
|
||||
/** Don't provide the standard object creation feature */
|
||||
'display_unauthorized_objects',
|
||||
/** bool Display objects for which the user has no read rights */
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,10 @@ class AjaxRenderController
|
||||
$oSet->SetShowObsoleteData($bShowObsoleteData);
|
||||
|
||||
// N°8606 : Check user permissions on the main class
|
||||
if (UserRights::IsActionAllowed($oSet->GetClass(), UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES) {
|
||||
if (
|
||||
UserRights::IsActionAllowed($oSet->GetClass(), UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES
|
||||
&& ($aExtraParams['display_unauthorized_objects'] ?? false) === false
|
||||
) {
|
||||
throw new Exception(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $oSet->GetClass()));
|
||||
}
|
||||
|
||||
@@ -109,7 +112,10 @@ class AjaxRenderController
|
||||
}
|
||||
|
||||
// N°8606 : Check user permissions on the current class
|
||||
if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES) {
|
||||
if (
|
||||
UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES
|
||||
&& ($aExtraParams['display_unauthorized_objects'] ?? false) === false
|
||||
) {
|
||||
throw new Exception(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $sClass));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use CoreException;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Dict;
|
||||
use EventNotificationNewsroom;
|
||||
use MetaModel;
|
||||
use SecurityException;
|
||||
use UserRights;
|
||||
@@ -361,6 +362,7 @@ JS
|
||||
// Search for all notifications for the current user
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT EventNotificationNewsroom');
|
||||
$oSearch->AddCondition('contact_id', UserRights::GetContactId(), '=');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch, ['read' => true, 'date' => false], []);
|
||||
|
||||
// Add main content block
|
||||
@@ -529,6 +531,7 @@ JS
|
||||
|
||||
if (utils::IsNotNullOrEmptyString($iContactId)) {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT EventNotificationNewsroom WHERE contact_id = :contact_id AND read = "no"');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch, [], ['contact_id' => $iContactId]);
|
||||
|
||||
while ($oMessage = $oSet->Fetch()) {
|
||||
@@ -542,7 +545,7 @@ $sMessage
|
||||
HTML;
|
||||
|
||||
$sIcon = $oMessage->Get('icon') !== null ?
|
||||
$oMessage->Get('icon')->GetDisplayURL('EventNotificationNewsroom', $oMessage->GetKey(), 'icon') :
|
||||
$oMessage->Get('icon')->GetDisplayURL(EventNotificationNewsroom::class, $oMessage->GetKey(), 'icon') :
|
||||
Branding::GetCompactMainLogoAbsoluteUrl();
|
||||
$aMessages[] = [
|
||||
'id' => $oMessage->GetKey(),
|
||||
@@ -579,6 +582,7 @@ HTML;
|
||||
|
||||
if (utils::IsNotNullOrEmptyString($iContactId)) {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT EventNotificationNewsroom WHERE contact_id = :contact_id AND read = "no"');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch, [], ['contact_id' => $iContactId]);
|
||||
|
||||
while ($oEvent = $oSet->Fetch()) {
|
||||
@@ -608,7 +612,7 @@ HTML;
|
||||
$sEventId = utils::ReadParam('event_id', 0);
|
||||
if ($sEventId > 0) {
|
||||
try {
|
||||
$oEvent = MetaModel::GetObject('EventNotificationNewsroom', $sEventId);
|
||||
$oEvent = MetaModel::GetObject(EventNotificationNewsroom::class, $sEventId, true, true);
|
||||
if ($oEvent !== null && $oEvent->Get('contact_id') === UserRights::GetContactId()) {
|
||||
$oEvent->Set('read', 'yes');
|
||||
$oEvent->SetCurrentDate('read_date');
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Service\Cron;
|
||||
|
||||
use LogAPI;
|
||||
use Page;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @since 3.1.0
|
||||
*/
|
||||
class CronLog extends LogAPI
|
||||
{
|
||||
public static int $iProcessNumber = 0;
|
||||
private static int $iDebugLevel = 0;
|
||||
private static ?Page $oP = null;
|
||||
|
||||
const CHANNEL_DEFAULT = 'Cron';
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* As this object is used during setup, without any conf file available, customizing the level can be done by changing this constant !
|
||||
*/
|
||||
const LEVEL_DEFAULT = self::LEVEL_INFO;
|
||||
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = []): void
|
||||
{
|
||||
$sMessage = 'cron'.str_pad(static::$iProcessNumber, 3).$sMessage;
|
||||
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
public static function Debug($sMessage, $sChannel = null, $aContext = []): void
|
||||
{
|
||||
if (self::$iDebugLevel > 0 && self::$oP) {
|
||||
self::$oP->p('cron'.str_pad(static::$iProcessNumber, 3).$sMessage);
|
||||
}
|
||||
parent::Debug($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
public static function Trace($sMessage, $sChannel = null, $aContext = []): void
|
||||
{
|
||||
if (self::$iDebugLevel > 1 && self::$oP) {
|
||||
self::$oP->p('cron'.str_pad(static::$iProcessNumber, 3).$sMessage);
|
||||
}
|
||||
parent::Trace($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
public static function SetDebug(Page $oP, int $iDebugLevel): void
|
||||
{
|
||||
self::$oP = $oP;
|
||||
self::$iDebugLevel = $iDebugLevel;
|
||||
}
|
||||
|
||||
public static function GetDebugClassName($sTaskClass): string
|
||||
{
|
||||
if (utils::StartsWith($sTaskClass, 'Combodo\\iTop\\Service\\')) {
|
||||
return substr($sTaskClass, strlen('Combodo\\iTop\\Service\\'));
|
||||
}
|
||||
if (utils::StartsWith($sTaskClass, 'Combodo\\iTop\\')) {
|
||||
return substr($sTaskClass, strlen('Combodo\\iTop\\'));
|
||||
}
|
||||
return $sTaskClass;
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,7 @@ class NotificationsRepository
|
||||
protected function PrepareSearchForNotificationsByContact(int $iContactId, array $aNotificationIds = []): DBSearch
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT EventNotificationNewsroom WHERE contact_id = :contact_id");
|
||||
$oSearch->AllowAllData();
|
||||
$aParams = [
|
||||
"contact_id" => $iContactId,
|
||||
];
|
||||
|
||||
@@ -645,4 +645,54 @@ SCSS;
|
||||
[ '/var/www/html/iTop/css/ui-lightness/images/ui-icons_222222_256x240.png', '/var/www/html/iTop/env-production//branding/themes/light-grey//../../../../css/ui-lightness/images/ui-icons_222222_256x240.png' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aThemeParameters
|
||||
* @param $bSetupCompilationTimestamp
|
||||
* @param $aExpectedClonedParameters
|
||||
* @dataProvider CloneParameterParameterOverloadProvider
|
||||
*/
|
||||
public function testCloneParameterParameterOverload($aThemeParameters, $bSetupCompilationTimestamp, $aExpectedClonedParameters)
|
||||
{
|
||||
$aClonedParameters = ThemeHandler::CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, [APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/imports/']);
|
||||
$this->assertEquals($aExpectedClonedParameters, $aClonedParameters);
|
||||
}
|
||||
|
||||
public function CloneParameterParameterOverloadProvider()
|
||||
{
|
||||
return [
|
||||
"empty parameters" => [
|
||||
'parameters' => [],
|
||||
'timestamp' => '1',
|
||||
'expected' => [
|
||||
'$version' => '1',
|
||||
],
|
||||
],
|
||||
"parameters without variables" => [
|
||||
'parameters' => [
|
||||
'variable_imports' => ['file1' => 'variable_imports.scss'],
|
||||
'utility_imports' => ['util1' => 'path2'],
|
||||
'stylesheets' => ['style1' => 'path3'],
|
||||
],
|
||||
'timestamp' => '2',
|
||||
'expected' => [
|
||||
'var1' => 'value1',
|
||||
'var2' => 'value2',
|
||||
'$version' => '2',
|
||||
],
|
||||
],
|
||||
"parameters with variables overload" => [
|
||||
'parameters' => [
|
||||
'variables' => ['var1' => 'value2'],
|
||||
'variable_imports' => ['file1' => 'variable_imports.scss'],
|
||||
],
|
||||
'timestamp' => '3',
|
||||
'expected' => [
|
||||
'var1' => 'value2',
|
||||
'var2' => 'value2',
|
||||
'$version' => '3',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
$var1: value1;
|
||||
$var2: value2;
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use lnkFunctionalCIToTicket;
|
||||
use MetaModel;
|
||||
use ormLinkSet;
|
||||
use UserRequest;
|
||||
use UserRights;
|
||||
|
||||
class DBSearchFilterJoinTest extends ItopDataTestCase
|
||||
{
|
||||
private const RESTRICTED_PROFILE = 'Configuration Manager';
|
||||
private $aData = [];
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('application/startup.inc.php');
|
||||
$this->aData = $this->CreateDBSearchFilterTestData();
|
||||
DBSearch::EnableQueryCache(false, false);
|
||||
$this->LoginRestrictedUser($this->aData['allowed_org_id'], self::RESTRICTED_PROFILE);
|
||||
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testDBSearchFilterAppliedToJoinsWhenEnabled(string $sOql, int $iExpectedCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(true);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testDBSearchFilterAppliedToJoinsWhenDisabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(false);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testAllowAllDataBypassesDBSearchFilterWhenEnabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(true);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JoinedAndNestedOqlProvider
|
||||
*/
|
||||
public function testAllowAllDataBypassesDBSearchFilterWhenDisabled(string $sOql, int $iExpectedCount, int $iExpectedDisabledCount): void
|
||||
{
|
||||
$this->EnableJoinFilterConfig(false);
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql, ['denied_org' => $this->aData['denied_org_name'], 'allowed_org' => $this->aData['allowed_org_name']]);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
CMDBSource::TestQuery($oSearch->MakeSelectQuery());
|
||||
$this->assertEquals($iExpectedDisabledCount, $oSet->Count());
|
||||
}
|
||||
|
||||
public function JoinedAndNestedOqlProvider(): array
|
||||
{
|
||||
return [
|
||||
'join-filter-on-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
],
|
||||
'nested-in-select' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF WHERE OSF.id IN (SELECT OSF1 FROM OSFamily AS OSF1 JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF1.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org)",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
|
||||
],
|
||||
'userrequest-join-person-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = VM.id JOIN UserRequest AS UR ON L.ticket_id = UR.id JOIN Person AS P ON UR.caller_id = P.id JOIN Organization AS O ON P.org_id = O.id WHERE O.name = :denied_org",
|
||||
'expected_filtered_count' => 0,
|
||||
'expected_unfiltered_count' => 1,
|
||||
],
|
||||
'union-join-filter-on-org' => [
|
||||
'oql' => "SELECT OSF FROM OSFamily AS OSF JOIN VirtualMachine AS VM ON VM.osfamily_id = OSF.id JOIN Organization AS O ON VM.org_id = O.id WHERE O.name = :denied_org UNION SELECT OSF2 FROM OSFamily AS OSF2 JOIN VirtualMachine AS VM2 ON VM2.osfamily_id = OSF2.id JOIN Organization AS O2 ON VM2.org_id = O2.id WHERE O2.name = :allowed_org",
|
||||
'expected_filtered_count' => 1,
|
||||
'expected_unfiltered_count' => 2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function EnableJoinFilterConfig(bool $bEnabled): void
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$oConfig->Set('security.disable_joined_classes_filter', !$bEnabled);
|
||||
}
|
||||
|
||||
private function CreateDBSearchFilterTestData(): array
|
||||
{
|
||||
$sSuffix = 'DBSearchFilterJoinTest';
|
||||
|
||||
$sAllowedOrgName = 'DBSearchFilterAllowedOrg-'.$sSuffix;
|
||||
$iAllowedOrgId = $this->GivenObjectInDB('Organization', [
|
||||
'name' => $sAllowedOrgName,
|
||||
]);
|
||||
|
||||
$this->debug("Org allowed id: $iAllowedOrgId");
|
||||
$sDeniedOrgName = 'DBSearchFilterDeniedOrg-'.$sSuffix;
|
||||
$iDeniedOrgId = $this->GivenObjectInDB('Organization', [
|
||||
'name' => $sDeniedOrgName,
|
||||
]);
|
||||
$this->debug("Org denied id: $iDeniedOrgId");
|
||||
|
||||
$iDeniedOsFamilyId = $this->GivenObjectInDB('OSFamily', [
|
||||
'name' => 'DBSearchFilterOsFamilyDenied-'.$sSuffix,
|
||||
]);
|
||||
|
||||
$iAllowedOsFamilyId = $this->GivenObjectInDB('OSFamily', [
|
||||
'name' => 'DBSearchFilterOsFamilyAllowed-'.$sSuffix,
|
||||
]);
|
||||
|
||||
$iDeniedVMId = $this->GivenObjectInDB('VirtualMachine', [
|
||||
'name' => 'DBSearchFilterVmDenied-'.$sSuffix,
|
||||
'org_id' => $iDeniedOrgId,
|
||||
'osfamily_id' => $iDeniedOsFamilyId,
|
||||
'virtualhost_id' => 1,
|
||||
]);
|
||||
|
||||
$iVirtualHostId = $this->GivenObjectInDB('Hypervisor', [
|
||||
'name' => 'DBSearchFilterVHost-'.$sSuffix,
|
||||
'org_id' => $iAllowedOrgId,
|
||||
]);
|
||||
|
||||
$this->GivenObjectInDB('VirtualMachine', [
|
||||
'name' => 'DBSearchFilterVmAllowed-'.$sSuffix,
|
||||
'org_id' => $iAllowedOrgId,
|
||||
'osfamily_id' => $iAllowedOsFamilyId,
|
||||
'virtualhost_id' => $iVirtualHostId,
|
||||
]);
|
||||
|
||||
$oDeniedPerson = $this->CreatePerson('Denied-'.$sSuffix, $iDeniedOrgId);
|
||||
|
||||
$oUserRequest = $this->CreateUserRequest('Denied'.$sSuffix, [
|
||||
'caller_id' => $oDeniedPerson->GetKey(),
|
||||
'org_id' => $iDeniedOrgId,
|
||||
]);
|
||||
|
||||
// Add Virtual Machine to UserRequest lnk
|
||||
$oLinkSet = new ormLinkSet(UserRequest::class, 'functionalcis_list', DBObjectSet::FromScratch(lnkFunctionalCIToTicket::class));
|
||||
|
||||
$oLink = MetaModel::NewObject(lnkFunctionalCIToTicket::class, ['functionalci_id' => $iDeniedVMId]);
|
||||
$oLinkSet->AddItem($oLink);
|
||||
|
||||
$oUserRequest->Set('functionalcis_list', $oLinkSet);
|
||||
$oUserRequest->DBUpdate();
|
||||
|
||||
return [
|
||||
'allowed_org_id' => $iAllowedOrgId,
|
||||
'allowed_org_name' => $sAllowedOrgName,
|
||||
'denied_org_name' => $sDeniedOrgName,
|
||||
];
|
||||
}
|
||||
|
||||
private function LoginRestrictedUser(int $iAllowedOrgId, string $sProfileName): void
|
||||
{
|
||||
$sLogin = $this->GivenUserRestrictedToAnOrganizationInDB($iAllowedOrgId, self::$aURP_Profiles[$sProfileName]);
|
||||
UserRights::Login($sLogin);
|
||||
}
|
||||
}
|
||||
@@ -230,14 +230,14 @@ class UserRightsTest extends ItopDataTestCase
|
||||
'User Portal UserRequest read' => [2, ['class' => 'UserRequest', 'action' => 1, 'res' => true]],
|
||||
'User Portal URP_UserProfile read' => [2, ['class' => 'URP_UserProfile', 'action' => 1, 'res' => false]],
|
||||
'User Portal UserLocal read' => [2, ['class' => 'UserLocal', 'action' => 1, 'res' => false]],
|
||||
'User Portal ModuleInstallation read' => [2, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => true]],
|
||||
'User Portal ModuleInstallation read' => [2, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => false]],
|
||||
|
||||
/* Configuration manager (1 = UR_ACTION_READ) */
|
||||
'Configuration manager FunctionalCI read' => [3, ['class' => 'FunctionalCI', 'action' => 1, 'res' => true]],
|
||||
'Configuration manager UserRequest read' => [3, ['class' => 'UserRequest', 'action' => 1, 'res' => true]],
|
||||
'Configuration manager URP_UserProfile read' => [3, ['class' => 'URP_UserProfile', 'action' => 1, 'res' => false]],
|
||||
'Configuration manager UserLocal read' => [3, ['class' => 'UserLocal', 'action' => 1, 'res' => false]],
|
||||
'Configuration manager ModuleInstallation read' => [3, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => true]],
|
||||
'Configuration manager ModuleInstallation read' => [3, ['class' => 'ModuleInstallation', 'action' => 1, 'res' => false]],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -283,14 +283,14 @@ class UserRightsTest extends ItopDataTestCase
|
||||
'User Portal UserRequest' => [2, ['class' => 'UserRequest', 'action' => 2, 'res' => true]],
|
||||
'User Portal URP_UserProfile' => [2, ['class' => 'URP_UserProfile', 'action' => 2, 'res' => false]],
|
||||
'User Portal UserLocal' => [2, ['class' => 'UserLocal', 'action' => 2, 'res' => false]],
|
||||
'User Portal ModuleInstallation' => [2, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => true]],
|
||||
'User Portal ModuleInstallation' => [2, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => false]],
|
||||
|
||||
/* Configuration manager (2 = UR_ACTION_MODIFY) */
|
||||
'Configuration manager FunctionalCI' => [3, ['class' => 'FunctionalCI', 'action' => 2, 'res' => true]],
|
||||
'Configuration manager UserRequest' => [3, ['class' => 'UserRequest', 'action' => 2, 'res' => false]],
|
||||
'Configuration manager URP_UserProfile' => [3, ['class' => 'URP_UserProfile', 'action' => 2, 'res' => false]],
|
||||
'Configuration manager UserLocal' => [3, ['class' => 'UserLocal', 'action' => 2, 'res' => false]],
|
||||
'Configuration manager ModuleInstallation' => [3, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => true]],
|
||||
'Configuration manager ModuleInstallation' => [3, ['class' => 'ModuleInstallation', 'action' => 2, 'res' => false]],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,36 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\CaptureWebPage;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ormDocument;
|
||||
use UserRights;
|
||||
|
||||
/**
|
||||
* Tests of the ormDocument class
|
||||
*/
|
||||
class ormDocumentTest extends ItopDataTestCase
|
||||
{
|
||||
private const RESTRICTED_PROFILE = 'Configuration Manager';
|
||||
private int $iUserOrg;
|
||||
private int $iOrgDifferentFromUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->iUserOrg = $this->GivenObjectInDB('Organization', [
|
||||
'name' => 'UserOrg',
|
||||
]);
|
||||
|
||||
$this->iOrgDifferentFromUser = $this->GivenObjectInDB('Organization', [
|
||||
'name' => 'OrgDifferentFromUser',
|
||||
]);
|
||||
|
||||
$this->LoginRestrictedUser($this->iUserOrg, self::RESTRICTED_PROFILE);
|
||||
$this->ResetMetaModelQueyCacheGetObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -248,4 +270,107 @@ class ormDocumentTest extends ItopDataTestCase
|
||||
$this->assertGreaterThanOrEqual($iMaxHeight, $aActualDimensions['height'], 'The new height should not be 0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DownloadDocument enforces rights for documents
|
||||
*
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testDownloadDocumentDifferentOrg(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iDeniedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iOrgDifferentFromUser, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageDenied = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageDenied, $sTargetClass, $iDeniedDocumentId, $sAttCode);
|
||||
$sDeniedHtml = (string) $oPageDenied->GetHtml();
|
||||
$this->assertStringContainsString(
|
||||
'the object does not exist or you are not allowed to view it',
|
||||
$sDeniedHtml,
|
||||
'Expected error message when rights are missing.'
|
||||
);
|
||||
$this->assertStringNotContainsString($sData, $sDeniedHtml, 'Unexpected file data present when rights are missing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DownloadDocument allows to retrieve document with the same org (or host object org)
|
||||
*
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testDownloadDocumentSameOrg(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iAllowedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iUserOrg, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageAllowed = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageAllowed, $sTargetClass, $iAllowedDocumentId, $sAttCode);
|
||||
$sAllowedHtml = (string) $oPageAllowed->GetHtml();
|
||||
$this->assertStringContainsString($sData, $sAllowedHtml, 'Expected file data present when rights are sufficient.');
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sAllowedHtml, 'Unexpected error message when rights are sufficient.');
|
||||
}
|
||||
|
||||
public function DownloadDocumentRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
'DocumentFile' => [
|
||||
'class' => 'DocumentFile',
|
||||
'data_attribute_id' => 'file',
|
||||
'data' => 'document_data',
|
||||
'file_name' => 'document.txt',
|
||||
'host_class' => null],
|
||||
'Attachment' => [
|
||||
'class' => 'Attachment',
|
||||
'data_attribute_id' => 'contents',
|
||||
'data' => 'attachment_data',
|
||||
'file_name' => 'attachment.txt',
|
||||
'host_class' => 'UserRequest'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to avoid duplicating object creation in tests
|
||||
* Created objects and host objects depending on the Document class
|
||||
* @param string $sTargetClass
|
||||
* @param string $sAttCode
|
||||
* @param int $iOrgId
|
||||
* @param string $sData
|
||||
* @param string $sFileName
|
||||
* @param string|null $sHostClass
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function CreateDownloadTargetInOrg(string $sTargetClass, string $sAttCode, int $iOrgId, string $sData, string $sFileName, ?string $sHostClass): int
|
||||
{
|
||||
|
||||
if ($sTargetClass === 'DocumentFile') {
|
||||
return $this->GivenObjectInDB($sTargetClass, [
|
||||
'name' => 'UnitTestDocFile_'.uniqid(),
|
||||
'org_id' => $iOrgId,
|
||||
$sAttCode => new ormDocument($sData, 'text/plain', $sFileName),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($sTargetClass === 'Attachment') {
|
||||
$iHostId = $this->GivenObjectInDB($sHostClass, [
|
||||
'title' => 'UnitTestUserRequest_'.uniqid(),
|
||||
'org_id' => $iOrgId,
|
||||
'description' => 'A user request for testing attachment download rights',
|
||||
]);
|
||||
|
||||
return $this->GivenObjectInDB('Attachment', [
|
||||
'item_class' => $sHostClass,
|
||||
'item_id' => $iHostId,
|
||||
'item_org_id' => $iOrgId,
|
||||
$sAttCode => new ormDocument($sData, 'text/plain', $sFileName),
|
||||
]);
|
||||
}
|
||||
|
||||
throw new \Exception("Unsupported target class: $sTargetClass");
|
||||
}
|
||||
|
||||
private function LoginRestrictedUser(int $iAllowedOrgId, string $sProfileName): void
|
||||
{
|
||||
if (UserRights::IsLoggedIn()) {
|
||||
UserRights::Logoff();
|
||||
}
|
||||
$sLogin = $this->GivenUserRestrictedToAnOrganizationInDB($iAllowedOrgId, self::$aURP_Profiles[$sProfileName]);
|
||||
UserRights::Login($sLogin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,10 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\WebPage\CLIPage;
|
||||
use Combodo\iTop\Application\WebPage\Page;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Service\Cron\CronLog;
|
||||
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
|
||||
|
||||
if (!defined('__DIR__')) {
|
||||
define('__DIR__', dirname(__FILE__));
|
||||
}
|
||||
|
||||
require_once(__DIR__.'/../approot.inc.php');
|
||||
|
||||
const EXIT_CODE_ERROR = -1;
|
||||
@@ -67,7 +63,7 @@ function UsageAndExit($oP)
|
||||
|
||||
if ($bModeCLI) {
|
||||
$oP->p("USAGE:\n");
|
||||
$oP->p("php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=0] [--status_only=1]\n");
|
||||
$oP->p("php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1] [--debug=1] [--status_only=1]\n");
|
||||
} else {
|
||||
$oP->p("Optional parameters: verbose, param_file, status_only\n");
|
||||
}
|
||||
@@ -95,6 +91,7 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit)
|
||||
$oProcess = new $TaskClass();
|
||||
$oRefClass = new ReflectionClass(get_class($oProcess));
|
||||
$oDateStarted = new DateTime();
|
||||
$oDatePlanned = new DateTime($oTask->Get('next_run_date'));
|
||||
$fStart = microtime(true);
|
||||
$oCtx = new ContextTag('CRON:Task:'.$TaskClass);
|
||||
|
||||
@@ -102,34 +99,25 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit)
|
||||
$oExceptionToThrow = null;
|
||||
try {
|
||||
// Record (when starting) that this task was started, just in case it crashes during the execution
|
||||
if ($oTask->Get('total_exec_count') == 0) {
|
||||
// First execution
|
||||
$oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s'));
|
||||
}
|
||||
$oTask->Set('latest_run_date', $oDateStarted->format('Y-m-d H:i:s'));
|
||||
// Record the current user running the cron
|
||||
$oTask->Set('system_user', utils::GetCurrentUserName());
|
||||
$oTask->Set('running', 1);
|
||||
// Compute the next run date
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess')) {
|
||||
// Schedules process do repeat at specific moments
|
||||
$oPlannedStart = $oProcess->GetNextOccurrence();
|
||||
} else {
|
||||
// Background processes do repeat periodically
|
||||
$oDatePlanned = new DateTime($oTask->Get('next_run_date'));
|
||||
$oPlannedStart = clone $oDatePlanned;
|
||||
// Let's schedule from the previous planned date of execution to avoid shift
|
||||
$oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds');
|
||||
$oNow = new DateTime();
|
||||
while ($oPlannedStart->format('U') <= $oNow->format('U')) {
|
||||
// Next planned start is already in the past, increase it again by a period
|
||||
$oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds');
|
||||
$oTask->DBUpdate();
|
||||
// Time in seconds allowed to the task
|
||||
$iCurrTimeLimit = $iTimeLimit;
|
||||
// Compute allowed time
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess') === false) {
|
||||
// Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity())
|
||||
$iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time');
|
||||
$iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime;
|
||||
// If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0
|
||||
if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) {
|
||||
$iCurrTimeLimit = $iTaskLimit;
|
||||
}
|
||||
}
|
||||
$oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
|
||||
$oTask->DBUpdate();
|
||||
|
||||
$sMessage = $oProcess->Process($iTimeLimit);
|
||||
$sMessage = $oProcess->Process($iCurrTimeLimit);
|
||||
$oTask->Set('running', 0);
|
||||
} catch (MySQLHasGoneAwayException $e) {
|
||||
throw $e;
|
||||
} catch (ProcessFatalException $e) {
|
||||
@@ -141,12 +129,34 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit)
|
||||
$sMessage = 'Processing failed with message: '.$e->getMessage();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$oTask->Set('running', 0);
|
||||
$fDuration = microtime(true) - $fStart;
|
||||
$oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics
|
||||
$oTask->DBUpdate();
|
||||
$fDuration = microtime(true) - $fStart;
|
||||
if ($oTask->Get('total_exec_count') == 0) {
|
||||
// First execution
|
||||
$oTask->Set('first_run_date', $oDateStarted->format('Y-m-d H:i:s'));
|
||||
}
|
||||
$oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics
|
||||
|
||||
// Update the timestamp since we want to be able to re-order the tasks based on the time they finished
|
||||
$oDateEnded = new DateTime();
|
||||
$oTask->Set('latest_run_date', $oDateEnded->format('Y-m-d H:i:s'));
|
||||
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess')) {
|
||||
// Schedules process do repeat at specific moments
|
||||
$oPlannedStart = $oProcess->GetNextOccurrence();
|
||||
} else {
|
||||
// Background processes do repeat periodically
|
||||
$oPlannedStart = clone $oDatePlanned;
|
||||
// Let's schedule from the previous planned date of execution to avoid shift
|
||||
$oPlannedStart->modify($oProcess->GetPeriodicity().' seconds');
|
||||
$oEnd = new DateTime();
|
||||
while ($oPlannedStart->format('U') < $oEnd->format('U')) {
|
||||
// Next planned start is already in the past, increase it again by a period
|
||||
$oPlannedStart = $oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds');
|
||||
}
|
||||
}
|
||||
|
||||
$oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
|
||||
$oTask->DBUpdate();
|
||||
|
||||
if ($oExceptionToThrow) {
|
||||
throw $oExceptionToThrow;
|
||||
@@ -158,6 +168,8 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CLIPage|WebPage $oP
|
||||
* @param boolean $bVerbose
|
||||
*
|
||||
* @param bool $bDebug
|
||||
*
|
||||
@@ -172,31 +184,22 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit)
|
||||
* @throws \OQLException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
function CronExec($bDebug )
|
||||
function CronExec($oP, $bVerbose, $bDebug = false)
|
||||
{
|
||||
$iStarted = time();
|
||||
$iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time');
|
||||
$iTimeLimit = $iStarted + $iMaxDuration;
|
||||
$iCronSleep = MetaModel::GetConfig()->Get('cron_sleep');
|
||||
$iMaxCronProcess = max(MetaModel::GetConfig()->Get('cron.max_processes'), 1);
|
||||
|
||||
// Allow a time slot for every task
|
||||
// knowing that there are $iMaxCronProcess running in parallel for the amount of tasks
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
$oSearch->AddCondition('status', 'active');
|
||||
$oTasks = new DBObjectSet($oSearch);
|
||||
$iCount = $oTasks->Count();
|
||||
$iTotalAvailableTime = $iMaxDuration * $iMaxCronProcess;
|
||||
$iTimeSlot = (int)($iTotalAvailableTime / max($iCount, 1));
|
||||
if ($bVerbose) {
|
||||
$oP->p("Planned duration = $iMaxDuration seconds");
|
||||
$oP->p("Loop pause = $iCronSleep seconds");
|
||||
}
|
||||
|
||||
CronLog::Trace("Planned duration = $iMaxDuration seconds");
|
||||
CronLog::Trace("Planned duration per task = $iTimeSlot seconds");
|
||||
CronLog::Trace("Loop pause = $iCronSleep seconds");
|
||||
|
||||
ReSyncProcesses($bDebug);
|
||||
ReSyncProcesses($oP, $bVerbose, $bDebug);
|
||||
|
||||
while (time() < $iTimeLimit) {
|
||||
CheckMaintenanceMode();
|
||||
CheckMaintenanceMode($oP);
|
||||
|
||||
$oNow = new DateTime();
|
||||
$sNow = $oNow->format('Y-m-d H:i:s');
|
||||
@@ -204,108 +207,103 @@ function CronExec($bDebug )
|
||||
$oSearch->AddCondition('next_run_date', $sNow, '<=');
|
||||
$oSearch->AddCondition('status', 'active');
|
||||
$oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]);
|
||||
$bWorkDone = false;
|
||||
|
||||
$aTasks = [];
|
||||
if ($oTasks->CountExceeds(0)) {
|
||||
$aDebugMessages = [];
|
||||
while ($oTask = $oTasks->Fetch()) {
|
||||
$sTaskName = $oTask->Get('class_name');
|
||||
$oTaskMutex = new iTopMutex("cron_$sTaskName");
|
||||
if ($oTaskMutex->IsLocked()) {
|
||||
// Already running, ignore
|
||||
continue;
|
||||
}
|
||||
$aTasks[] = $oTask;
|
||||
$sStatus = $oTask->Get('status');
|
||||
$sLastRunDate = $oTask->Get('latest_run_date');
|
||||
$sNextRunDate = $oTask->Get('next_run_date');
|
||||
$aDebugMessages[] = sprintf('Task Class: %1$-25.25s Status: %2$-7s Last Run: %3$-19s Next Run: %4$-19s', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate);
|
||||
$bWorkDone = true;
|
||||
$aTasks = [];
|
||||
if ($bVerbose) {
|
||||
$sCount = $oTasks->Count();
|
||||
$oP->p("$sCount Tasks planned to run now ($sNow):");
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+');
|
||||
$oP->p('| Task Class | Status | Last Run | Next Run |');
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+');
|
||||
}
|
||||
$sCount = count($aDebugMessages);
|
||||
CronLog::Trace("$sCount Tasks planned to run now ($sNow):");
|
||||
foreach ($aDebugMessages as $sDebugMessage) {
|
||||
CronLog::Trace($sDebugMessage);
|
||||
while ($oTask = $oTasks->Fetch()) {
|
||||
$aTasks[$oTask->Get('class_name')] = $oTask;
|
||||
if ($bVerbose) {
|
||||
$sTaskName = $oTask->Get('class_name');
|
||||
$sStatus = $oTask->Get('status');
|
||||
$sLastRunDate = $oTask->Get('latest_run_date');
|
||||
$sNextRunDate = $oTask->Get('next_run_date');
|
||||
$oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate));
|
||||
}
|
||||
}
|
||||
if ($bVerbose) {
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+');
|
||||
}
|
||||
$aRunTasks = [];
|
||||
while (count($aTasks) > 0) {
|
||||
$oTask = array_shift($aTasks);
|
||||
foreach ($aTasks as $oTask) {
|
||||
$sTaskClass = $oTask->Get('class_name');
|
||||
|
||||
// Check if the current task is running
|
||||
$oTaskMutex = new iTopMutex("cron_$sTaskClass");
|
||||
if (!$oTaskMutex->TryLock()) {
|
||||
// Task is already running, try next one
|
||||
continue;
|
||||
}
|
||||
|
||||
$aRunTasks[] = $sTaskClass;
|
||||
|
||||
// N°3219 for each process will use a specific CMDBChange object with a specific track info
|
||||
// Any BackgroundProcess can override this as needed
|
||||
// Any BackgroundProcess can overrides this as needed
|
||||
CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)");
|
||||
|
||||
// Run the task and record its next run time
|
||||
$sDebugTaskClass = CronLog::GetDebugClassName($sTaskClass);
|
||||
$oNow = new DateTime();
|
||||
CronLog::Debug(sprintf("> Starting >>> %-'>49s", $sDebugTaskClass.' '));
|
||||
if ($bVerbose) {
|
||||
$oNow = new DateTime();
|
||||
$oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=49s", ' '.$sTaskClass.' '));
|
||||
}
|
||||
try {
|
||||
// The limit of time for this task corresponds to the time slot allowed for every task
|
||||
// but limited to the cron job time limit
|
||||
$sMessage = RunTask($oTask, min($iTimeLimit, time() + $iTimeSlot));
|
||||
}
|
||||
catch (MySQLHasGoneAwayException $e) {
|
||||
CronLog::Error("ERROR : 'MySQL has gone away' thrown when processing $sDebugTaskClass (error_code=".$e->getCode().")", CronLog::CHANNEL_DEFAULT, ['stack' => $e->getTraceAsString()]);
|
||||
$sMessage = RunTask($aTasks[$sTaskClass], $iTimeLimit);
|
||||
} catch (MySQLHasGoneAwayException $e) {
|
||||
$oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")");
|
||||
exit(EXIT_CODE_FATAL);
|
||||
} catch (ProcessFatalException $e) {
|
||||
$oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")");
|
||||
IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')');
|
||||
}
|
||||
catch (ProcessFatalException $e) {
|
||||
CronLog::Error("ERROR : an exception was thrown when processing '$sDebugTaskClass' (".$e->getInfoLog().")", CronLog::CHANNEL_DEFAULT, ['stack' => $e->getTraceAsString()]);
|
||||
if ($bVerbose) {
|
||||
if (!empty($sMessage)) {
|
||||
$oP->p("$sTaskClass: $sMessage");
|
||||
}
|
||||
$oEnd = new DateTime();
|
||||
$sNextRunDate = $oTask->Get('next_run_date');
|
||||
$oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=42s", ' '.$sTaskClass.' ')." Next: $sNextRunDate");
|
||||
}
|
||||
finally {
|
||||
$oTaskMutex->Unlock();
|
||||
}
|
||||
if (!empty($sMessage)) {
|
||||
CronLog::Debug("$sDebugTaskClass: $sMessage");
|
||||
}
|
||||
$oEnd = new DateTime();
|
||||
$sNextRunDate = $oTask->Get('next_run_date');
|
||||
CronLog::Debug(sprintf("< Ending <<<<< %-'<49s", $sDebugTaskClass.' ')." Next: $sNextRunDate");
|
||||
if (time() > $iTimeLimit) {
|
||||
break 2;
|
||||
}
|
||||
CheckMaintenanceMode();
|
||||
if ($iMaxCronProcess > 1) {
|
||||
// Reindex tasks every time
|
||||
break;
|
||||
}
|
||||
CheckMaintenanceMode($oP);
|
||||
}
|
||||
|
||||
// Tasks to run later
|
||||
if (count($aTasks) == 0) {
|
||||
if ($bVerbose) {
|
||||
$oP->p('--');
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
$oSearch->AddCondition('next_run_date', $sNow, '>');
|
||||
$oSearch->AddCondition('status', 'active');
|
||||
$oTasks = new DBObjectSet($oSearch, ['next_run_date' => true]);
|
||||
while ($oTask = $oTasks->Fetch()) {
|
||||
if (!in_array($oTask->Get('class_name'), $aRunTasks)) {
|
||||
$sDebugTaskClass = CronLog::GetDebugClassName($oTask->Get('class_name'));
|
||||
CronLog::Trace(sprintf("-- Skipping task: %-'-40s", $sDebugTaskClass.' ')." until: ".$oTask->Get('next_run_date'));
|
||||
$oP->p(sprintf("-- Skipping task: %-'-40s", $oTask->Get('class_name').' ')." until: ".$oTask->Get('next_run_date'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($aTasks) == 0) {
|
||||
CronLog::Trace("sleeping...");
|
||||
sleep($iCronSleep);
|
||||
|
||||
if ($bVerbose && $bWorkDone) {
|
||||
$oP->p("Sleeping...\n");
|
||||
}
|
||||
sleep($iCronSleep);
|
||||
}
|
||||
if ($bVerbose) {
|
||||
$oP->p('');
|
||||
DisplayStatus($oP, ['next_run_date' => true]);
|
||||
$oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)");
|
||||
}
|
||||
CronLog::Trace("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)");
|
||||
}
|
||||
|
||||
function CheckMaintenanceMode()
|
||||
/**
|
||||
* @param WebPage $oP
|
||||
*/
|
||||
function CheckMaintenanceMode(Page $oP)
|
||||
{
|
||||
// Verify files instead of reloading the full config each time
|
||||
// Verify files instead of reloading the full config each time
|
||||
if (file_exists(MAINTENANCE_MODE_FILE) || file_exists(READONLY_MODE_FILE)) {
|
||||
CronLog::Info("Maintenance detected, exiting");
|
||||
$oP->p("Maintenance detected, exiting");
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -320,7 +318,7 @@ function CheckMaintenanceMode()
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
function DisplayStatus($oP = null, $aTaskOrderBy = [])
|
||||
function DisplayStatus($oP, $aTaskOrderBy = [])
|
||||
{
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
$oTasks = new DBObjectSet($oSearch, $aTaskOrderBy);
|
||||
@@ -348,6 +346,8 @@ function DisplayStatus($oP = null, $aTaskOrderBy = [])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oP
|
||||
* @param $bVerbose
|
||||
* @param $bDebug
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
@@ -359,7 +359,7 @@ function DisplayStatus($oP = null, $aTaskOrderBy = [])
|
||||
* @throws \OQLException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
function ReSyncProcesses($bDebug)
|
||||
function ReSyncProcesses($oP, $bVerbose, $bDebug)
|
||||
{
|
||||
// Enumerate classes implementing BackgroundProcess
|
||||
//
|
||||
@@ -394,9 +394,10 @@ function ReSyncProcesses($bDebug)
|
||||
// Background processes do start asap, i.e. "now"
|
||||
$oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s'));
|
||||
}
|
||||
$sDebugTaskClass = CronLog::GetDebugClassName($sTaskClass);
|
||||
CronLog::Trace('Creating record for: '.$sDebugTaskClass);
|
||||
CronLog::Trace('First execution planned at: '.$oTask->Get('next_run_date'));
|
||||
if ($bVerbose) {
|
||||
$oP->p('Creating record for: '.$sTaskClass);
|
||||
$oP->p('First execution planned at: '.$oTask->Get('next_run_date'));
|
||||
}
|
||||
$oTask->DBInsert();
|
||||
} else {
|
||||
/** @var \BackgroundTask $oTask */
|
||||
@@ -429,12 +430,14 @@ function ReSyncProcesses($bDebug)
|
||||
}
|
||||
}
|
||||
|
||||
$aDisplayProcesses = [];
|
||||
foreach ($aProcesses as $oExecInstance) {
|
||||
$aDisplayProcesses[] = get_class($oExecInstance);
|
||||
if ($bVerbose) {
|
||||
$aDisplayProcesses = [];
|
||||
foreach ($aProcesses as $oExecInstance) {
|
||||
$aDisplayProcesses[] = get_class($oExecInstance);
|
||||
}
|
||||
$sDisplayProcesses = implode(', ', $aDisplayProcesses);
|
||||
$oP->p("Background processes: ".$sDisplayProcesses);
|
||||
}
|
||||
$sDisplayProcesses = implode(', ', $aDisplayProcesses);
|
||||
CronLog::Trace("Background processes: ".$sDisplayProcesses);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -444,23 +447,18 @@ function ReSyncProcesses($bDebug)
|
||||
|
||||
set_time_limit(0); // Some background actions may really take long to finish (like backup)
|
||||
try {
|
||||
$bIsModeCLI = utils::IsModeCLI();
|
||||
if ($bIsModeCLI) {
|
||||
$oP = new CLIPage("iTop - cron");
|
||||
$bIsModeCLI = utils::IsModeCLI();
|
||||
if ($bIsModeCLI) {
|
||||
$oP = new CLIPage("iTop - cron");
|
||||
|
||||
SetupUtils::CheckPhpAndExtensionsForCli($oP, EXIT_CODE_FATAL);
|
||||
utils::UseParamFile();
|
||||
} else {
|
||||
$oP = new WebPage("iTop - cron");
|
||||
}
|
||||
$oP = new WebPage("iTop - cron");
|
||||
}
|
||||
|
||||
try {
|
||||
utils::UseParamFile();
|
||||
|
||||
// Allow verbosity on output from 0 => none, 1 => debug, 2 => trace
|
||||
// (writing debug messages to the cron.log file is configured with log_level_min config parameter)
|
||||
$iVerbose = utils::ReadParam('verbose', 0, true /* Allow CLI */);
|
||||
CronLog::SetDebug($oP, $iVerbose);
|
||||
$bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
|
||||
$bDebug = utils::ReadParam('debug', false, true /* Allow CLI */);
|
||||
|
||||
if ($bIsModeCLI) {
|
||||
// Next steps:
|
||||
@@ -493,41 +491,31 @@ try {
|
||||
}
|
||||
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
$oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')');
|
||||
} catch (Exception $e) {
|
||||
$oP->p("Error: ".$e->GetMessage());
|
||||
$oP->output();
|
||||
exit(EXIT_CODE_FATAL);
|
||||
}
|
||||
|
||||
CronLog::Enable(APPROOT.'/log/error.log');
|
||||
try {
|
||||
$oMutex = new iTopMutex('cron');
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) {
|
||||
CronLog::Debug("A maintenance is ongoing");
|
||||
$oP->p("A maintenance is ongoing");
|
||||
} else {
|
||||
// Limit the number of cron process to run in parallel
|
||||
$iMaxCronProcess = max(MetaModel::GetConfig()->Get('cron.max_processes'), 1);
|
||||
$bCanRun = false;
|
||||
$iProcessNumber = 0;
|
||||
for ($i = 0; $i < $iMaxCronProcess; $i++) {
|
||||
$oMutex = new iTopMutex("cron#$i");
|
||||
if ($oMutex->TryLock()) {
|
||||
$iProcessNumber = $i + 1;
|
||||
$bCanRun = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($bCanRun) {
|
||||
CronLog::$iProcessNumber = $iProcessNumber;
|
||||
CronLog::Debug('Starting: '.time().' ('.date('Y-m-d H:i:s').')');
|
||||
CronExec($iVerbose > 0);
|
||||
if ($oMutex->TryLock()) {
|
||||
CronExec($oP, $bVerbose, $bDebug);
|
||||
} else {
|
||||
CronLog::$iProcessNumber = $iMaxCronProcess + 1;
|
||||
CronLog::Trace("The limit of $iMaxCronProcess cron process running in parallel is already reached");
|
||||
// Exit silently
|
||||
$oP->p("Already running...");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
CronLog::Error("ERROR: '".$e->getMessage()."'", CronLog::CHANNEL_DEFAULT, ['stack' => $e->getTraceAsString()]);
|
||||
} catch (Exception $e) {
|
||||
$oP->p("ERROR: '".$e->getMessage()."'");
|
||||
if ($bDebug) {
|
||||
// Might contain verb parameters such a password...
|
||||
$oP->p($e->getTraceAsString());
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
$oMutex->Unlock();
|
||||
@@ -540,5 +528,5 @@ catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
CronLog::Debug("Exiting: ".time().' ('.date('Y-m-d H:i:s').')');
|
||||
$oP->p("Exiting: ".time().' ('.date('Y-m-d H:i:s').')');
|
||||
$oP->Output();
|
||||
|
||||
Reference in New Issue
Block a user