mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 16:04:10 +01:00
Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bed0b63c6c | ||
|
|
5112fa4927 | ||
|
|
7246f957fd | ||
|
|
82552c7c98 | ||
|
|
9a010bd02a | ||
|
|
2b40c57c1d | ||
|
|
6bf229e803 | ||
|
|
ae5eb9ebff | ||
|
|
b60dbf1389 | ||
|
|
39f08d0ce0 | ||
|
|
de68827653 | ||
|
|
dcf94f1161 | ||
|
|
5712981b4e | ||
|
|
598965a51f | ||
|
|
893725948a | ||
|
|
f2b4c17dca | ||
|
|
a232681995 | ||
|
|
e7e382e886 | ||
|
|
b6702365c6 | ||
|
|
2ce0bf4acc | ||
|
|
882ceff6fc | ||
|
|
0b4597e65e | ||
|
|
60fa0e08f0 | ||
|
|
64e0569613 | ||
|
|
d0c1d94b14 | ||
|
|
c15853d982 | ||
|
|
1d0bf94ea4 | ||
|
|
e12f2501ee | ||
|
|
cd25a4dc34 | ||
|
|
c0fd0e736f | ||
|
|
2adcb108a5 | ||
|
|
40fbdcc88c | ||
|
|
76839b1b2c | ||
|
|
05289dc241 | ||
|
|
de81fbcb09 | ||
|
|
602b64d19a | ||
|
|
2a60a57c46 | ||
|
|
b384313453 | ||
|
|
337a003d28 | ||
|
|
7df95addcc | ||
|
|
2055e9399f | ||
|
|
dc7c622dba | ||
|
|
f42e641faf | ||
|
|
635ca103af | ||
|
|
4673a3f22d | ||
|
|
08cda15762 | ||
|
|
3c384ff498 | ||
|
|
47587fb97e | ||
|
|
f7f4fbce51 | ||
|
|
d898cffd4e | ||
|
|
9c89a58a57 | ||
|
|
e7eecf81ee | ||
|
|
b909dfc321 | ||
|
|
c9ee203970 | ||
|
|
cbb771154a | ||
|
|
cc74591036 | ||
|
|
b6eeaae24a | ||
|
|
f1966619b9 | ||
|
|
01fa323b38 | ||
|
|
6dca5afc83 | ||
|
|
aacdb525cf | ||
|
|
864033f27c | ||
|
|
15f900e630 | ||
|
|
c0ba515797 | ||
|
|
dd0eec3cc8 | ||
|
|
437ace992e | ||
|
|
60ae969c89 | ||
|
|
bc0645d5cc | ||
|
|
760454608d | ||
|
|
75fcd2a021 | ||
|
|
ff1f3c185b | ||
|
|
a89252c6b3 | ||
|
|
d651196eae | ||
|
|
73bedb1522 | ||
|
|
b2f42ae3f4 | ||
|
|
44d3fb2738 | ||
|
|
9a08895b2c | ||
|
|
3277be00c1 | ||
|
|
52e1a1d40a | ||
|
|
89b4de01a9 | ||
|
|
5d16ab9654 | ||
|
|
61a006dfbe | ||
|
|
e0f3cdac51 | ||
|
|
4a6e08e3e9 | ||
|
|
d13270acc7 | ||
|
|
e6aafc165b | ||
|
|
49f72aee28 | ||
|
|
45f4d8f625 | ||
|
|
3992425a27 | ||
|
|
64ef7fbc08 | ||
|
|
5807ae79d2 | ||
|
|
41f77f63fd | ||
|
|
67148bc80d | ||
|
|
28fa99d976 | ||
|
|
e1c51d278e | ||
|
|
85b38a07ee | ||
|
|
c3c314097e | ||
|
|
7d774c7c88 | ||
|
|
bbcd1ef22c | ||
|
|
fba368fb46 | ||
|
|
2a9a373c61 | ||
|
|
42a882ae62 | ||
|
|
634d96e23f | ||
|
|
cc461630ea | ||
|
|
b3ca6f776e | ||
|
|
dee911c12b | ||
|
|
d7ee97f5a4 | ||
|
|
67938e433b | ||
|
|
01ef529db2 | ||
|
|
bfcc6ea239 | ||
|
|
a574b1b4e8 | ||
|
|
c9baf59018 | ||
|
|
49a1052333 | ||
|
|
40006c3ba2 | ||
|
|
b8e4f3d762 | ||
|
|
a222f296ef | ||
|
|
899045dece | ||
|
|
096236cb3a | ||
|
|
746c97818e | ||
|
|
600c447529 | ||
|
|
4f1be53b68 | ||
|
|
5e6061b341 | ||
|
|
281edea101 | ||
|
|
8280159eee | ||
|
|
c380c19d2a | ||
|
|
28ead17d00 | ||
|
|
471bf9e820 | ||
|
|
cee84074e1 | ||
|
|
70dbe00f4d | ||
|
|
3e81986e0f | ||
|
|
2cadb34eaa | ||
|
|
dc9a6382f9 | ||
|
|
40b3e8290b | ||
|
|
dfdec57d3f | ||
|
|
ce887e25bf | ||
|
|
48d2e9213e | ||
|
|
fb551cc3d2 | ||
|
|
b76c890408 | ||
|
|
8711356118 | ||
|
|
09aef4ef39 | ||
|
|
950c868230 | ||
|
|
93bbfeae1f | ||
|
|
7577e560bb | ||
|
|
0f495e5730 | ||
|
|
6ea6dcef16 | ||
|
|
5e2e6b393c | ||
|
|
593f1fadbe | ||
|
|
43dd075c44 | ||
|
|
1632c51abd | ||
|
|
45c0ad5597 | ||
|
|
c55a46e52b | ||
|
|
5863128c0c | ||
|
|
10a9326e19 | ||
|
|
eae396f250 | ||
|
|
33c5839273 | ||
|
|
9f92bc4b8a | ||
|
|
2af2fd0aea |
@@ -79,7 +79,7 @@ class Html2Text {
|
||||
// replace with spaces
|
||||
|
||||
$html = str_replace(" ", " ", $html);
|
||||
$html = mb_str_replace("\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
|
||||
$html = mb_str_replace("\xc2\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
|
||||
|
||||
$html = static::fixNewlines($html);
|
||||
|
||||
|
||||
@@ -202,32 +202,16 @@ EOF
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
|
||||
|
||||
// Additional UI widgets to be activated inside the ajax fragment ??
|
||||
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
|
||||
// Additional UI widgets to be activated inside the ajax fragment
|
||||
// Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script)
|
||||
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
|
||||
{
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
PrepareWidgets();
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$s_captured_output = $this->ob_get_clean_safe();
|
||||
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* Abstract class that implements some common and useful methods for displaying
|
||||
* the objects
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -655,16 +655,19 @@ EOF
|
||||
if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
|
||||
{
|
||||
// Check if the attribute is not read-only because of a synchro...
|
||||
$aReasons = array();
|
||||
$sSynchroIcon = '';
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$aReasons = array();
|
||||
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$sTip = addslashes($sTip);
|
||||
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
@@ -688,6 +691,7 @@ EOF
|
||||
else
|
||||
{
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>", 'comments' => $sComments, 'infos' => $sInfos);
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1744,7 +1748,7 @@ EOF
|
||||
$aEventsList[] ='change';
|
||||
|
||||
$sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"';
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"15\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationSpan}{$sReloadSpan}";
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"19\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationSpan}{$sReloadSpan}";
|
||||
break;
|
||||
|
||||
case 'Duration':
|
||||
@@ -1829,7 +1833,7 @@ EOF
|
||||
$sStyle = 'style="'.implode('; ', $aStyles).'"';
|
||||
}
|
||||
$sHeader = '<div class="caselog_input_header"></div>'; // will be hidden in CSS (via :empty) if it remains empty
|
||||
$sEditValue = $oAttDef->GetEditValue($value);
|
||||
$sEditValue = is_object($value) ? $value->GetModifiedEntry('html') : '';
|
||||
$sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : '';
|
||||
$iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0;
|
||||
$sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains
|
||||
@@ -3327,8 +3331,6 @@ EOF
|
||||
{
|
||||
$res = parent::DBInsertNoReload();
|
||||
|
||||
InlineImage::FinalizeInlineImages($this);
|
||||
|
||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
@@ -3338,6 +3340,14 @@ EOF
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches InlineImages to the current object
|
||||
*/
|
||||
protected function OnObjectKeyReady()
|
||||
{
|
||||
InlineImage::FinalizeInlineImages($this);
|
||||
}
|
||||
|
||||
protected function DBCloneTracked_Internal($newKey = null)
|
||||
{
|
||||
$oNewObj = parent::DBCloneTracked_Internal($newKey);
|
||||
@@ -3502,18 +3512,21 @@ EOF
|
||||
if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)))
|
||||
{
|
||||
// Check if the attribute is not read-only because of a synchro...
|
||||
$aReasons = array();
|
||||
$sSynchroIcon = '';
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$aReasons = array();
|
||||
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$sTip = addslashes($sTip);
|
||||
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
|
||||
@@ -3542,7 +3555,6 @@ EOF
|
||||
// b) or override some of the configuration settings, using the second parameter of ckeditor()
|
||||
$aConfig = array();
|
||||
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
|
||||
$aConfig['font_style'] = $sLanguage;
|
||||
$aConfig['language'] = $sLanguage;
|
||||
$aConfig['contentsLanguage'] = $sLanguage;
|
||||
$aConfig['extraPlugins'] = 'disabler';
|
||||
|
||||
@@ -530,7 +530,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$this->sGroupByAttCode = $sGroupBy;
|
||||
$this->sFunction = null;
|
||||
}
|
||||
if ($this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
|
||||
if (($sClass != '') && $this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
|
||||
{
|
||||
$sAttLabel = $this->oModelReflection->GetLabel($sClass, $this->sGroupByAttCode);
|
||||
if (!is_null($this->sFunction))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* DisplayBlock and derived class
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -795,9 +795,17 @@ class DisplayBlock
|
||||
$sCsvFile = strtolower($this->m_oFilter->GetClass()).'.csv';
|
||||
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($sCsvFile);
|
||||
$sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize()).'&format=csv';
|
||||
// Pass the parameters via POST, since expression may be very long
|
||||
$aParamsToPost = array(
|
||||
'expression' => $this->m_oFilter->ToOQL(true),
|
||||
'format' => 'csv',
|
||||
'filename' => $sCsvFile,
|
||||
'charset' => 'UTF-8',
|
||||
);
|
||||
if ($bAdvancedMode)
|
||||
{
|
||||
$sDownloadLink .= '&fields_advanced=1';
|
||||
$aParamsToPost['fields_advance'] = 1;
|
||||
$sChecked = 'CHECKED';
|
||||
}
|
||||
else
|
||||
@@ -805,7 +813,7 @@ class DisplayBlock
|
||||
$sLinkToToggle = $sLinkToToggle.'&advanced=1';
|
||||
$sChecked = '';
|
||||
}
|
||||
$sAjaxLink = $sDownloadLink.'&charset=UTF-8'; // Includes &fields_advanced=1 if in advanced mode
|
||||
$sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
|
||||
|
||||
/*
|
||||
$sCSVData = cmdbAbstractObject::GetSetAsCSV($this->m_oSet, array('fields_advanced' => $bAdvancedMode));
|
||||
@@ -856,7 +864,8 @@ class DisplayBlock
|
||||
$sHtml .= "<div id=\"csv_content_loading\"><div style=\"width: 250px; height: 20px; background: url(../setup/orange-progress.gif); border: 1px #999 solid; margin-left:auto; margin-right: auto; text-align: center;\">".Dict::S('UI:Loading')."</div></div><textarea id=\"csv_content\" style=\"display:none;\">\n";
|
||||
//$sHtml .= htmlentities($sCSVData, ENT_QUOTES, 'UTF-8');
|
||||
$sHtml .= "</textarea>\n";
|
||||
$oPage->add_ready_script("$.post('$sAjaxLink', {}, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
|
||||
$sJsonParams = json_encode($aParamsToPost);
|
||||
$oPage->add_ready_script("$.post('$sAjaxLink', $sJsonParams, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
|
||||
break;
|
||||
|
||||
case 'modify':
|
||||
@@ -951,7 +960,6 @@ EOF
|
||||
$sContextParam = $oContext->GetForLink();
|
||||
|
||||
$aGroupBy = array();
|
||||
$aLabels = array();
|
||||
$iTotalCount = 0;
|
||||
$aValues = array();
|
||||
$aURLs = array();
|
||||
@@ -959,7 +967,6 @@ EOF
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aLabels[$iRow] = strip_tags($sHtmlValue);
|
||||
$aGroupBy[(int)$iRow] = (int) $aRow['_itop_count_'];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow['_itop_count_']);
|
||||
@@ -979,7 +986,7 @@ EOF
|
||||
$aNames = array();
|
||||
foreach($aValues as $idx => $aValue)
|
||||
{
|
||||
$aNames[$idx] = $aValue['label_html'];
|
||||
$aNames[$idx] = $aValue['label'];
|
||||
}
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
@@ -1006,6 +1013,12 @@ var chart = c3.generate({
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
tick: {
|
||||
culling: {max: 25}, // Maximum 24 labels on x axis (2 years).
|
||||
centered: true,
|
||||
rotate: 90,
|
||||
multiline: false
|
||||
},
|
||||
type: 'category' // this needed to load string x value
|
||||
}
|
||||
},
|
||||
@@ -1057,6 +1070,7 @@ var chart = c3.generate({
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
},
|
||||
tooltip: {
|
||||
format: {
|
||||
|
||||
@@ -531,7 +531,7 @@ EOF
|
||||
|
||||
public function GetFieldId($sCode)
|
||||
{
|
||||
return $this->GetPrefix().'attr_'.$sCode;
|
||||
return $this->GetPrefix().'attr_'.utils::GetSafeId($sCode.$this->GetSuffix());
|
||||
}
|
||||
|
||||
public function GetFieldName($sCode)
|
||||
@@ -881,7 +881,7 @@ class DesignerTextField extends DesignerFormField
|
||||
$this->sValidationPattern = $sValidationPattern;
|
||||
}
|
||||
|
||||
public function SetForbiddenValues($aValues, $sExplain)
|
||||
public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
|
||||
{
|
||||
$aForbiddenValues = $aValues;
|
||||
|
||||
@@ -893,7 +893,7 @@ class DesignerTextField extends DesignerFormField
|
||||
|
||||
}
|
||||
|
||||
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain);
|
||||
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain, 'case_sensitive' => $bCaseSensitive);
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
@@ -1408,8 +1408,12 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
|
||||
public function GetDefaultValue($sClass = 'Contact')
|
||||
{
|
||||
$sIconPath = MetaModel::GetClassIcon($sClass, false);
|
||||
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
|
||||
$sIcon = '';
|
||||
if (MetaModel::IsValidClass($sClass))
|
||||
{
|
||||
$sIconPath = MetaModel::GetClassIcon($sClass, false);
|
||||
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
|
||||
}
|
||||
return $sIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,8 @@ EOF;
|
||||
$sJSDatePickerOptions = json_encode($aPickerOptions);
|
||||
|
||||
// Time picker additional options
|
||||
|
||||
$aPickerOptions['showOn'] = '';
|
||||
$aPickerOptions['buttonImage'] = null;
|
||||
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
|
||||
$aPickerOptions['controlType'] = 'select';
|
||||
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
|
||||
@@ -208,7 +209,39 @@ EOF;
|
||||
}";
|
||||
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
|
||||
}
|
||||
|
||||
$this->add_script(
|
||||
<<< EOF
|
||||
function PrepareWidgets()
|
||||
{
|
||||
// note: each action implemented here must be idempotent,
|
||||
// because this helper function might be called several times on a given page
|
||||
|
||||
$(".date-pick").datepicker($sJSDatePickerOptions);
|
||||
|
||||
// Hack for the date and time picker addon issue on Chrome (see #1305)
|
||||
// The workaround is to instantiate the widget on demand
|
||||
// It relies on the same markup, thus reverting to the original implementation should be straightforward
|
||||
$(".datetime-pick:not(.is-widget-ready)").each(function(){
|
||||
var oInput = this;
|
||||
$(oInput).addClass('is-widget-ready');
|
||||
$('<img class="datetime-pick-button" src="../images/calendar.png">')
|
||||
.insertAfter($(this))
|
||||
.on('click', function(){
|
||||
$(oInput)
|
||||
.datetimepicker($sJSDateTimePickerOptions)
|
||||
.datetimepicker('show')
|
||||
.datetimepicker('option', 'onClose', function(dateText,inst){
|
||||
$(oInput).datetimepicker('destroy');
|
||||
})
|
||||
.on('click keypress', function(){
|
||||
$(oInput).datetimepicker('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->m_sInitScript =
|
||||
<<< EOF
|
||||
try
|
||||
@@ -444,8 +477,7 @@ EOF
|
||||
|
||||
// End of Tabs handling
|
||||
|
||||
$(".date-pick").datepicker($sJSDatePickerOptions);
|
||||
$(".datetime-pick").datetimepicker($sJSDateTimePickerOptions);
|
||||
PrepareWidgets();
|
||||
|
||||
// Make sortable, everything that claims to be sortable
|
||||
$('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function()
|
||||
|
||||
@@ -572,27 +572,59 @@ EOF
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
}
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
|
||||
{
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
// Let's reply that we're currently logged-out
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit;
|
||||
}
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
|
||||
{
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
// Let's reply that we're currently logged-out
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit;
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
@@ -602,66 +634,33 @@ EOF
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
UserRights::_InitSessionCache();
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
UserRights::_InitSessionCache();
|
||||
}
|
||||
}
|
||||
return self::EXIT_CODE_OK;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Class PortalWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -96,15 +96,88 @@ class PortalWebPage extends NiceWebPage
|
||||
$this->add_linked_script("../js/ckeditor/ckeditor.js");
|
||||
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
|
||||
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
|
||||
$sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage'));
|
||||
$sJSDaysMin = json_encode(array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')));
|
||||
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
|
||||
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
|
||||
$aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min'));
|
||||
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
|
||||
$sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat();
|
||||
$oTimeFormat = new DateTimeFormat($sTimeFormat);
|
||||
$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
|
||||
|
||||
// Date picker options
|
||||
$aPickerOptions = array(
|
||||
'showOn' => 'button',
|
||||
'buttonImage' => '../images/calendar.png',
|
||||
'buttonImageOnly' => true,
|
||||
'dateFormat' => AttributeDate::GetFormat()->ToDatePicker(),
|
||||
'constrainInput' => false,
|
||||
'changeMonth' => true,
|
||||
'changeYear' => true,
|
||||
'dayNamesMin' => $aDaysMin,
|
||||
'monthNamesShort' => $aMonthsShort,
|
||||
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
|
||||
);
|
||||
$sJSDatePickerOptions = json_encode($aPickerOptions);
|
||||
|
||||
// Time picker additional options
|
||||
$aPickerOptions['showOn'] = '';
|
||||
$aPickerOptions['buttonImage'] = null;
|
||||
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
|
||||
$aPickerOptions['controlType'] = 'select';
|
||||
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
|
||||
$sJSDateTimePickerOptions = json_encode($aPickerOptions);
|
||||
if ($sJSLangShort != '"en"')
|
||||
{
|
||||
// More options that cannot be passed via json_encode since they must be evaluated client-side
|
||||
$aMoreJSOptions = ",
|
||||
'timeText': $.timepicker.regional[$sJSLangShort].timeText,
|
||||
'hourText': $.timepicker.regional[$sJSLangShort].hourText,
|
||||
'minuteText': $.timepicker.regional[$sJSLangShort].minuteText,
|
||||
'secondText': $.timepicker.regional[$sJSLangShort].secondText,
|
||||
'currentText': $.timepicker.regional[$sJSLangShort].currentText
|
||||
}";
|
||||
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
|
||||
}
|
||||
$this->add_script(
|
||||
<<< EOF
|
||||
function PrepareWidgets()
|
||||
{
|
||||
// note: each action implemented here must be idempotent,
|
||||
// because this helper function might be called several times on a given page
|
||||
|
||||
$(".date-pick").datepicker($sJSDatePickerOptions);
|
||||
|
||||
// Hack for the date and time picker addon issue on Chrome (see #1305)
|
||||
// The workaround is to instantiate the widget on demand
|
||||
// It relies on the same markup, thus reverting to the original implementation should be straightforward
|
||||
$(".datetime-pick:not(.is-widget-ready)").each(function(){
|
||||
var oInput = this;
|
||||
$(oInput).addClass('is-widget-ready');
|
||||
$('<img class="datetime-pick-button" src="../images/calendar.png">')
|
||||
.insertAfter($(this))
|
||||
.on('click', function(){
|
||||
$(oInput)
|
||||
.datetimepicker($sJSDateTimePickerOptions)
|
||||
.datetimepicker('show')
|
||||
.datetimepicker('option', 'onClose', function(dateText,inst){
|
||||
$(oInput).datetimepicker('destroy');
|
||||
})
|
||||
.on('click keypress', function(){
|
||||
$(oInput).datetimepicker('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
@@ -146,34 +219,10 @@ try
|
||||
}
|
||||
});
|
||||
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
PrepareWidgets();
|
||||
|
||||
//$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry_html').toggle(); });
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
|
||||
$(document).ajaxSend(function(event, jqxhr, options) {
|
||||
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class DisplayTemplate
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -353,11 +353,15 @@ class ObjectDetailsTemplate extends DisplayTemplate
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sSynchroIcon = " <img id=\"synchro_$iInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,9 +20,8 @@
|
||||
* Class UIHTMLEditorWidget
|
||||
* UI wdiget for displaying and editing one-way encrypted passwords
|
||||
*
|
||||
* @author Phil Eddies
|
||||
* @author Romain Quetiez
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -99,9 +98,26 @@ class UIHTMLEditorWidget
|
||||
|
||||
// Could also be bound to 'instanceReady.ckeditor'
|
||||
$oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
|
||||
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$iId').bind('update', function(evt){
|
||||
BlockField('cke_$iId', $('#$iId').attr('disabled'));
|
||||
//Delayed execution - ckeditor must be properly initialized before setting readonly
|
||||
var retryCount = 0;
|
||||
var oMe = $('#$iId');
|
||||
var delayedSetReadOnly = function () {
|
||||
if (oMe.data('ckeditorInstance').editable() == undefined && retryCount++ < 10) {
|
||||
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
|
||||
}
|
||||
else
|
||||
{
|
||||
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
|
||||
}
|
||||
};
|
||||
setTimeout(delayedSetReadOnly, 50);
|
||||
});
|
||||
EOF
|
||||
);
|
||||
return $sHtmlValue;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -163,38 +163,10 @@ class UILinksWidget
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
$sState = '';
|
||||
$sJSDaysMin = json_encode(array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')));
|
||||
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
|
||||
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
|
||||
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
PrepareWidgets();
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
@@ -437,6 +437,45 @@ class utils
|
||||
return $iReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
|
||||
*
|
||||
* @param type $value
|
||||
* @return string
|
||||
*/
|
||||
public static function BytesToFriendlyFormat($value)
|
||||
{
|
||||
$sReturn = '';
|
||||
// Kilobytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'K';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
// Megabytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'M';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
// Gigabytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'G';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
// Terabytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'T';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
|
||||
$value = round($value, 1);
|
||||
|
||||
return $value . '' . $sReturn . 'B';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
|
||||
* Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
|
||||
@@ -1275,10 +1314,21 @@ class utils
|
||||
*/
|
||||
public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
|
||||
{
|
||||
// If image size smaller than maximums, we do nothing
|
||||
if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
|
||||
{
|
||||
return $oImage;
|
||||
}
|
||||
|
||||
|
||||
// If gd extension is not loaded, we put a warning in the log and return the image as is
|
||||
if (extension_loaded('gd') === false)
|
||||
{
|
||||
IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. It will remain as ' . $iWidth . 'x' . $iHeight . ' instead of ' . $iMaxImageWidth . 'x' . $iMaxImageHeight);
|
||||
return $oImage;
|
||||
}
|
||||
|
||||
|
||||
switch($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
|
||||
@@ -138,6 +138,22 @@ class WizardHelper
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
}
|
||||
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
if ($value != null)
|
||||
{
|
||||
$oDate = $oAttDef->GetFormat()->Parse($value);
|
||||
if ($oDate instanceof DateTime)
|
||||
{
|
||||
$value = $oDate->format($oAttDef->GetInternalFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oObj->Set($sAttCode, $value);
|
||||
|
||||
69
core/apc-compat.php
Normal file
69
core/apc-compat.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
// Emulate the API of APC, over APCU
|
||||
// Note: for PHP < 7, this compatibility used to be provided by APCU itself (if compiled with some options)
|
||||
// for PHP 7+, it can be provided by the mean of apcu_bc, which is not so simple to install
|
||||
// The current emulation aims at skipping this complexity
|
||||
if (!function_exists('apc_store') && function_exists('apcu_store'))
|
||||
{
|
||||
function apc_add($key, $var, $ttl = 0)
|
||||
{
|
||||
return apcu_add($key, $var, $ttl);
|
||||
}
|
||||
function apc_cache_info($cache_type = '', $limited = false)
|
||||
{
|
||||
return apcu_cache_info($limited);
|
||||
}
|
||||
function apc_cas($key, $old, $new)
|
||||
{
|
||||
return apcu_cas($key, $old, $new);
|
||||
}
|
||||
function apc_clear_cache($cache_type = '')
|
||||
{
|
||||
return apcu_clear_cache();
|
||||
}
|
||||
function apc_dec($key, $step = 1, &$success = null)
|
||||
{
|
||||
apcu_dec($key, $step, $success);
|
||||
}
|
||||
function apc_delete($key)
|
||||
{
|
||||
return apcu_delete($key);
|
||||
}
|
||||
function apc_exists($keys)
|
||||
{
|
||||
return apcu_exists($keys);
|
||||
}
|
||||
function apc_fetch($key)
|
||||
{
|
||||
return apcu_fetch($key);
|
||||
}
|
||||
function apc_inc($key, $step = 1, &$success = null)
|
||||
{
|
||||
apcu_inc($key, $step, $success);
|
||||
}
|
||||
function apc_sma_info($limited = false)
|
||||
{
|
||||
return apcu_sma_info($limited);
|
||||
}
|
||||
function apc_store($key, $var, $ttl = 0)
|
||||
{
|
||||
return apcu_store($key, $var, $ttl);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Typology for the attributes
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -565,7 +565,7 @@ abstract class AttributeDefinition
|
||||
}
|
||||
|
||||
// - Comparing flags
|
||||
if (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY))
|
||||
if ($this->IsWritable() && (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY)))
|
||||
{
|
||||
$oFormField->SetMandatory(true);
|
||||
}
|
||||
@@ -2116,6 +2116,41 @@ class AttributeFinalClass extends AttributeString
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum can be localized
|
||||
*/
|
||||
public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
|
||||
{
|
||||
if ($bLocalizedValue)
|
||||
{
|
||||
// Lookup for the value matching the input
|
||||
//
|
||||
$sFoundValue = null;
|
||||
$aRawValues = self::GetAllowedValues();
|
||||
if (!is_null($aRawValues))
|
||||
{
|
||||
foreach ($aRawValues as $sKey => $sValue)
|
||||
{
|
||||
if ($sProposedValue == $sValue)
|
||||
{
|
||||
$sFoundValue = $sKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_null($sFoundValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return $this->MakeRealValue($sFoundValue, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Because this is sometimes used to get a localized/string version of an attribute...
|
||||
public function GetEditValue($sValue, $oHostObj = null)
|
||||
{
|
||||
@@ -2733,7 +2768,7 @@ class AttributeCaseLog extends AttributeLongText
|
||||
if ($proposedValue instanceof ormCaseLog)
|
||||
{
|
||||
// Passthrough
|
||||
$ret = $proposedValue;
|
||||
$ret = clone $proposedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3551,8 +3586,16 @@ class AttributeMetaEnum extends AttributeEnum
|
||||
else
|
||||
{
|
||||
$sParent = MetaModel::GetParentClass($sClass);
|
||||
$aMappingData = $this->GetMapRule($sParent);
|
||||
if (is_null($sParent))
|
||||
{
|
||||
$aMappingData = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aMappingData = $this->GetMapRule($sParent);
|
||||
}
|
||||
}
|
||||
|
||||
return $aMappingData;
|
||||
}
|
||||
}
|
||||
@@ -3690,6 +3733,15 @@ class AttributeDateTime extends AttributeDBField
|
||||
}
|
||||
|
||||
protected function GetSQLCol($bFullSpec = false) {return "DATETIME";}
|
||||
|
||||
public function GetImportColumns()
|
||||
{
|
||||
// Allow an empty string to be a valid value (synonym for "reset")
|
||||
$aColumns = array();
|
||||
$aColumns[$this->GetCode()] = 'VARCHAR(19)';
|
||||
return $aColumns;
|
||||
}
|
||||
|
||||
public static function GetAsUnixSeconds($value)
|
||||
{
|
||||
$oDeadlineDateTime = new DateTime($value);
|
||||
@@ -3791,6 +3843,8 @@ class AttributeDateTime extends AttributeDBField
|
||||
elseif (empty($value))
|
||||
{
|
||||
// Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null.
|
||||
// todo: this is NOT valid in strict mode (default setting for MySQL 5.7)
|
||||
// todo: if to be kept, this should be overloaded for AttributeDate (0000-00-00)
|
||||
return '0000-00-00 00:00:00';
|
||||
}
|
||||
return $value;
|
||||
@@ -4003,7 +4057,7 @@ class AttributeDuration extends AttributeInteger
|
||||
|
||||
static public function GetFormFieldClass()
|
||||
{
|
||||
return '\\Combodo\\iTop\\Form\\Field\\LabelField';
|
||||
return '\\Combodo\\iTop\\Form\\Field\\DurationField';
|
||||
}
|
||||
|
||||
public function MakeFormField(DBObject $oObject, $oFormField = null)
|
||||
@@ -4017,7 +4071,7 @@ class AttributeDuration extends AttributeInteger
|
||||
|
||||
// Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition
|
||||
$sAttCode = $this->GetCode();
|
||||
$oFormField->SetCurrentValue(html_entity_decode($oObject->GetAsHTML($sAttCode), ENT_QUOTES, 'UTF-8'));
|
||||
$oFormField->SetCurrentValue($oObject->Get($sAttCode));
|
||||
$oFormField->SetReadOnly(true);
|
||||
|
||||
return $oFormField;
|
||||
@@ -4073,7 +4127,15 @@ class AttributeDate extends AttributeDateTime
|
||||
|
||||
public function GetEditClass() {return "Date";}
|
||||
protected function GetSQLCol($bFullSpec = false) {return "DATE";}
|
||||
|
||||
public function GetImportColumns()
|
||||
{
|
||||
// Allow an empty string to be a valid value (synonym for "reset")
|
||||
$aColumns = array();
|
||||
$aColumns[$this->GetCode()] = 'VARCHAR(10)';
|
||||
return $aColumns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override to specify Field class
|
||||
*
|
||||
@@ -4330,9 +4392,13 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
$oTmpField = $oFormField;
|
||||
$oFormField->SetOnFinalizeCallback(function() use ($oTmpField, $oTmpAttDef, $oObject)
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression());
|
||||
$oSearch->SetInternalParams(array('this' => $oObject));
|
||||
$oTmpField->SetSearch($oSearch);
|
||||
// We set search object only if it has not already been set (overrided)
|
||||
if ($oTmpField->GetSearch() === null)
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression());
|
||||
$oSearch->SetInternalParams(array('this' => $oObject));
|
||||
$oTmpField->SetSearch($oSearch);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -4825,7 +4891,11 @@ class AttributeBlob extends AttributeDefinition
|
||||
// from a CSV by specifying its path/URL
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
if (!is_object($proposedValue))
|
||||
if (is_object($proposedValue))
|
||||
{
|
||||
$proposedValue = clone $proposedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file_exists($proposedValue) && UserRights::IsAdministrator())
|
||||
{
|
||||
@@ -5063,6 +5133,33 @@ class AttributeBlob extends AttributeDefinition
|
||||
}
|
||||
return $sFingerprint;
|
||||
}
|
||||
|
||||
static public function GetFormFieldClass()
|
||||
{
|
||||
return '\\Combodo\\iTop\\Form\\Field\\BlobField';
|
||||
}
|
||||
|
||||
public function MakeFormField(DBObject $oObject, $oFormField = null)
|
||||
{
|
||||
if ($oFormField === null)
|
||||
{
|
||||
$sFormFieldClass = static::GetFormFieldClass();
|
||||
$oFormField = new $sFormFieldClass($this->GetCode());
|
||||
}
|
||||
|
||||
// Note: As of today we want this field to always be read-only
|
||||
$oFormField->SetReadOnly(true);
|
||||
|
||||
// Generating urls
|
||||
$value = $oObject->Get($this->GetCode());
|
||||
$oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
|
||||
$oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
|
||||
|
||||
parent::MakeFormField($oObject, $oFormField);
|
||||
|
||||
return $oFormField;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5078,7 +5175,11 @@ class AttributeImage extends AttributeBlob
|
||||
// from a CSV by specifying its path/URL
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
if (!is_object($proposedValue))
|
||||
if (is_object($proposedValue))
|
||||
{
|
||||
$proposedValue = clone $proposedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file_exists($proposedValue) && UserRights::IsAdministrator())
|
||||
{
|
||||
@@ -5975,7 +6076,11 @@ class AttributeOneWayPassword extends AttributeDefinition
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
$oPassword = $proposedValue;
|
||||
if (!is_object($oPassword))
|
||||
if (is_object($oPassword))
|
||||
{
|
||||
$oPassword = clone $proposedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPassword = new ormPassword('', '');
|
||||
$oPassword->SetPassword($proposedValue);
|
||||
|
||||
@@ -22,6 +22,7 @@ MetaModel::IncludeModule('application/user.preferences.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
|
||||
@@ -828,8 +828,8 @@ class BulkChange
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sRegExp = $oFormat->ToRegExpr();
|
||||
if (!preg_match('/'.$sRegExp.'/', $this->m_aData[$iRow][$iCol]))
|
||||
$sRegExp = $oFormat->ToRegExpr('/');
|
||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -48,7 +48,6 @@ define ('DEFAULT_LOG_GLOBAL', true);
|
||||
define ('DEFAULT_LOG_NOTIFICATION', true);
|
||||
define ('DEFAULT_LOG_ISSUE', true);
|
||||
define ('DEFAULT_LOG_WEB_SERVICE', true);
|
||||
define ('DEFAULT_LOG_QUERIES', false);
|
||||
|
||||
define ('DEFAULT_QUERY_CACHE_ENABLED', true);
|
||||
|
||||
@@ -246,7 +245,7 @@ class Config
|
||||
),
|
||||
'access_mode' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)',
|
||||
'description' => 'Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3',
|
||||
'default' => ACCESS_FULL,
|
||||
'value' => ACCESS_FULL,
|
||||
'source_of_value' => '',
|
||||
@@ -990,7 +989,6 @@ class Config
|
||||
protected $m_bLogNotification;
|
||||
protected $m_bLogIssue;
|
||||
protected $m_bLogWebService;
|
||||
protected $m_bLogQueries; // private setting
|
||||
protected $m_bQueryCacheEnabled; // private setting
|
||||
|
||||
/**
|
||||
@@ -1085,7 +1083,6 @@ class Config
|
||||
$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
|
||||
$this->m_sEncryptionKey = DEFAULT_ENCRYPTION_KEY;
|
||||
$this->m_aCharsets = array();
|
||||
$this->m_bLogQueries = DEFAULT_LOG_QUERIES;
|
||||
$this->m_bQueryCacheEnabled = DEFAULT_QUERY_CACHE_ENABLED;
|
||||
|
||||
$this->m_aModuleSettings = array();
|
||||
@@ -1198,7 +1195,6 @@ class Config
|
||||
$this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION;
|
||||
$this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE;
|
||||
$this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE;
|
||||
$this->m_bLogQueries = isset($MySettings['log_queries']) ? (bool) trim($MySettings['log_queries']) : DEFAULT_LOG_QUERIES;
|
||||
$this->m_bQueryCacheEnabled = isset($MySettings['query_cache_enabled']) ? (bool) trim($MySettings['query_cache_enabled']) : DEFAULT_QUERY_CACHE_ENABLED;
|
||||
|
||||
$this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT;
|
||||
@@ -1317,7 +1313,7 @@ class Config
|
||||
|
||||
public function GetLogQueries()
|
||||
{
|
||||
return $this->m_bLogQueries;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function GetQueryCacheEnabled()
|
||||
@@ -1519,7 +1515,6 @@ class Config
|
||||
$aSettings['log_notification'] = $this->m_bLogNotification;
|
||||
$aSettings['log_issue'] = $this->m_bLogIssue;
|
||||
$aSettings['log_web_service'] = $this->m_bLogWebService;
|
||||
$aSettings['log_queries'] = $this->m_bLogQueries;
|
||||
$aSettings['query_cache_enabled'] = $this->m_bQueryCacheEnabled;
|
||||
$aSettings['min_display_limit'] = $this->m_iMinDisplayLimit;
|
||||
$aSettings['max_display_limit'] = $this->m_iMaxDisplayLimit;
|
||||
@@ -1578,7 +1573,6 @@ class Config
|
||||
'log_notification' => $this->m_bLogNotification,
|
||||
'log_issue' => $this->m_bLogIssue,
|
||||
'log_web_service' => $this->m_bLogWebService,
|
||||
'log_queries' => $this->m_bLogQueries,
|
||||
'query_cache_enabled' => $this->m_bQueryCacheEnabled,
|
||||
'secure_connection_required' => $this->m_bSecureConnectionRequired,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
// Copyright (C) 2016-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -29,13 +29,13 @@
|
||||
*
|
||||
* if (ContextTag::Check('GUI:Portal'))
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2016-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ContextTag
|
||||
{
|
||||
protected static $aStack;
|
||||
protected static $aStack = array();
|
||||
|
||||
/**
|
||||
* Store a context tag on the stack
|
||||
|
||||
@@ -413,10 +413,16 @@ EOF
|
||||
/**
|
||||
* Get the regular expression to (approximately) validate a date/time for the current format
|
||||
* The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
|
||||
* @param string $sDelimiter Surround the regexp (and escape) if needed
|
||||
* @return string The regular expression in PCRE syntax
|
||||
*/
|
||||
public function ToRegExpr()
|
||||
public function ToRegExpr($sDelimiter = null)
|
||||
{
|
||||
return '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]/:').'$';
|
||||
$sRet = '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]:').'$';
|
||||
if ($sDelimiter !== null)
|
||||
{
|
||||
$sRet = $sDelimiter.str_replace($sDelimiter, '\\'.$sDelimiter, $sRet).$sDelimiter;
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,10 +192,14 @@ abstract class DBObject implements iDisplay
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bAllowAllData DEPRECATED: the reload must never fail!
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function Reload($bAllowAllData = false)
|
||||
{
|
||||
assert($this->m_bIsInDB);
|
||||
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, $bAllowAllData/* in the future $this->m_bAllowAllData ??*/);
|
||||
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, true /* AllowAllData */);
|
||||
if (empty($aRow))
|
||||
{
|
||||
throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey);
|
||||
@@ -1407,6 +1411,12 @@ abstract class DBObject implements iDisplay
|
||||
return true;
|
||||
}
|
||||
|
||||
// used only by insert
|
||||
protected function OnObjectKeyReady()
|
||||
{
|
||||
// Meant to be overloaded
|
||||
}
|
||||
|
||||
// used both by insert/update
|
||||
private function DBWriteLinks()
|
||||
{
|
||||
@@ -1644,7 +1654,9 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
@@ -1931,6 +1943,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
$oFilter = new DBObjectSearch(get_class($this));
|
||||
$oFilter->AddCondition('id', $this->m_iKey, '=');
|
||||
$oFilter->AllowAllData();
|
||||
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aDBChanges);
|
||||
CMDBSource::Query($sSQL);
|
||||
@@ -3393,7 +3406,7 @@ abstract class DBObject implements iDisplay
|
||||
throw new Exception('Missing argument #1: source attribute');
|
||||
}
|
||||
$sSourceKeyAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
|
||||
if (($sSourceKeyAttCode != 'id') && !MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oObjectToRead)."::".$sSourceKeyAttCode);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,10 +20,12 @@
|
||||
/**
|
||||
* Define filters for a given class of objects (formerly named "filter")
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
// Dev hack for disabling the some query build optimizations (Folding/Merging)
|
||||
define('ENABLE_OPT', true);
|
||||
|
||||
class DBObjectSearch extends DBSearch
|
||||
{
|
||||
@@ -35,6 +37,11 @@ class DBObjectSearch extends DBSearch
|
||||
private $m_aPointingTo;
|
||||
private $m_aReferencedBy;
|
||||
|
||||
// By default, some information may be hidden to the current user
|
||||
// But it may happen that we need to disable that feature
|
||||
protected $m_bAllowAllData = false;
|
||||
protected $m_bDataFiltered = false;
|
||||
|
||||
public function __construct($sClass, $sClassAlias = null)
|
||||
{
|
||||
parent::__construct();
|
||||
@@ -52,6 +59,11 @@ class DBObjectSearch extends DBSearch
|
||||
$this->m_aReferencedBy = array();
|
||||
}
|
||||
|
||||
public function AllowAllData($bAllowAllData = true) {$this->m_bAllowAllData = $bAllowAllData;}
|
||||
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
|
||||
protected function IsDataFiltered() {return $this->m_bDataFiltered; }
|
||||
protected function SetDataFiltered() {$this->m_bDataFiltered = true;}
|
||||
|
||||
// Create a search definition that leads to 0 result, still a valid search object
|
||||
static public function FromEmptySet($sClass)
|
||||
{
|
||||
@@ -172,12 +184,87 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
if (!array_key_exists($sAlias, $this->m_aClasses))
|
||||
{
|
||||
throw new CoreException("Invalid class alias $sAlias");
|
||||
throw new CoreException("SetSelectedClasses: Invalid class alias $sAlias");
|
||||
}
|
||||
$this->m_aSelectedClasses[$sAlias] = $this->m_aClasses[$sAlias];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
$bFound = false;
|
||||
if (array_key_exists($sOldName, $this->m_aClasses))
|
||||
{
|
||||
$bFound = true;
|
||||
}
|
||||
if (array_key_exists($sNewName, $this->m_aClasses))
|
||||
{
|
||||
throw new Exception("RenameAlias: alias '$sNewName' already used");
|
||||
}
|
||||
|
||||
$aClasses = array();
|
||||
foreach ($this->m_aClasses as $sAlias => $sClass)
|
||||
{
|
||||
if ($sAlias === $sOldName)
|
||||
{
|
||||
$aClasses[$sNewName] = $sClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aClasses[$sAlias] = $sClass;
|
||||
}
|
||||
}
|
||||
$this->m_aClasses = $aClasses;
|
||||
|
||||
$aSelectedClasses = array();
|
||||
foreach ($this->m_aSelectedClasses as $sAlias => $sClass)
|
||||
{
|
||||
if ($sAlias === $sOldName)
|
||||
{
|
||||
$aSelectedClasses[$sNewName] = $sClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSelectedClasses[$sAlias] = $sClass;
|
||||
}
|
||||
}
|
||||
$this->m_aSelectedClasses = $aSelectedClasses;
|
||||
|
||||
$this->m_oSearchCondition->RenameAlias($sOldName, $sNewName);
|
||||
|
||||
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
|
||||
{
|
||||
foreach($aPointingTo as $iOperatorCode => $aFilter)
|
||||
{
|
||||
foreach($aFilter as $oExtFilter)
|
||||
{
|
||||
$bFound = $oExtFilter->RenameAlias($sOldName, $sNewName) || $bFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($this->m_aReferencedBy as $sForeignClass => $aReferences)
|
||||
{
|
||||
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
|
||||
{
|
||||
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $oForeignFilter)
|
||||
{
|
||||
$bFound = $oForeignFilter->RenameAlias($sOldName, $sNewName) || $bFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $bFound;
|
||||
}
|
||||
|
||||
public function SetModifierProperty($sPluginClass, $sProperty, $value)
|
||||
{
|
||||
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
|
||||
@@ -554,8 +641,85 @@ class DBObjectSearch extends DBSearch
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to
|
||||
* - convert a translation table (format optimized for the translation in an expression tree) into simple hash
|
||||
* - compile over an eventually existing map
|
||||
*
|
||||
* @param $aRealiasingMap Map to update
|
||||
* @param $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
|
||||
* @return array of <old-alias> => <new-alias>
|
||||
*/
|
||||
protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
|
||||
{
|
||||
if ($aRealiasingMap !== null)
|
||||
{
|
||||
foreach ($aAliasTranslation as $sPrevAlias => $aRules)
|
||||
{
|
||||
if (isset($aRules['*']))
|
||||
{
|
||||
$sNewAlias = $aRules['*'];
|
||||
$sOriginalAlias = array_search($sPrevAlias, $aRealiasingMap);
|
||||
if ($sOriginalAlias !== false)
|
||||
{
|
||||
$aRealiasingMap[$sOriginalAlias] = $sNewAlias;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRealiasingMap[$sPrevAlias] = $sNewAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
/**
|
||||
* Completes the list of alias=>class by browsing the whole structure recursively
|
||||
* This a workaround to handle some cases in which the list of classes is not correctly updated.
|
||||
* This code should disappear as soon as DBObjectSearch get split between a container search class and a Node class
|
||||
*
|
||||
* @param $aClasses List to be completed
|
||||
*/
|
||||
protected function RecomputeClassList(&$aClasses)
|
||||
{
|
||||
$aClasses[$this->GetFirstJoinedClassAlias()] = $this->GetFirstJoinedClass();
|
||||
|
||||
// Recurse in the query tree
|
||||
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
|
||||
{
|
||||
foreach($aPointingTo as $iOperatorCode => $aFilter)
|
||||
{
|
||||
foreach($aFilter as $oFilter)
|
||||
{
|
||||
$oFilter->RecomputeClassList($aClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
|
||||
{
|
||||
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
|
||||
{
|
||||
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $oForeignFilter)
|
||||
{
|
||||
$oForeignFilter->RecomputeClassList($aClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
*/
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode))
|
||||
{
|
||||
@@ -579,6 +743,28 @@ class DBObjectSearch extends DBSearch
|
||||
$aAliasTranslation = array();
|
||||
$res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
|
||||
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
|
||||
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
|
||||
|
||||
if (ENABLE_OPT && ($oFilter->GetClass() == $oFilter->GetFirstJoinedClass()))
|
||||
{
|
||||
if (isset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]))
|
||||
{
|
||||
foreach ($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode] as $oRemoteFilter)
|
||||
{
|
||||
if ($this->GetClass() == $oRemoteFilter->GetClass())
|
||||
{
|
||||
// Optimization - fold sibling query
|
||||
$aAliasTranslation = array();
|
||||
$this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation);
|
||||
unset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]);
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false);
|
||||
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->RecomputeClassList($this->m_aClasses);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -587,11 +773,33 @@ class DBObjectSearch extends DBSearch
|
||||
// Find the node on which the new tree must be attached (most of the time it is "this")
|
||||
$oReceivingFilter = $this->GetNode($this->GetClassAlias());
|
||||
|
||||
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
|
||||
$oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][] = $oFilter;
|
||||
$bMerged = false;
|
||||
if (ENABLE_OPT && isset($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode]))
|
||||
{
|
||||
foreach ($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode] as $oExisting)
|
||||
{
|
||||
if ($oExisting->GetClass() == $oFilter->GetClass())
|
||||
{
|
||||
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
|
||||
$bMerged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$bMerged)
|
||||
{
|
||||
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
|
||||
$oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][] = $oFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
$sForeignClass = $oFilter->GetClass();
|
||||
if (!MetaModel::IsValidKeyAttCode($sForeignClass, $sForeignExtKeyAttCode))
|
||||
@@ -604,6 +812,7 @@ class DBObjectSearch extends DBSearch
|
||||
// à refaire en spécifique dans FromOQL
|
||||
throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
|
||||
}
|
||||
|
||||
// Note: though it seems to be a good practice to clone the given source filter
|
||||
// (as it was done and fixed an issue in Intersect())
|
||||
// this was not implemented here because it was causing a regression (login as admin, select an org, click on any badge)
|
||||
@@ -613,6 +822,28 @@ class DBObjectSearch extends DBSearch
|
||||
$aAliasTranslation = array();
|
||||
$res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
|
||||
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
|
||||
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
|
||||
|
||||
if (ENABLE_OPT && ($oFilter->GetClass() == $oFilter->GetFirstJoinedClass()))
|
||||
{
|
||||
if (isset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]))
|
||||
{
|
||||
foreach ($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode] as $oRemoteFilter)
|
||||
{
|
||||
if ($this->GetClass() == $oRemoteFilter->GetClass())
|
||||
{
|
||||
// Optimization - fold sibling query
|
||||
$aAliasTranslation = array();
|
||||
$this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation);
|
||||
unset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]);
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false);
|
||||
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->RecomputeClassList($this->m_aClasses);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -623,8 +854,24 @@ class DBObjectSearch extends DBSearch
|
||||
// Find the node on which the new tree must be attached (most of the time it is "this")
|
||||
$oReceivingFilter = $this->GetNode($this->GetClassAlias());
|
||||
|
||||
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
|
||||
$oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][] = $oFilter;
|
||||
$bMerged = false;
|
||||
if (ENABLE_OPT && isset($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode]))
|
||||
{
|
||||
foreach ($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode] as $oExisting)
|
||||
{
|
||||
if ($oExisting->GetClass() == $oFilter->GetClass())
|
||||
{
|
||||
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
|
||||
$bMerged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$bMerged)
|
||||
{
|
||||
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
|
||||
$oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][] = $oFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public function Intersect(DBSearch $oFilter)
|
||||
@@ -654,7 +901,10 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
$oLeftFilter = $this->DeepClone();
|
||||
$oRightFilter = $oRightFilter->DeepClone();
|
||||
|
||||
|
||||
$bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed());
|
||||
$oLeftFilter->AllowAllData($bAllowAllData);
|
||||
|
||||
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
|
||||
{
|
||||
if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass()))
|
||||
@@ -810,7 +1060,7 @@ class DBObjectSearch extends DBSearch
|
||||
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
|
||||
}
|
||||
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null)
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
|
||||
{
|
||||
// Currently unused, but could be useful later
|
||||
$bRetrofitParams = false;
|
||||
@@ -850,6 +1100,10 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
$sRes .= " AND MATCHES '$sFullText'";
|
||||
}
|
||||
if ($bWithAllowAllFlag && $this->m_bAllowAllData)
|
||||
{
|
||||
$sRes .= " ALLOW ALL DATA";
|
||||
}
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
@@ -1007,6 +1261,22 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
$aAliases = array($sClassAlias => $sClass);
|
||||
|
||||
// Note: the condition must be built here, it may be altered later on when optimizing some joins
|
||||
$oConditionTree = $oOqlQuery->GetCondition();
|
||||
if ($oConditionTree instanceof Expression)
|
||||
{
|
||||
$aRawAliases = array($sClassAlias => $sClass);
|
||||
$aJoinSpecs = $oOqlQuery->GetJoins();
|
||||
if (is_array($aJoinSpecs))
|
||||
{
|
||||
foreach ($aJoinSpecs as $oJoinSpec)
|
||||
{
|
||||
$aRawAliases[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
|
||||
}
|
||||
}
|
||||
$this->m_oSearchCondition = $this->OQLExpressionToCondition($sQuery, $oConditionTree, $aRawAliases);
|
||||
}
|
||||
|
||||
// Maintain an array of filters, because the flat list is in fact referring to a tree
|
||||
// And this will be an easy way to dispatch the conditions
|
||||
// $this will be referenced by the other filters, or the other way around...
|
||||
@@ -1015,19 +1285,32 @@ class DBObjectSearch extends DBSearch
|
||||
$aJoinSpecs = $oOqlQuery->GetJoins();
|
||||
if (is_array($aJoinSpecs))
|
||||
{
|
||||
$aAliasTranslation = array();
|
||||
foreach ($aJoinSpecs as $oJoinSpec)
|
||||
{
|
||||
$sJoinClass = $oJoinSpec->GetClass();
|
||||
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
|
||||
if (isset($aAliasTranslation[$sJoinClassAlias]['*']))
|
||||
{
|
||||
$sJoinClassAlias = $aAliasTranslation[$sJoinClassAlias]['*'];
|
||||
}
|
||||
|
||||
// Assumption: ext key on the left only !!!
|
||||
// normalization should take care of this
|
||||
$oLeftField = $oJoinSpec->GetLeftField();
|
||||
$sFromClass = $oLeftField->GetParent();
|
||||
if (isset($aAliasTranslation[$sFromClass]['*']))
|
||||
{
|
||||
$sFromClass = $aAliasTranslation[$sFromClass]['*'];
|
||||
}
|
||||
$sExtKeyAttCode = $oLeftField->GetName();
|
||||
|
||||
$oRightField = $oJoinSpec->GetRightField();
|
||||
$sToClass = $oRightField->GetParent();
|
||||
if (isset($aAliasTranslation[$sToClass]['*']))
|
||||
{
|
||||
$sToClass = $aAliasTranslation[$sToClass]['*'];
|
||||
}
|
||||
|
||||
$aAliases[$sJoinClassAlias] = $sJoinClass;
|
||||
$aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias);
|
||||
@@ -1069,19 +1352,16 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
$oReceiver = $aJoinItems[$sToClass];
|
||||
$oNewComer = $aJoinItems[$sFromClass];
|
||||
|
||||
$aAliasTranslation = array();
|
||||
$oReceiver->AddCondition_ReferencedBy_InNameSpace($oNewComer, $sExtKeyAttCode, $oReceiver->m_aClasses, $aAliasTranslation, $iOperatorCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oReceiver = $aJoinItems[$sFromClass];
|
||||
$oNewComer = $aJoinItems[$sToClass];
|
||||
|
||||
$aAliasTranslation = array();
|
||||
$oReceiver->AddCondition_PointingTo_InNameSpace($oNewComer, $sExtKeyAttCode, $oReceiver->m_aClasses, $aAliasTranslation, $iOperatorCode);
|
||||
}
|
||||
}
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false /* leave unresolved fields */);
|
||||
}
|
||||
|
||||
// Check and prepare the select information
|
||||
@@ -1092,12 +1372,6 @@ class DBObjectSearch extends DBSearch
|
||||
$this->m_aSelectedClasses[$sClassToSelect] = $aAliases[$sClassToSelect];
|
||||
}
|
||||
$this->m_aClasses = $aAliases;
|
||||
|
||||
$oConditionTree = $oOqlQuery->GetCondition();
|
||||
if ($oConditionTree instanceof Expression)
|
||||
{
|
||||
$this->m_oSearchCondition = $this->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1129,7 +1403,139 @@ class DBObjectSearch extends DBSearch
|
||||
return $oSQLQuery->RenderUpdate($aScalarArgs);
|
||||
}
|
||||
|
||||
public function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
{
|
||||
// Hide objects that are not visible to the current user
|
||||
//
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
|
||||
{
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false)
|
||||
{
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
|
||||
}
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
else
|
||||
{
|
||||
// should be true at this point, meaning that no additional filtering
|
||||
// is required
|
||||
}
|
||||
}
|
||||
|
||||
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
|
||||
//
|
||||
$aModifierProperties = MetaModel::MakeModifierProperties($oSearch);
|
||||
|
||||
// Create a unique cache id
|
||||
//
|
||||
if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
|
||||
{
|
||||
// Need to identify the query
|
||||
$sOqlQuery = $oSearch->ToOql(false, null, true);
|
||||
|
||||
if (count($aModifierProperties))
|
||||
{
|
||||
array_multisort($aModifierProperties);
|
||||
$sModifierProperties = json_encode($aModifierProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sModifierProperties = '';
|
||||
}
|
||||
|
||||
$sRawId = $sOqlQuery.$sModifierProperties;
|
||||
if (!is_null($aAttToLoad))
|
||||
{
|
||||
$sRawId .= json_encode($aAttToLoad);
|
||||
}
|
||||
if (!is_null($aGroupByExpr))
|
||||
{
|
||||
foreach($aGroupByExpr as $sAlias => $oExpr)
|
||||
{
|
||||
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
|
||||
}
|
||||
}
|
||||
$sRawId .= $bGetCount;
|
||||
if (is_array($aSelectedClasses))
|
||||
{
|
||||
$sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns
|
||||
}
|
||||
$sOqlId = md5($sRawId);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sOqlQuery = "SELECTING... ".$oSearch->GetClass();
|
||||
$sOqlId = "query id ? n/a";
|
||||
}
|
||||
|
||||
|
||||
// Query caching
|
||||
//
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
// Warning: using directly the query string as the key to the hash array can FAIL if the string
|
||||
// is long and the differences are only near the end... so it's safer (but not bullet proof?)
|
||||
// to use a hash (like md5) of the string as the key !
|
||||
//
|
||||
// Example of two queries that were found as similar by the hash array:
|
||||
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2
|
||||
// and
|
||||
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2
|
||||
// the only difference is R instead or O at position 285 (TTR instead of TTO)...
|
||||
//
|
||||
if (array_key_exists($sOqlId, self::$m_aQueryStructCache))
|
||||
{
|
||||
// hit!
|
||||
|
||||
$oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId]));
|
||||
// Note: cloning is not enough because the subtree is made of objects
|
||||
}
|
||||
elseif (self::$m_bUseAPCCache)
|
||||
{
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
$sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId;
|
||||
$oKPI = new ExecutionKPI();
|
||||
$result = apc_fetch($sOqlAPCCacheId);
|
||||
$oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery);
|
||||
|
||||
if (is_object($result))
|
||||
{
|
||||
$oSQLQuery = $result;
|
||||
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($oSQLQuery))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
|
||||
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
|
||||
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
if (self::$m_bUseAPCCache)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL);
|
||||
$oKPI->ComputeStats('Query APC (store)', $sOqlQuery);
|
||||
}
|
||||
|
||||
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone();
|
||||
}
|
||||
}
|
||||
return $oSQLQuery;
|
||||
}
|
||||
|
||||
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
{
|
||||
$oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
|
||||
|
||||
|
||||
@@ -445,8 +445,8 @@ class DBObjectSet
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aOrderBy Format: field_code => boolean (true = ascending, false = descending)
|
||||
*
|
||||
* @param hash $aOrderBy Format: [alias.]attcode => boolean (true = ascending, false = descending)
|
||||
*/
|
||||
public function SetOrderBy($aOrderBy)
|
||||
{
|
||||
@@ -461,6 +461,34 @@ class DBObjectSet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aAliases Format: alias => boolean (true = ascending, false = descending). If omitted, then it defaults to all the selected classes
|
||||
*/
|
||||
public function SetOrderByClasses($aAliases = null)
|
||||
{
|
||||
if ($aAliases === null)
|
||||
{
|
||||
$aAliases = array();
|
||||
foreach ($this->GetSelectedClasses() as $sAlias => $sClass)
|
||||
{
|
||||
$aAliases[$sAlias] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aAttributes = array();
|
||||
foreach ($aAliases as $sAlias => $bClassDirection)
|
||||
{
|
||||
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClass($sAlias)) as $sAttCode => $bAttributeDirection)
|
||||
{
|
||||
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
|
||||
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
|
||||
}
|
||||
}
|
||||
$this->SetOrderBy($aAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 'count' limit for loading the rows from the DB
|
||||
*
|
||||
|
||||
@@ -42,14 +42,9 @@ abstract class DBSearch
|
||||
const JOIN_POINTING_TO = 0;
|
||||
const JOIN_REFERENCED_BY = 1;
|
||||
|
||||
protected $m_bDataFiltered = false;
|
||||
protected $m_bNoContextParameters = false;
|
||||
protected $m_aModifierProperties = array();
|
||||
|
||||
// By default, some information may be hidden to the current user
|
||||
// But it may happen that we need to disable that feature
|
||||
protected $m_bAllowAllData = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@@ -62,12 +57,11 @@ abstract class DBSearch
|
||||
return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well
|
||||
}
|
||||
|
||||
public function AllowAllData() {$this->m_bAllowAllData = true;}
|
||||
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
|
||||
abstract public function AllowAllData();
|
||||
abstract public function IsAllDataAllowed();
|
||||
|
||||
public function NoContextParameters() {$this->m_bNoContextParameters = true;}
|
||||
public function HasContextParameters() {return $this->m_bNoContextParameters;}
|
||||
public function IsDataFiltered() {return $this->m_bDataFiltered; }
|
||||
public function SetDataFiltered() {$this->m_bDataFiltered = true;}
|
||||
|
||||
public function SetModifierProperty($sPluginClass, $sProperty, $value)
|
||||
{
|
||||
@@ -103,6 +97,15 @@ abstract class DBSearch
|
||||
*/
|
||||
abstract public function SetSelectedClasses($aSelectedClasses);
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
abstract public function RenameAlias($sOldName, $sNewName);
|
||||
|
||||
abstract public function IsAny();
|
||||
|
||||
public function Describe(){return 'deprecated - use ToOQL() instead';}
|
||||
@@ -127,19 +130,35 @@ abstract class DBSearch
|
||||
abstract public function AddConditionAdvanced($sAttSpec, $value);
|
||||
abstract public function AddCondition_FullText($sFullText);
|
||||
|
||||
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS);
|
||||
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS);
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
*/
|
||||
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
abstract public function Intersect(DBSearch $oFilter);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param DBSearch $oFilter
|
||||
* @param integer $iDirection
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param integer $iOperatorCode
|
||||
* @param array &$RealisasingMap Map of aliases from the attached query, that could have been renamed by the optimization process
|
||||
* @return DBSearch
|
||||
*/
|
||||
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
$oSourceFilter = $this->DeepClone();
|
||||
$oRet = null;
|
||||
@@ -149,7 +168,7 @@ abstract class DBSearch
|
||||
$aSearches = array();
|
||||
foreach ($oFilter->GetSearches() as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode);
|
||||
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
$oRet = new DBUnionSearch($aSearches);
|
||||
}
|
||||
@@ -157,7 +176,7 @@ abstract class DBSearch
|
||||
{
|
||||
if ($iDirection === static::JOIN_POINTING_TO)
|
||||
{
|
||||
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
|
||||
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -165,7 +184,7 @@ abstract class DBSearch
|
||||
{
|
||||
throw new Exception('Only TREE_OPERATOR_EQUALS operator code is supported yet for AddCondition_ReferencedBy.');
|
||||
}
|
||||
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode);
|
||||
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode, TREE_OPERATOR_EQUALS, $aRealiasingMap);
|
||||
}
|
||||
$oRet = $oSourceFilter;
|
||||
}
|
||||
@@ -219,7 +238,7 @@ abstract class DBSearch
|
||||
return $oSearchWithAlias;
|
||||
}
|
||||
|
||||
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null);
|
||||
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false);
|
||||
|
||||
static protected $m_aOQLQueries = array();
|
||||
|
||||
@@ -492,129 +511,8 @@ abstract class DBSearch
|
||||
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
|
||||
{
|
||||
// Hide objects that are not visible to the current user
|
||||
//
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
|
||||
{
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false)
|
||||
{
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
|
||||
}
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
else
|
||||
{
|
||||
// should be true at this point, meaning that no additional filtering
|
||||
// is required
|
||||
}
|
||||
}
|
||||
|
||||
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
|
||||
//
|
||||
$aModifierProperties = MetaModel::MakeModifierProperties($oSearch);
|
||||
|
||||
// Create a unique cache id
|
||||
//
|
||||
if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
|
||||
{
|
||||
// Need to identify the query
|
||||
$sOqlQuery = $oSearch->ToOql();
|
||||
|
||||
if (count($aModifierProperties))
|
||||
{
|
||||
array_multisort($aModifierProperties);
|
||||
$sModifierProperties = json_encode($aModifierProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sModifierProperties = '';
|
||||
}
|
||||
|
||||
$sRawId = $sOqlQuery.$sModifierProperties;
|
||||
if (!is_null($aAttToLoad))
|
||||
{
|
||||
$sRawId .= json_encode($aAttToLoad);
|
||||
}
|
||||
if (!is_null($aGroupByExpr))
|
||||
{
|
||||
foreach($aGroupByExpr as $sAlias => $oExpr)
|
||||
{
|
||||
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
|
||||
}
|
||||
}
|
||||
$sRawId .= $bGetCount;
|
||||
$sOqlId = md5($sRawId);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sOqlQuery = "SELECTING... ".$oSearch->GetClass();
|
||||
$sOqlId = "query id ? n/a";
|
||||
}
|
||||
|
||||
|
||||
// Query caching
|
||||
//
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
// Warning: using directly the query string as the key to the hash array can FAIL if the string
|
||||
// is long and the differences are only near the end... so it's safer (but not bullet proof?)
|
||||
// to use a hash (like md5) of the string as the key !
|
||||
//
|
||||
// Example of two queries that were found as similar by the hash array:
|
||||
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2
|
||||
// and
|
||||
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2
|
||||
// the only difference is R instead or O at position 285 (TTR instead of TTO)...
|
||||
//
|
||||
if (array_key_exists($sOqlId, self::$m_aQueryStructCache))
|
||||
{
|
||||
// hit!
|
||||
|
||||
$oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId]));
|
||||
// Note: cloning is not enough because the subtree is made of objects
|
||||
}
|
||||
elseif (self::$m_bUseAPCCache)
|
||||
{
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
$sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId;
|
||||
$oKPI = new ExecutionKPI();
|
||||
$result = apc_fetch($sOqlAPCCacheId);
|
||||
$oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery);
|
||||
|
||||
if (is_object($result))
|
||||
{
|
||||
$oSQLQuery = $result;
|
||||
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($oSQLQuery))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSQLQuery = $oSearch->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
|
||||
$oSQLQuery->SetSourceOQL($sOqlQuery);
|
||||
$oKPI->ComputeStats('MakeSQLQuery', $sOqlQuery);
|
||||
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
if (self::$m_bUseAPCCache)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL);
|
||||
$oKPI->ComputeStats('Query APC (store)', $sOqlQuery);
|
||||
}
|
||||
|
||||
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone();
|
||||
}
|
||||
}
|
||||
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
$oSQLQuery->SetSourceOQL($this->ToOQL());
|
||||
|
||||
// Join to an additional table, if required...
|
||||
//
|
||||
@@ -624,7 +522,7 @@ abstract class DBSearch
|
||||
$aExtendedFields = array();
|
||||
foreach($aExtendedDataSpec['fields'] as $sColumn)
|
||||
{
|
||||
$sColRef = $oSearch->GetClassAlias().'_extdata_'.$sColumn;
|
||||
$sColRef = $this->GetClassAlias().'_extdata_'.$sColumn;
|
||||
$aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias);
|
||||
}
|
||||
$oSQLQueryExt = new SQLObjectQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* A union of DBObjectSearches
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,22 @@ class DBUnionSearch extends DBSearch
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
public function AllowAllData()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
public function IsAllDataAllowed()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
if ($oSearch->IsAllDataAllowed() === false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest common ancestor for each of the selected class
|
||||
*/
|
||||
@@ -187,6 +203,23 @@ class DBUnionSearch extends DBSearch
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
$bRet = false;
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$bRet = $oSearch->RenameAlias($sOldName, $sNewName) || $bRet;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function IsAny()
|
||||
{
|
||||
$bIsAny = true;
|
||||
@@ -282,19 +315,33 @@ class DBUnionSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
*/
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
|
||||
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode);
|
||||
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,12 +404,12 @@ class DBUnionSearch extends DBSearch
|
||||
/**
|
||||
* Overloads for query building
|
||||
*/
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null)
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
|
||||
{
|
||||
$aSubQueries = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams);
|
||||
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams, $bWithAllowAllFlag);
|
||||
}
|
||||
$sRet = implode(' UNION ', $aSubQueries);
|
||||
return $sRet;
|
||||
@@ -409,11 +456,11 @@ class DBUnionSearch extends DBSearch
|
||||
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
protected function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
protected function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
{
|
||||
if (count($this->aSearches) == 1)
|
||||
{
|
||||
return $this->aSearches[0]->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
}
|
||||
|
||||
$aSQLQueries = array();
|
||||
@@ -468,7 +515,13 @@ class DBUnionSearch extends DBSearch
|
||||
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->MakeSQLQuery($aQueryAttToLoad, false, $aModifierProperties, $aQueryGroupByExpr, $aSearchSelectedClasses);
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses);
|
||||
if (count($aSearchAliases) > 1)
|
||||
{
|
||||
// Necessary to make sure that selected columns will match throughout all the queries
|
||||
// (default order of selected fields depending on the order of JOINS)
|
||||
$oSubQuery->SortSelectedFields();
|
||||
}
|
||||
$aSQLQueries[] = $oSubQuery;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,8 +195,6 @@ class EMail
|
||||
|
||||
$aFailedRecipients = array();
|
||||
$this->m_oMessage->setMaxLineLength(0);
|
||||
IssueLog::Info(__METHOD__.' '.$this->m_oMessage->getMaxLineLength());
|
||||
IssueLog::Info(__METHOD__.' '.$this->m_oMessage->toString());
|
||||
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
|
||||
if ($iSent === 0)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?php
|
||||
// Copyright (C) 2016-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
/**
|
||||
* Base class for all possible implementations of HTML Sanitization
|
||||
*/
|
||||
@@ -138,7 +154,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
protected static $aTagsWhiteList = array(
|
||||
'html' => array(),
|
||||
'body' => array(),
|
||||
'a' => array('href', 'name', 'style'),
|
||||
'a' => array('href', 'name', 'style', 'target'),
|
||||
'p' => array('style'),
|
||||
'br' => array(),
|
||||
'span' => array('style'),
|
||||
@@ -159,7 +175,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'nav' => array('style'),
|
||||
'section' => array('style'),
|
||||
'code' => array('style'),
|
||||
'table' => array('style', 'width'),
|
||||
'table' => array('style', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing'),
|
||||
'thead' => array('style'),
|
||||
'tbody' => array('style'),
|
||||
'tr' => array('style'),
|
||||
@@ -183,21 +199,37 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'hr' => array('style'),
|
||||
'pre' => array(),
|
||||
'center' => array(),
|
||||
'caption' => array(),
|
||||
);
|
||||
|
||||
protected static $aAttrsWhiteList = array(
|
||||
'href' => '/^(http:|https:)/i',
|
||||
'src' => '/^(http:|https:|data:)/i',
|
||||
);
|
||||
|
||||
protected static $aStylesWhiteList = array(
|
||||
'background-color', 'color', 'float', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height',
|
||||
'background-color', 'color', 'float', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height', 'text-align',
|
||||
);
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!array_key_exists('href', self::$aAttrsWhiteList))
|
||||
{
|
||||
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>\s*</o:p>@', '<br>', $sHTML);
|
||||
|
||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
@@ -464,7 +464,7 @@ EOF
|
||||
oEditor.on( 'instanceReady', function() {
|
||||
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .editor_magnifier').length == 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url($sAppRootUrl/images/full-screen.png)"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url(\\'$sAppRootUrl/images/full-screen.png\\')"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox .editor_magnifier').on('click', function() {
|
||||
oEditor.execCommand('maximize');
|
||||
if ($(this).closest('.cke_maximized').length != 0)
|
||||
@@ -473,12 +473,15 @@ EOF
|
||||
}
|
||||
});
|
||||
}
|
||||
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
|
||||
var oData = JSON.parse(upload.xhr.responseText);
|
||||
this.replaceWith( '<img src="' + upload.url + '" ' +
|
||||
'width="' + oData.width + '" ' +
|
||||
'height="' + oData.height + '">' );
|
||||
}
|
||||
if (oEditor.widgets.registered.uploadimage)
|
||||
{
|
||||
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
|
||||
var oData = JSON.parse(upload.xhr.responseText);
|
||||
this.replaceWith( '<img src="' + upload.url + '" ' +
|
||||
'width="' + oData.width + '" ' +
|
||||
'height="' + oData.height + '">' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
EOF
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* File logging
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -69,81 +69,54 @@ class FileLog
|
||||
}
|
||||
}
|
||||
|
||||
class SetupLog
|
||||
abstract class LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
static::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Error($sText);
|
||||
}
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Info($sText);
|
||||
}
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IssueLog
|
||||
class SetupLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
class ToolsLog
|
||||
class IssueLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
class ToolsLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -22,6 +22,7 @@ require_once(APPROOT.'core/querymodifier.class.inc.php');
|
||||
require_once(APPROOT.'core/metamodelmodifier.inc.php');
|
||||
require_once(APPROOT.'core/computing.inc.php');
|
||||
require_once(APPROOT.'core/relationgraph.class.inc.php');
|
||||
require_once(APPROOT.'core/apc-compat.php');
|
||||
|
||||
/**
|
||||
* Metamodel
|
||||
@@ -1779,7 +1780,7 @@ abstract class MetaModel
|
||||
$oFriendlyName = new AttributeFriendlyName($sFriendlyNameAttCode, $sAttCode);
|
||||
$oFriendlyName->SetHostClass($sClass);
|
||||
self::$m_aAttribDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyName;
|
||||
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
|
||||
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = self::$m_aAttribOrigins[$sClass][$sAttCode];
|
||||
$oFriendlyNameFlt = new FilterFromAttribute($oFriendlyName);
|
||||
self::$m_aFilterDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyNameFlt;
|
||||
self::$m_aFilterOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -23,7 +23,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
|
||||
/**
|
||||
* Class to store a "case log" in a structured way, keeping track of its successive entries
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class ormCaseLog {
|
||||
@@ -388,8 +388,9 @@ class ormCaseLog {
|
||||
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
|
||||
{
|
||||
// Don't display the first element, that is still considered as editable
|
||||
$iPos = $aIndex[0]['separator_length'] + $aIndex[0]['text_length'];
|
||||
array_shift($aIndex);
|
||||
$aLastEntry = end($aIndex);
|
||||
$iPos = $aLastEntry['separator_length'] + $aLastEntry['text_length'];
|
||||
array_pop($aIndex);
|
||||
}
|
||||
for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
|
||||
{
|
||||
@@ -568,8 +569,6 @@ class ormCaseLog {
|
||||
|
||||
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
|
||||
|
||||
if (isset($oJson->user_id))
|
||||
{
|
||||
if (!UserRights::IsAdministrator())
|
||||
@@ -616,10 +615,16 @@ class ormCaseLog {
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: what is the default format ? text ?
|
||||
// The default is HTML
|
||||
$sFormat = 'html';
|
||||
}
|
||||
|
||||
|
||||
$sText = isset($oJson->message) ? $oJson->message : '';
|
||||
if ($sFormat == 'html')
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize($sText);
|
||||
}
|
||||
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
|
||||
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
@@ -639,12 +644,12 @@ class ormCaseLog {
|
||||
}
|
||||
|
||||
|
||||
public function GetModifiedEntry()
|
||||
public function GetModifiedEntry($sFormat = 'text')
|
||||
{
|
||||
$sModifiedEntry = '';
|
||||
if ($this->m_bModified)
|
||||
{
|
||||
$sModifiedEntry = $this->GetLatestEntry();
|
||||
$sModifiedEntry = $this->GetLatestEntry($sFormat);
|
||||
}
|
||||
return $sModifiedEntry;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class ormCustomFieldsValue
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return 'template...verb='.$sVerb.' sur "'.json_encode($this->aCurrentValues).'"';
|
||||
return $oHandler->GetForTemplate($this->aCurrentValues, $sVerb, $bLocalize);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,21 +115,29 @@ class ormDocument
|
||||
*/
|
||||
public function GetDownloadLink($sClass, $Id, $sAttCode)
|
||||
{
|
||||
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
|
||||
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an URL to display a document like an image
|
||||
* @return string
|
||||
*/
|
||||
public function GetDisplayURL($sClass, $Id, $sAttCode)
|
||||
{
|
||||
return utils::GetAbsoluteUrlAppRoot() . "pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an URL to download a document like an image (uses HTTP caching)
|
||||
* @return string
|
||||
*/
|
||||
*/
|
||||
public function GetDownloadURL($sClass, $Id, $sAttCode)
|
||||
{
|
||||
// Compute a signature to reset the cache anytime the data changes (this is acceptable if used only with icon files)
|
||||
$sSignature = md5($this->GetData());
|
||||
return utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
|
||||
return utils::GetAbsoluteUrlAppRoot() . "pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
|
||||
}
|
||||
|
||||
|
||||
public function IsPreviewAvailable()
|
||||
{
|
||||
$bRet = false;
|
||||
@@ -176,7 +184,7 @@ class ormDocument
|
||||
{
|
||||
$oPage->TrashUnexpectedOutput();
|
||||
$oPage->SetContentType($oDocument->GetMimeType());
|
||||
//$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
|
||||
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
|
||||
$oPage->add($oDocument->GetData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -22,7 +22,7 @@ require_once('backgroundprocess.inc.php');
|
||||
* ormStopWatch
|
||||
* encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -260,7 +260,7 @@ class ormStopWatch
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
protected function ComputeDeadline($oObject, $oAttDef, $iStartTime, $iDurationSec)
|
||||
protected function ComputeDeadline($oObject, $oAttDef, $iPercent, $iStartTime, $iDurationSec)
|
||||
{
|
||||
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
|
||||
if ($sWorkingTimeComputer == '')
|
||||
@@ -280,7 +280,7 @@ class ormStopWatch
|
||||
}
|
||||
// GetDeadline($oObject, $iDuration, DateTime $oStartDate)
|
||||
$oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
|
||||
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate);
|
||||
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate, $iPercent);
|
||||
$iRet = $oDeadline->format('U');
|
||||
return $iRet;
|
||||
}
|
||||
@@ -384,8 +384,8 @@ class ormStopWatch
|
||||
$sAttCode = $oAttDef->GetCode();
|
||||
WorkingTimeRecorder::Start($oObject, $iComputationRefTime, "ormStopWatch-Deadline-$iPercent-$sAttCode", 'Core:ExplainWTC:StopWatch-Deadline', array("Class:$sClass/Attribute:$sAttCode", $iPercent));
|
||||
}
|
||||
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
|
||||
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration);
|
||||
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $iPercent, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
|
||||
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $iPercent, $this->iStarted, $iThresholdDuration);
|
||||
|
||||
if (class_exists('WorkingTimeRecorder'))
|
||||
{
|
||||
@@ -494,6 +494,7 @@ class CheckStopWatchThresholds implements iBackgroundProcess
|
||||
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < '$sNow'";
|
||||
$oFilter = DBObjectSearch::FromOQL($sExpression);
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
$oSet->OptimizeColumnLoad(array($sAttCode));
|
||||
while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
|
||||
{
|
||||
$sClass = get_class($oObj);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,7 +18,7 @@
|
||||
/**
|
||||
* Data structures (i.e. PHP classes) to build and use relation graphs
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
@@ -106,7 +106,7 @@ class RelationRedundancyNode extends GraphNode
|
||||
/**
|
||||
* Make a normalized ID to ensure the uniqueness of such a node
|
||||
*/
|
||||
public static function MakeId($sRelCode, $sNeighbourId, $oSinkObject)
|
||||
public static function MakeId($sRelCode, $sNeighbourId, $oSourceObject, $oSinkObject)
|
||||
{
|
||||
return 'redundancy-'.$sRelCode.'-'.$sNeighbourId.'-'.get_class($oSinkObject).'::'.$oSinkObject->GetKey();
|
||||
}
|
||||
@@ -326,7 +326,7 @@ class RelationGraph extends SimpleGraph
|
||||
$this->AddRelatedObjects($sRelCode, false, $oSinkNode, $iMaxDepth, $bEnableRedundancy);
|
||||
//echo "<h5>After processing of {$oSinkNode->GetId()}</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
|
||||
}
|
||||
|
||||
|
||||
// Mark also the "context" nodes as reached and record the "root causes" for each node
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $oNode)
|
||||
@@ -407,11 +407,11 @@ class RelationGraph extends SimpleGraph
|
||||
do
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
|
||||
|
||||
$sObjectRef = RelationObjectNode::MakeId($oRelatedObj);
|
||||
$oRelatedNode = $this->GetNode($sObjectRef);
|
||||
if (is_null($oRelatedNode))
|
||||
{
|
||||
{
|
||||
$oRelatedNode = new RelationObjectNode($this, $oRelatedObj);
|
||||
}
|
||||
$oSourceNode = $bDown ? $oObjectNode : $oRelatedNode;
|
||||
@@ -449,8 +449,8 @@ class RelationGraph extends SimpleGraph
|
||||
$oObject = $oToNode->GetProperty('object');
|
||||
if ($this->IsRedundancyEnabled($sRelCode, $aQueryInfo, $oToNode))
|
||||
{
|
||||
|
||||
$sId = RelationRedundancyNode::MakeId($sRelCode, $aQueryInfo['sNeighbour'], $oToNode->GetProperty('object'));
|
||||
$sUniqueNeighbourId = $aQueryInfo['sDefinedInClass'].'-'.$aQueryInfo['sNeighbour'];
|
||||
$sId = RelationRedundancyNode::MakeId($sRelCode, $sUniqueNeighbourId, $oFromNode->GetProperty('object'), $oToNode->GetProperty('object'));
|
||||
|
||||
$oRedundancyNode = $this->GetNode($sId);
|
||||
if (is_null($oRedundancyNode))
|
||||
@@ -540,10 +540,13 @@ class RelationGraph extends SimpleGraph
|
||||
{
|
||||
if ($oAttDef->Get('relation_code') == $sRelCode)
|
||||
{
|
||||
if ($oAttDef->Get('neighbour_id') == $aQueryInfo['sNeighbour'])
|
||||
if ($oAttDef->Get('from_class') == $aQueryInfo['sFromClass'])
|
||||
{
|
||||
$oRet = $oAttDef;
|
||||
break;
|
||||
if ($oAttDef->Get('neighbour_id') == $aQueryInfo['sNeighbour'])
|
||||
{
|
||||
$oRet = $oAttDef;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,7 +18,7 @@
|
||||
/**
|
||||
* Data structures (i.e. PHP classes) to manage "graphs"
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
* Example:
|
||||
@@ -346,14 +346,15 @@ class SimpleGraph
|
||||
}
|
||||
unset($this->aNodes[$oNode->GetId()]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given node but preserves the connectivity of the graph
|
||||
* all "source" nodes are connected to all "sink" nodes
|
||||
* @param GraphNode $oNode
|
||||
* @param bool $bAllowLoopingEdge
|
||||
* @throws SimpleGraphException
|
||||
*/
|
||||
public function FilterNode(GraphNode $oNode)
|
||||
public function FilterNode(GraphNode $oNode, $bAllowLoopingEdge = false)
|
||||
{
|
||||
if (!array_key_exists($oNode->GetId(), $this->aNodes)) throw new SimpleGraphException('Cannot filter the node (id='.$oNode->GetId().') from the graph. The node was not found in the graph.');
|
||||
|
||||
@@ -362,13 +363,19 @@ class SimpleGraph
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$sSinkId = $oEdge->GetSinkNode()->GetId();
|
||||
$aSinkNodes[$sSinkId] = $oEdge->GetSinkNode();
|
||||
if ($sSinkId != $oNode->GetId())
|
||||
{
|
||||
$aSinkNodes[$sSinkId] = $oEdge->GetSinkNode();
|
||||
}
|
||||
$this->_RemoveEdge($oEdge);
|
||||
}
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$sSourceId = $oEdge->GetSourceNode()->GetId();
|
||||
$aSourceNodes[$sSourceId] = $oEdge->GetSourceNode();
|
||||
if ($sSourceId != $oNode->GetId())
|
||||
{
|
||||
$aSourceNodes[$sSourceId] = $oEdge->GetSourceNode();
|
||||
}
|
||||
$this->_RemoveEdge($oEdge);
|
||||
}
|
||||
unset($this->aNodes[$oNode->GetId()]);
|
||||
@@ -377,7 +384,10 @@ class SimpleGraph
|
||||
{
|
||||
foreach($aSinkNodes as $sSinkId => $oSinkNode)
|
||||
{
|
||||
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
|
||||
if ($bAllowLoopingEdge || ($oSourceNode->GetId() != $oSinkNode->GetId()))
|
||||
{
|
||||
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
$oP->p(" *\tno_localize: (optional) pass 1 to retrieve the raw (untranslated) values for enumerated fields. Default: 0.");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
|
||||
$oP->p(" *\tformatted_text: set to 1 to formatted text fields with their HTML markup, 0 to remove formatting. Default is 1 (= formatted text)");
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
@@ -51,12 +52,19 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:SpreadsheetOptions').'</legend>');
|
||||
$oP->add('<table>');
|
||||
$oP->add('<tr>');
|
||||
$oP->add('<td><input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label></td>');
|
||||
|
||||
|
||||
$oP->add('<td style="vertical-align:top">');
|
||||
$sChecked = (utils::ReadParam('formatted_text', 1) == 1) ? ' checked ' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
|
||||
$oP->add('<input type="hidden" name="formatted_text" value="0">'); // Trick to pass the zero value if the checkbox below is unchecked, since we want the default value to be "1"
|
||||
$oP->add('<input type="checkbox" id="spreadsheet_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="spreadsheet_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label><br/><br/>');
|
||||
$oP->add('<input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label>');
|
||||
$oP->add('</td>');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
|
||||
|
||||
$oP->add('<td>');
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
@@ -65,23 +73,23 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="spreadsheet_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="spreadsheet_date_time_format_custom" name="spreadsheet_date_format_radio" value="custom"'.$sCustomChecked.'><label for="spreadsheet_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
$oP->add('</td>');
|
||||
|
||||
|
||||
$oP->add('</tr>');
|
||||
$oP->add('</table>');
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
$('#spreadsheet_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_spreadsheet_options').on('preview_updated', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_date_time_format_default').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_date_time_format_custom').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); });
|
||||
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); FormatDatesInPreview('spreadsheet', 'spreadsheet'); }).on('keyup', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); FormatDatesInPreview('spreadsheet', 'spreadsheet'); }).on('keyup', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
EOF
|
||||
);
|
||||
);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
@@ -90,26 +98,27 @@ EOF
|
||||
public function ReadParameters()
|
||||
{
|
||||
parent::ReadParameters();
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 1, true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('spreadsheet_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
@@ -126,6 +135,7 @@ EOF
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
$bFormattedText = (array_key_exists('formatted_text', $this->aStatusInfo) ? $this->aStatusInfo['formatted_text'] : false);
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
@@ -147,6 +157,18 @@ EOF
|
||||
{
|
||||
$sRet = '';
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeText)
|
||||
{
|
||||
if ($bFormattedText)
|
||||
{
|
||||
// Replace paragraphs (<p...>...</p>, etc) by line breaks (<br/>) since Excel (pre-2016) splits the cells when there is a paragraph
|
||||
$sRet = static::HtmlToSpreadsheet($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = utils::HtmlToText($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
@@ -175,7 +197,7 @@ EOF
|
||||
{
|
||||
// Integration within MS-Excel web queries + HTTPS + IIS:
|
||||
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
|
||||
// Then the fix is to force the reset of header values Pragma and Cache-control
|
||||
// Then the fix is to force the reset of header values Pragma and Cache-control
|
||||
$oPage->add_header("Pragma:", true);
|
||||
$oPage->add_header("Cache-control:", true);
|
||||
}
|
||||
@@ -230,13 +252,14 @@ EOF
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
|
||||
$this->OptimizeColumnLoad($oSet);
|
||||
|
||||
|
||||
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
|
||||
$bFormattedText = (array_key_exists('formatted_text', $this->aStatusInfo) ? $this->aStatusInfo['formatted_text'] : false);
|
||||
// Date & time formats
|
||||
$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
$oTimeFormat = new DateTimeFormat($oDateTimeFormat->ToTimeFormat());
|
||||
|
||||
|
||||
$iCount = 0;
|
||||
$sData = '';
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
@@ -258,55 +281,69 @@ EOF
|
||||
$sData .= "<td x:str></td>";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sField = $oObj->GetKey();
|
||||
$sData .= "<td>$sField</td>";
|
||||
break;
|
||||
$sField = $oObj->GetKey();
|
||||
$sData .= "<td>$sField</td>";
|
||||
break;
|
||||
|
||||
default:
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime')
|
||||
{
|
||||
// Split the date and time in two columns
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
$sData .= "<td>$sTime</td>";
|
||||
}
|
||||
else if (get_class($oFinalAttDef) == 'AttributeDate')
|
||||
{
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
$sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
|
||||
// Trick for Excel: treat the content as text even if it begins with an equal sign
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sField = $oObj->GetAsHTML($sAttCode, $this->bLocalizeOutput);
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
if ($this->bLocalizeOutput)
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime')
|
||||
{
|
||||
$sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
|
||||
// Split the date and time in two columns
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
$sData .= "<td>$sTime</td>";
|
||||
}
|
||||
else if (get_class($oFinalAttDef) == 'AttributeDate')
|
||||
{
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
$sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
|
||||
// Trick for Excel: treat the content as text even if it begins with an equal sign
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeText)
|
||||
{
|
||||
if ($bFormattedText)
|
||||
{
|
||||
// Replace paragraphs (<p...>...</p>, etc) by line breaks (<br/>) since Excel (pre-2016) splits the cells when there is a paragraph
|
||||
$sField = static::HtmlToSpreadsheet($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert to plain text
|
||||
$sField = utils::HtmlToText($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sField = $oObj->GetAsHTML($sAttCode, $this->bLocalizeOutput);
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
if ($this->bLocalizeOutput)
|
||||
{
|
||||
$sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
$sData .= "<td>$sField</td>";
|
||||
}
|
||||
$sData .= "<td>$sField</td>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -354,4 +391,51 @@ EOF
|
||||
{
|
||||
return 'html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all markup displayed as line breaks (except <br> tags) since this
|
||||
* causes Excel (pre-2016) to generate extra lines in the table, thus breaking
|
||||
* the tabular disposition of the export
|
||||
* Note: Excel 2016 also refuses line breaks, so the only solution for this case is alas plain text
|
||||
* @param string $sHtml The HTML to cleanup
|
||||
* @return string The cleaned HTML
|
||||
*/
|
||||
public static function HtmlToSpreadsheet($sHtml)
|
||||
{
|
||||
if (trim(strip_tags($sHtml)) === '')
|
||||
{
|
||||
// Display this value as an empty cell in the table
|
||||
return ' ';
|
||||
}
|
||||
// The tags listed here are a subset of the whitelist defined in HTMLDOMSanitizer
|
||||
// Tags causing a visual "line break" in the displayed page (i.e. display: block) are to be replaced by a <span> followed by a <br/>
|
||||
// in order to preserve any inline style/attribute of the removed tag
|
||||
$aTagsToReplace = array(
|
||||
'pre', 'div', 'p', 'hr', 'center', 'h1', 'h2', 'h3', 'h4', 'li', 'fieldset', 'legend', 'nav', 'section', 'tr', 'caption',
|
||||
);
|
||||
// Tags to completely remove from the markup
|
||||
$aTagsToRemove = array(
|
||||
'table', 'thead', 'tbody', 'ul', 'ol', 'td', 'th',
|
||||
);
|
||||
|
||||
// Remove the englobing <div class="HTML" >...</div> to prevent an extra line break
|
||||
$sHtml = preg_replace('|^<div class="HTML" >(.*)</div>$|s', '$1', $sHtml); // Must use the "s" (. matches newline) modifier
|
||||
|
||||
foreach($aTagsToReplace as $sTag)
|
||||
{
|
||||
$sHtml = preg_replace("|<{$sTag} ?([^>]*)>|is", '<span $1>', $sHtml);
|
||||
$sHtml = preg_replace("|</{$sTag}>|i", '</span><br/>', $sHtml);
|
||||
}
|
||||
|
||||
foreach($aTagsToRemove as $sTag)
|
||||
{
|
||||
$sHtml = preg_replace("|<{$sTag} ?([^>]*)>|is", '', $sHtml);
|
||||
$sHtml = preg_replace("|</{$sTag}>|i", '', $sHtml);
|
||||
}
|
||||
|
||||
// Remove any trailing <br/>, if any, to prevent an extra line break
|
||||
$sHtml = preg_replace("|<br/>$|", '', $sHtml);
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* SQLObjectQuery
|
||||
* build a mySQL compatible SQL query
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -138,6 +138,11 @@ class SQLObjectQuery extends SQLQuery
|
||||
$this->m_aFields = $aExpressions;
|
||||
}
|
||||
|
||||
public function SortSelectedFields()
|
||||
{
|
||||
ksort($this->m_aFields);
|
||||
}
|
||||
|
||||
public function AddSelect($sAlias, $oExpression)
|
||||
{
|
||||
$this->m_aFields[$sAlias] = $oExpression;
|
||||
|
||||
@@ -245,7 +245,7 @@ abstract class User extends cmdbAbstractObject
|
||||
{
|
||||
if (is_null($this->oContactObject))
|
||||
{
|
||||
if ($this->Get('contactid') != 0)
|
||||
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
|
||||
{
|
||||
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
|
||||
}
|
||||
@@ -1045,7 +1045,12 @@ class UserRights
|
||||
{
|
||||
$oUser = self::$m_oUser;
|
||||
}
|
||||
if ($oUser->GetKey() == self::$m_oUser->GetKey())
|
||||
if ($oUser === null)
|
||||
{
|
||||
// Not logged in: no profile at all
|
||||
$aProfiles = array();
|
||||
}
|
||||
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
|
||||
{
|
||||
// Data about the current user can be found into the session data
|
||||
if (array_key_exists('profile_list', $_SESSION))
|
||||
|
||||
@@ -292,13 +292,13 @@ td a.mailto, td a.mailto:visited {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
padding-left: 20px;
|
||||
background: url(../images/mail.png?v=v2.3.0b) no-repeat left;
|
||||
background: url(../images/mail.png?v=v2.3.0) no-repeat left;
|
||||
}
|
||||
td a.mailto:hover {
|
||||
text-decoration: underline;
|
||||
color: #e87c1e;
|
||||
padding-left: 20px;
|
||||
background: url(../images/mail.png?v=v2.3.0b) no-repeat left;
|
||||
background: url(../images/mail.png?v=v2.3.0) no-repeat left;
|
||||
}
|
||||
a.small_action {
|
||||
font-family: Tahoma, Verdana, Arial, Helvetica;
|
||||
@@ -316,10 +316,10 @@ a.small_action {
|
||||
padding-left: 5px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
background: #e87c1e url(../images/actions_left.png?v=v2.3.0b) no-repeat left;
|
||||
background: #e87c1e url(../images/actions_left.png?v=v2.3.0) no-repeat left;
|
||||
}
|
||||
.actions_details span {
|
||||
background: url(../images/actions_right.png?v=v2.3.0b) no-repeat right;
|
||||
background: url(../images/actions_right.png?v=v2.3.0) no-repeat right;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding-top: 2px;
|
||||
@@ -493,7 +493,7 @@ div.actions_menu > ul {
|
||||
nowidth: 70px;
|
||||
padding-left: 5px;
|
||||
/* Nasty work-around for IE... en attendant mieux */
|
||||
background: #e87c1e url(../images/actions_left.png?v=v2.3.0b) no-repeat top left;
|
||||
background: #e87c1e url(../images/actions_left.png?v=v2.3.0) no-repeat top left;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -505,7 +505,7 @@ div.actions_menu > ul > li {
|
||||
height: 17px;
|
||||
padding-right: 16px;
|
||||
padding-left: 4px;
|
||||
background: url(../images/actions_right.png?v=v2.3.0b) no-repeat top right transparent;
|
||||
background: url(../images/actions_right.png?v=v2.3.0) no-repeat top right transparent;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
vertical-align: middle;
|
||||
@@ -648,7 +648,7 @@ td a.dp-choose-date, a.dp-choose-date, td a.dp-choose-date:hover, a.dp-choose-da
|
||||
display: block;
|
||||
text-indent: -2000px;
|
||||
overflow: hidden;
|
||||
background: url(../images/calendar.png?v=v2.3.0b) no-repeat;
|
||||
background: url(../images/calendar.png?v=v2.3.0) no-repeat;
|
||||
}
|
||||
td a.dp-choose-date.dp-disabled, a.dp-choose-date.dp-disabled {
|
||||
background-position: 0 -20px;
|
||||
@@ -739,19 +739,19 @@ div.HRDrawer {
|
||||
}
|
||||
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
|
||||
table.listResults tr.odd td.truncated, table.listResults tr td.truncated, .wizContainer table.listResults tr.odd td.truncated, .wizContainer table.listResults tr td.truncated {
|
||||
background: url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
|
||||
background: url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
|
||||
}
|
||||
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
|
||||
table.listResults tr.even td.truncated, .wizContainer table.listResults tr.even td.truncated {
|
||||
background: #f9f9f1 url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
|
||||
background: #f9f9f1 url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
|
||||
}
|
||||
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
|
||||
table.listResults tr.even td.hover.truncated, .wizContainer table.listResults tr.even td.hover.truncated {
|
||||
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
|
||||
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
|
||||
}
|
||||
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
|
||||
table.listResults tr.odd td.hover.truncated, table.listResults tr td.hover.truncated, .wizContainer table.listResults tr.odd td.hover.truncated, .wizContainer table.listResults tr td.hover.truncated {
|
||||
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
|
||||
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
|
||||
}
|
||||
table.listResults.truncated {
|
||||
border-bottom: 0;
|
||||
@@ -859,7 +859,7 @@ div#logo {
|
||||
div#logo div {
|
||||
height: 88px;
|
||||
width: 244px;
|
||||
background: url(../images/itop-logo-2.png?v=v2.3.0b) left no-repeat;
|
||||
background: url(../images/itop-logo-2.png?v=v2.3.0) left no-repeat;
|
||||
}
|
||||
#left-pane .ui-layout-north {
|
||||
overflow: hidden;
|
||||
@@ -908,7 +908,7 @@ div#logo div {
|
||||
}
|
||||
#global-search-image {
|
||||
vertical-align: middle;
|
||||
background: url(../images/search.png?v=v2.3.0b) center center no-repeat;
|
||||
background: url(../images/search.png?v=v2.3.0) center center no-repeat;
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 30px;
|
||||
@@ -937,7 +937,7 @@ span.ui-icon {
|
||||
margin: 0 2px;
|
||||
}
|
||||
.ui-layout-button-pin-down {
|
||||
background: url(../images/splitter-bkg.png?v=v2.3.0b) transparent;
|
||||
background: url(../images/splitter-bkg.png?v=v2.3.0) transparent;
|
||||
width: 16px;
|
||||
background-position: -144px -144px;
|
||||
}
|
||||
@@ -1148,7 +1148,7 @@ img.prev, img.first, img.next, img.last {
|
||||
}
|
||||
div.actions_button {
|
||||
float: right;
|
||||
background: #e87c1e url("../images/actions_left.png?v=v2.3.0b") no-repeat scroll left top;
|
||||
background: #e87c1e url("../images/actions_left.png?v=v2.3.0") no-repeat scroll left top;
|
||||
padding-left: 5px;
|
||||
margin-top: 0;
|
||||
margin-right: 10px;
|
||||
@@ -1156,7 +1156,7 @@ div.actions_button {
|
||||
vertical-align: middle;
|
||||
}
|
||||
div.actions_button a, .actions_button a:hover, .actions_button a:visited {
|
||||
background: #e87c1e url(../images/actions_bkg.png?v=v2.3.0b) no-repeat scroll right top;
|
||||
background: #e87c1e url(../images/actions_bkg.png?v=v2.3.0) no-repeat scroll right top;
|
||||
color: #fff;
|
||||
padding-right: 8px;
|
||||
cursor: pointer;
|
||||
@@ -1180,10 +1180,10 @@ select#org_id {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.dragHover {
|
||||
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0b);
|
||||
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0);
|
||||
}
|
||||
.edit_mode .dashlet {
|
||||
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0b);
|
||||
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0);
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
@@ -1215,7 +1215,7 @@ table.prop_table {
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
background: transparent url(../images/delete.png?v=v2.3.0b) no-repeat center;
|
||||
background: transparent url(../images/delete.png?v=v2.3.0) no-repeat center;
|
||||
}
|
||||
td.prop_value {
|
||||
text-align: left;
|
||||
@@ -1409,17 +1409,17 @@ a.summary, a.summary:hover {
|
||||
}
|
||||
.message_info {
|
||||
border: 1px solid #993;
|
||||
background: url(../images/info-mini.png?v=v2.3.0b) 1em 1em no-repeat #ffc;
|
||||
background: url(../images/info-mini.png?v=v2.3.0) 1em 1em no-repeat #ffc;
|
||||
padding-left: 3em;
|
||||
}
|
||||
.message_ok {
|
||||
border: 1px solid #393;
|
||||
background: url(../images/ok.png?v=v2.3.0b) 1em 1em no-repeat #cfc;
|
||||
background: url(../images/ok.png?v=v2.3.0) 1em 1em no-repeat #cfc;
|
||||
padding-left: 3em;
|
||||
}
|
||||
.message_error {
|
||||
border: 1px solid #933;
|
||||
background: url(../images/error.png?v=v2.3.0b) 1em 1em no-repeat #fcc;
|
||||
background: url(../images/error.png?v=v2.3.0) 1em 1em no-repeat #fcc;
|
||||
padding-left: 3em;
|
||||
}
|
||||
.fg-menu a img {
|
||||
@@ -1547,18 +1547,18 @@ div.explain-printable {
|
||||
}
|
||||
#hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter span {
|
||||
padding-left: 20px;
|
||||
background: url(../images/eye-open-555.png?v=v2.3.0b) 2px center no-repeat;
|
||||
background: url(../images/eye-open-555.png?v=v2.3.0) 2px center no-repeat;
|
||||
}
|
||||
#hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter.strikethrough span {
|
||||
text-decoration: line-through;
|
||||
background: url(../images/eye-closed-555.png?v=v2.3.0b) 2px center no-repeat;
|
||||
background: url(../images/eye-closed-555.png?v=v2.3.0) 2px center no-repeat;
|
||||
}
|
||||
.printable-version legend {
|
||||
padding-left: 26px;
|
||||
background: #1c94c4 url(../images/eye-open-fff.png?v=v2.3.0b) 8px center no-repeat;
|
||||
background: #1c94c4 url(../images/eye-open-fff.png?v=v2.3.0) 8px center no-repeat;
|
||||
}
|
||||
.printable-version .strikethrough legend {
|
||||
background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.3.0b) 8px center no-repeat;
|
||||
background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.3.0) 8px center no-repeat;
|
||||
}
|
||||
.printable-version fieldset.strikethrough span {
|
||||
display: none;
|
||||
@@ -1577,7 +1577,7 @@ span.refresh-button {
|
||||
width: 21px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
background: transparent url(../images/refresh-fff.png?v=v2.3.0b) left center no-repeat;
|
||||
background: transparent url(../images/refresh-fff.png?v=v2.3.0) left center no-repeat;
|
||||
}
|
||||
.case-log-history-entry {
|
||||
display: block;
|
||||
@@ -1705,7 +1705,7 @@ span.refresh-button {
|
||||
#itop-breadcrumb .breadcrumb-item a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-image: url(../images/breadcrumb-separator.png?v=v2.3.0b);
|
||||
background-image: url(../images/breadcrumb-separator.png?v=v2.3.0);
|
||||
background-repeat: no-repeat;
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
@@ -1746,3 +1746,20 @@ span.refresh-button {
|
||||
.mfp-close {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
.qtip-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
.qtip-content p:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.qtip-content p:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.synchro-source-title {
|
||||
font-weight: bolder;
|
||||
}
|
||||
.synchro-source-description {
|
||||
font-size: smaller;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
@@ -1869,3 +1869,23 @@ span.refresh-button {
|
||||
.mfp-close {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.qtip-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
.qtip-content p:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.qtip-content p:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.synchro-source {
|
||||
}
|
||||
.synchro-source-title {
|
||||
font-weight: bolder;
|
||||
}
|
||||
.synchro-source-description {
|
||||
font-size: smaller;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
@@ -244,6 +244,7 @@ class BackupExec implements iScheduledProcess
|
||||
{
|
||||
break;
|
||||
}
|
||||
$iNextPos = false; // necessary on sundays
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -552,7 +552,7 @@
|
||||
<static>false</static>
|
||||
<access>public</access>
|
||||
<type>Overload-DBObject</type>
|
||||
<code><![CDATA[ public function DBDeleteSingleObject(&$oDeletionPlan)
|
||||
<code><![CDATA[ public function DBDeleteSingleObject()
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
@@ -1582,7 +1582,8 @@
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode($sSubClass);
|
||||
if ($sStateAttCode != '')
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL("SELECT $sSubClass AS t JOIN $sLnkClass AS lnk ON lnk.$sExtKeyToRemote = t.id WHERE $sExtKeyToMe = :myself AND $sStateAttCode NOT IN ('rejected', 'resolved', 'closed')", array('myself' => $this->GetKey()));
|
||||
// Todo: base the search condition on operational_status = 'ongoing' for a more flexible behavior
|
||||
$oSearch = DBSearch::FromOQL("SELECT $sSubClass AS t JOIN $sLnkClass AS lnk ON lnk.$sExtKeyToRemote = t.id WHERE lnk.$sExtKeyToMe = :myself AND t.$sStateAttCode NOT IN ('rejected', 'resolved', 'closed')", array('myself' => $this->GetKey()));
|
||||
$aSearches[$sSubClass] = $oSearch;
|
||||
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
@@ -105,7 +105,7 @@ try
|
||||
{
|
||||
$oP->add("<div class=\"header_message message_info\">Sorry, iTop is in <b>demonstration mode</b>: the configuration file cannot be edited.</div>");
|
||||
}
|
||||
if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled')
|
||||
else if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled')
|
||||
{
|
||||
$oP->add("<div class=\"header_message message_info\">iTop interactive edition of the configuration as been disabled. See <tt>'config_editor' => 'disabled'</tt> in the configuration file.</div>");
|
||||
}
|
||||
|
||||
@@ -1229,7 +1229,7 @@
|
||||
if (!MetaModel::IsValidClass('UserRequest')) return true; // Do nothing
|
||||
|
||||
$oLog = $this->Get('public_log');
|
||||
$sLogPublic = $oLog->GetModifiedEntry();
|
||||
$sLogPublic = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPublic != '')
|
||||
{
|
||||
$sOQL = "SELECT UserRequest WHERE parent_incident_id=:ticket";
|
||||
@@ -1247,7 +1247,7 @@
|
||||
|
||||
}
|
||||
$oLog = $this->Get('private_log');
|
||||
$sLogPrivate = $oLog->GetModifiedEntry();
|
||||
$sLogPrivate = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPrivate != '')
|
||||
{
|
||||
$sOQL = "SELECT UserRequest WHERE parent_incident_id=:ticket";
|
||||
@@ -1274,7 +1274,7 @@
|
||||
<code><![CDATA[ public function UpdateChildIncidentLog()
|
||||
{
|
||||
$oLog = $this->Get('public_log');
|
||||
$sLogPublic = $oLog->GetModifiedEntry();
|
||||
$sLogPublic = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPublic != '')
|
||||
{
|
||||
$sOQL = "SELECT Incident WHERE parent_incident_id=:ticket";
|
||||
@@ -1292,7 +1292,7 @@
|
||||
|
||||
}
|
||||
$oLog = $this->Get('private_log');
|
||||
$sLogPrivate = $oLog->GetModifiedEntry();
|
||||
$sLogPrivate = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPrivate != '')
|
||||
{
|
||||
$sOQL = "SELECT Incident WHERE parent_incident_id=:ticket";
|
||||
|
||||
@@ -622,7 +622,7 @@
|
||||
<parent_att/>
|
||||
<name_att/>
|
||||
<tooltip_att/>
|
||||
<title>Catégories</title>
|
||||
<title>Class:FAQCategory</title>
|
||||
<actions/>
|
||||
<levels>
|
||||
<level id="1">
|
||||
@@ -630,7 +630,7 @@
|
||||
<parent_att>category_id</parent_att>
|
||||
<name_att>title</name_att>
|
||||
<tooltip_att>summary</tooltip_att>
|
||||
<title>FAQs</title>
|
||||
<title>Class:FAQ</title>
|
||||
<fields>
|
||||
<field id="error_code"/>
|
||||
<field id="key_words">
|
||||
|
||||
@@ -125,5 +125,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:lnkDocumentToError/Attribute:error_name+' => '',
|
||||
'Class:FAQ/Attribute:category_name' => 'Kategoriename',
|
||||
'Class:FAQ/Attribute:category_name+' => '',
|
||||
'Brick:Portal:FAQ:Menu' => 'FAQ',
|
||||
'Brick:Portal:FAQ:Title' => 'Oft gestellte Fragen (FAQs)',
|
||||
'Brick:Portal:FAQ:Title+' => '<p>In Eile?</p><p>Sehen Sie sich die meistgestellten Fragen an (FAQs) und finden Sie (eventuell) die Antwort direkt dort.</p>',
|
||||
));
|
||||
?>
|
||||
|
||||
@@ -187,5 +187,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Menu:FAQ' => 'Preguntas y Respuestas Frecuentes',
|
||||
'Menu:FAQ+' => 'Preguntas y Respuestas Frecuentes',
|
||||
|
||||
'Brick:Portal:FAQ:Menu' => 'FAQ',
|
||||
'Brick:Portal:FAQ:Title' => 'Preguntas y Respuestas Frecuentes',
|
||||
'Brick:Portal:FAQ:Title+' => '<p>¿En una prisa?</p><p>Vea la lista de las preguntas más comunes y encontrará (tal vez) la respuesta inmediata a sus necesidades.</p>',
|
||||
));
|
||||
?>
|
||||
|
||||
@@ -45,8 +45,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Error:HTTP:GetHelp' => 'Kontaktujte prosím administrátora, pokud problém přetrvá.',
|
||||
'Error:XHR:Fail' => 'Data se nepodařilo načíst, kontaktujte prosím administrátora.',
|
||||
'Portal:Datatables:Language:Processing' => 'Počkejte prosím',
|
||||
'Portal:Datatables:Language:Search' => 'filtr :',
|
||||
'Portal:Datatables:Language:LengthMenu' => 'Zobrazit _MENU_ položek na stránku',
|
||||
'Portal:Datatables:Language:Search' => 'Filtr :',
|
||||
'Portal:Datatables:Language:LengthMenu' => 'Zobrazit _MENU_ položek na stránku',
|
||||
'Portal:Datatables:Language:ZeroRecords' => 'Žádný výsledek',
|
||||
'Portal:Datatables:Language:Info' => 'Stránka _PAGE_ z _PAGES_',
|
||||
'Portal:Datatables:Language:InfoEmpty' => 'Žádná informace',
|
||||
@@ -61,6 +61,9 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Portal:Datatables:Language:Sort:Descending' => 'řadit sestupně',
|
||||
'Portal:Autocomplete:NoResult' => 'Žádná data',
|
||||
'Portal:Attachments:DropZone:Message' => 'Přesuňte soubory myší pro vložení',
|
||||
'Portal:File:None' => 'No file',
|
||||
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
117
datamodels/2.x/itop-portal-base/de.dict.itop-portal-base.php
Normal file
117
datamodels/2.x/itop-portal-base/de.dict.itop-portal-base.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2016 ITOMIG GmbH
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
// Portal
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Page:DefaultTitle' => 'iTop - Benutzer Portal',
|
||||
'Page:PleaseWait' => 'Bitte warten...',
|
||||
'Page:Home' => 'Start',
|
||||
'Page:GoPortalHome' => 'Startseite',
|
||||
'Page:GoPreviousPage' => 'vorherige Seite',
|
||||
'Portal:Button:Submit' => 'Abschicken',
|
||||
'Portal:Button:Cancel' => 'Zurück',
|
||||
'Portal:Button:Close' => 'Schließen',
|
||||
'Portal:Button:Add' => 'Hinzu',
|
||||
'Portal:Button:Remove' => 'Wegnehmen',
|
||||
'Portal:Button:Delete' => 'Löschen',
|
||||
'Portal:EnvironmentBanner:Title' => 'Sie sind im Moment im <strong>%1$s</strong> Modus',
|
||||
'Portal:EnvironmentBanner:GoToProduction' => 'Zurück zum PRODUCTION Modus',
|
||||
'Error:HTTP:404' => 'Seite nicht gefunden.',
|
||||
'Error:HTTP:500' => 'Oops! Es ist ein Fehler aufgetreten.',
|
||||
'Error:HTTP:GetHelp' => 'Bitte kontaktieren Sie Ihren iTop administrator falls das Problem öfter auftaucht.',
|
||||
'Error:XHR:Fail' => 'Konnte Daten nicht laden, bitte kontaktieren Sie Ihren iTop administrator',
|
||||
'Portal:Datatables:Language:Processing' => 'Bitte warten...',
|
||||
'Portal:Datatables:Language:Search' => 'Filter :',
|
||||
'Portal:Datatables:Language:LengthMenu' => 'Anzahl _MENU_ Einträge pro Seite',
|
||||
'Portal:Datatables:Language:ZeroRecords' => 'Keine Resultate',
|
||||
'Portal:Datatables:Language:Info' => 'Seite _PAGE_ von _PAGES_',
|
||||
'Portal:Datatables:Language:InfoEmpty' => 'Keine Information',
|
||||
'Portal:Datatables:Language:InfoFiltered' => 'gefiltert aus _MAX_ Resultaten',
|
||||
'Portal:Datatables:Language:EmptyTable' => 'Keine Daten in dieser Tabelle verfügbar',
|
||||
'Portal:Datatables:Language:DisplayLength:All' => 'Alle',
|
||||
'Portal:Datatables:Language:Paginate:First' => '1.Seite',
|
||||
'Portal:Datatables:Language:Paginate:Previous' => 'vorherige',
|
||||
'Portal:Datatables:Language:Paginate:Next' => 'Nächste',
|
||||
'Portal:Datatables:Language:Paginate:Last' => 'Letzte',
|
||||
'Portal:Datatables:Language:Sort:Ascending' => 'wähle aufsteigende Sortierung',
|
||||
'Portal:Datatables:Language:Sort:Descending' => 'wähle abfallende Sortierung',
|
||||
'Portal:Autocomplete:NoResult' => 'keine Daten',
|
||||
'Portal:Attachments:DropZone:Message' => 'Legen Sie hier Ihre Files ab, um sie als Anhang dem Ticket hinzuzufügen',
|
||||
'Portal:File:None' => 'Kein File vorhanden',
|
||||
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Öffnen</a> / <a href="%4$s" class="file_download_link">Download</a>',
|
||||
));
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Brick:Portal:UserProfile:Name' => 'Benutzer Profil',
|
||||
'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil' => 'Mein Profil',
|
||||
'Brick:Portal:UserProfile:Navigation:Dropdown:Logout' => 'Abmelden',
|
||||
'Brick:Portal:UserProfile:Password:Title' => 'Passwort',
|
||||
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Passwort wählen',
|
||||
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Passwort bestätigen',
|
||||
'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator' => 'Um das Password zu ändern, kontaktieren Sie bitte Ihren iTop Administrator',
|
||||
'Brick:Portal:UserProfile:Password:CantChangeForUnknownReason' => 'Kann das Passwort nicht ändern - bitte kontaktieren Sie Ihren iTop Administrator',
|
||||
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Persönliche Informationen',
|
||||
'Brick:Portal:UserProfile:Photo:Title' => 'Foto',
|
||||
));
|
||||
|
||||
// BrowseBrick brick
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Brick:Portal:Browse:Name' => 'List durchgehen',
|
||||
'Brick:Portal:Browse:Mode:List' => 'Liste',
|
||||
'Brick:Portal:Browse:Mode:Tree' => 'Baum',
|
||||
'Brick:Portal:Browse:Action:Drilldown' => 'Drilldown',
|
||||
'Brick:Portal:Browse:Action:View' => 'Details',
|
||||
'Brick:Portal:Browse:Action:Edit' => 'Editieren',
|
||||
'Brick:Portal:Browse:Action:Create' => 'Erstellen',
|
||||
'Brick:Portal:Browse:Action:CreateObjectFromThis' => 'Neue %1$s',
|
||||
'Brick:Portal:Browse:Tree:ExpandAll' => 'Alle expandieren',
|
||||
'Brick:Portal:Browse:Tree:CollapseAll' => 'Alle kollabieren',
|
||||
'Brick:Portal:Browse:Filter:NoData' => 'Kein Eintrag',
|
||||
));
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Brick:Portal:Manage:Name' => 'Einträge managen',
|
||||
'Brick:Portal:Manage:Table:NoData' => 'Kein Eintrag.',
|
||||
));
|
||||
|
||||
// ObjectBrick brick
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Brick:Portal:Object:Name' => 'Object',
|
||||
'Brick:Portal:Object:Form:Create:Title' => 'Neue %1$s',
|
||||
'Brick:Portal:Object:Form:Edit:Title' => 'Wird aktualisiert %2$s (%1$s)',
|
||||
'Brick:Portal:Object:Form:View:Title' => '%1$s : %2$s',
|
||||
'Brick:Portal:Object:Form:Stimulus:Title' => 'Bitte die folgendenen Informationen ausfüllen:',
|
||||
'Brick:Portal:Object:Form:Message:Saved' => 'Saved',
|
||||
'Brick:Portal:Object:Search:Regular:Title' => 'Select %1$s (%2$s)',
|
||||
'Brick:Portal:Object:Search:Hierarchy:Title' => 'Select %1$s (%2$s)',
|
||||
));
|
||||
|
||||
// CreateBrick brick
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Brick:Portal:Create:Name' => 'Schnelles Erstellen',
|
||||
));
|
||||
?>
|
||||
@@ -41,7 +41,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Error:HTTP:GetHelp' => 'Please contact your iTop administrator if the problem keeps happening.',
|
||||
'Error:XHR:Fail' => 'Could not load data, please contact your iTop administrator',
|
||||
'Portal:Datatables:Language:Processing' => 'Please wait...',
|
||||
'Portal:Datatables:Language:Search' => 'filter :',
|
||||
'Portal:Datatables:Language:Search' => 'Filter:',
|
||||
'Portal:Datatables:Language:LengthMenu' => 'Display _MENU_ items per page',
|
||||
'Portal:Datatables:Language:ZeroRecords' => 'No result',
|
||||
'Portal:Datatables:Language:Info' => 'Page _PAGE_ of _PAGES_',
|
||||
@@ -57,6 +57,9 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Portal:Datatables:Language:Sort:Descending' => 'enable for a descending sort',
|
||||
'Portal:Autocomplete:NoResult' => 'No data',
|
||||
'Portal:Attachments:DropZone:Message' => 'Drop your files to add them as attachments',
|
||||
'Portal:File:None' => 'No file',
|
||||
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
116
datamodels/2.x/itop-portal-base/es_cr.dict.itop-portal-base.php
Normal file
116
datamodels/2.x/itop-portal-base/es_cr.dict.itop-portal-base.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
// Portal
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Page:DefaultTitle' => 'iTop User portal',
|
||||
'Page:PleaseWait' => 'Please wait...',
|
||||
'Page:Home' => 'Bienvenido',
|
||||
'Page:GoPortalHome' => 'Regresar a bienvenida',
|
||||
'Page:GoPreviousPage' => 'página anterior',
|
||||
'Portal:Button:Submit' => 'Enviar',
|
||||
'Portal:Button:Cancel' => 'Cancelar',
|
||||
'Portal:Button:Close' => 'Cerrar',
|
||||
'Portal:Button:Add' => 'Añadir',
|
||||
'Portal:Button:Remove' => 'Eliminar',
|
||||
'Portal:Button:Delete' => 'Borrar',
|
||||
'Error:HTTP:404' => 'Página no encontrada',
|
||||
'Error:HTTP:500' => '¡Vaya! Ha ocurrido un error.',
|
||||
'Error:HTTP:GetHelp' => 'Póngase en contacto con el administrador de iTop si el problema persiste.',
|
||||
'Error:XHR:Fail' => 'No se pudieron cargar datos, póngase en contacto con su administrador de iTop',
|
||||
'Portal:Datatables:Language:Processing' => 'Por favor esperar...',
|
||||
'Portal:Datatables:Language:Search' => 'Filtrar:',
|
||||
'Portal:Datatables:Language:LengthMenu' => 'Mostrar _MENU_ elementos por página',
|
||||
'Portal:Datatables:Language:ZeroRecords' => 'Sin resultados',
|
||||
'Portal:Datatables:Language:Info' => 'Página _PAGE_ de _PAGES_',
|
||||
'Portal:Datatables:Language:InfoEmpty' => 'Sin información',
|
||||
'Portal:Datatables:Language:InfoFiltered' => 'Filtrada de _MAX_ elementos',
|
||||
'Portal:Datatables:Language:EmptyTable' => 'No hay datos disponibles en esta tabla',
|
||||
'Portal:Datatables:Language:DisplayLength:All' => 'Todas',
|
||||
'Portal:Datatables:Language:Paginate:First' => 'primero',
|
||||
'Portal:Datatables:Language:Paginate:Previous' => 'Anterior',
|
||||
'Portal:Datatables:Language:Paginate:Next' => 'Siguiente',
|
||||
'Portal:Datatables:Language:Paginate:Last' => 'Último',
|
||||
'Portal:Datatables:Language:Sort:Ascending' => 'Habilitar para un orden ascendente',
|
||||
'Portal:Datatables:Language:Sort:Descending' => 'Habilitar para un tipo descendente',
|
||||
'Portal:Autocomplete:NoResult' => 'Sin datos',
|
||||
'Portal:Attachments:DropZone:Message' => 'Agrega tus archivos para agregarlos como documentos adjuntos',
|
||||
'Portal:File:None' => 'No file',
|
||||
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Brick:Portal:UserProfile:Name' => 'Perfil del usuario',
|
||||
'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil' => 'Mi perfil',
|
||||
'Brick:Portal:UserProfile:Navigation:Dropdown:Logout' => 'Desconectarse',
|
||||
'Brick:Portal:UserProfile:Password:Title' => 'Contraseña',
|
||||
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Elegir una contraseña',
|
||||
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Confirmar contraseña',
|
||||
'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator' => 'Para cambiar su contraseña, póngase en contacto con su administrador de iTop',
|
||||
'Brick:Portal:UserProfile:Password:CantChangeForUnknownReason' => 'No se puede cambiar la contraseña, póngase en contacto con el administrador de iTop',
|
||||
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Informaciones personales',
|
||||
'Brick:Portal:UserProfile:Photo:Title' => 'Foto',
|
||||
));
|
||||
|
||||
// BrowseBrick brick
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Brick:Portal:Browse:Name' => 'Buscar en todos los elementos',
|
||||
'Brick:Portal:Browse:Mode:List' => 'Lista',
|
||||
'Brick:Portal:Browse:Mode:Tree' => 'Árbol',
|
||||
'Brick:Portal:Browse:Action:Drilldown' => 'Desglose',
|
||||
'Brick:Portal:Browse:Action:View' => 'Detalles',
|
||||
'Brick:Portal:Browse:Action:Edit' => 'Editar',
|
||||
'Brick:Portal:Browse:Action:Create' => 'Crear',
|
||||
'Brick:Portal:Browse:Action:CreateObjectFromThis' => 'Nuevo %1$s',
|
||||
'Brick:Portal:Browse:Tree:ExpandAll' => 'Expandir todo',
|
||||
'Brick:Portal:Browse:Tree:CollapseAll' => 'Desplegar todo',
|
||||
'Brick:Portal:Browse:Filter:NoData' => 'Sin objeto',
|
||||
));
|
||||
|
||||
// ManageBrick brick
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Brick:Portal:Manage:Name' => 'Administrar elementos',
|
||||
'Brick:Portal:Manage:Table:NoData' => 'Sin objeto.',
|
||||
));
|
||||
|
||||
// ObjectBrick brick
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Brick:Portal:Object:Name' => 'Object',
|
||||
'Brick:Portal:Object:Form:Create:Title' => 'New %1$s',
|
||||
'Brick:Portal:Object:Form:Edit:Title' => 'Updating %2$s (%1$s)',
|
||||
'Brick:Portal:Object:Form:View:Title' => '%1$s : %2$s',
|
||||
'Brick:Portal:Object:Form:Stimulus:Title' => 'Please, fill the following informations:',
|
||||
'Brick:Portal:Object:Form:Message:Saved' => 'Saved',
|
||||
'Brick:Portal:Object:Search:Regular:Title' => 'Select %1$s (%2$s)',
|
||||
'Brick:Portal:Object:Search:Hierarchy:Title' => 'Select %1$s (%2$s)',
|
||||
));
|
||||
|
||||
// CreateBrick brick
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Brick:Portal:Create:Name' => 'Creación rápida',
|
||||
));
|
||||
?>
|
||||
@@ -57,6 +57,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Portal:Datatables:Language:Sort:Descending' => 'activer pour trier la colonne par ordre décroissant',
|
||||
'Portal:Autocomplete:NoResult' => 'Aucun résultat',
|
||||
'Portal:Attachments:DropZone:Message' => 'Déposez vos fichiers pour les ajouter en pièces jointes',
|
||||
'Portal:File:None' => 'Aucun fichier',
|
||||
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Ouvrir</a> / <a href="%4$s" class="file_download_link">Télécharger</a>',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal-base/1.0.0', array(
|
||||
'itop-portal-base/1.0.1', array(
|
||||
// Identification
|
||||
'label' => 'Portal Development Library',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -41,6 +41,7 @@ use \Combodo\iTop\Portal\Brick\BrowseBrick;
|
||||
class BrowseBrickController extends BrickController
|
||||
{
|
||||
const LEVEL_SEPARATOR = '-';
|
||||
public static $aOptionalAttributes = array('tooltip_att');
|
||||
|
||||
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sBrowseMode = null, $sDataLoading = null)
|
||||
{
|
||||
@@ -93,11 +94,19 @@ class BrowseBrickController extends BrickController
|
||||
{
|
||||
// Retrieving class alias for all depth
|
||||
array_unshift($aLevelsClasses, $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetClassAlias());
|
||||
|
||||
|
||||
// Joining queries from bottom-up
|
||||
if ($i < $iLoopMax)
|
||||
{
|
||||
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att']);
|
||||
$aRealiasingMap = array();
|
||||
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att'], TREE_OPERATOR_EQUALS, $aRealiasingMap);
|
||||
foreach ($aLevelsPropertiesKeys as $sLevelAlias)
|
||||
{
|
||||
if (array_key_exists($sLevelAlias, $aRealiasingMap))
|
||||
{
|
||||
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->RenameAlias($aRealiasingMap[$sLevelAlias], $sLevelAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adding search clause
|
||||
@@ -168,7 +177,7 @@ class BrowseBrickController extends BrickController
|
||||
}
|
||||
}
|
||||
$oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search'];
|
||||
|
||||
|
||||
// Testing appropriate data loading mode if we are in auto
|
||||
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO)
|
||||
{
|
||||
@@ -259,7 +268,41 @@ class BrowseBrickController extends BrickController
|
||||
{
|
||||
$oSet = new DBObjectSet($oQuery);
|
||||
}
|
||||
|
||||
|
||||
// Optimizing the ObjectSet to retrieve only necessary columns
|
||||
$aColumnAttrs = array();
|
||||
foreach ($oSet->GetFilter()->GetSelectedClasses() as $sTmpClassAlias => $sTmpClassName)
|
||||
{
|
||||
if (isset($aLevelsProperties[$sTmpClassAlias]))
|
||||
{
|
||||
$aTmpLevelProperties = $aLevelsProperties[$sTmpClassAlias];
|
||||
// Mandatory main attribute
|
||||
$aTmpColumnAttrs = array($aTmpLevelProperties['name_att']);
|
||||
// Optionnal attributes, only if in list mode
|
||||
if ($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_LIST)
|
||||
{
|
||||
foreach ($aTmpLevelProperties['fields'] as $aTmpField)
|
||||
{
|
||||
$aTmpColumnAttrs[] = $aTmpField['code'];
|
||||
}
|
||||
}
|
||||
// Optional attributes
|
||||
foreach(static::$aOptionalAttributes as $sOptionalAttribute)
|
||||
{
|
||||
if($aTmpLevelProperties[$sOptionalAttribute] !== null)
|
||||
{
|
||||
$aTmpColumnAttrs[] = $aTmpLevelProperties[$sOptionalAttribute];
|
||||
}
|
||||
}
|
||||
|
||||
$aColumnAttrs[$sTmpClassAlias] = $aTmpColumnAttrs;
|
||||
}
|
||||
}
|
||||
$oSet->OptimizeColumnLoad($aColumnAttrs);
|
||||
|
||||
// Sorting objects through defined order (in DM)
|
||||
$oSet->SetOrderByClasses();
|
||||
|
||||
// Retrieving results and organizing them for templating
|
||||
$aItems = array();
|
||||
while ($aCurrentRow = $oSet->FetchAssoc())
|
||||
@@ -338,10 +381,16 @@ class BrowseBrickController extends BrickController
|
||||
{
|
||||
$sCurrentLevelAlias = $sLevelAliasPrefix . static::LEVEL_SEPARATOR . $aLevel['id'];
|
||||
$oSearch = DBSearch::CloneWithAlias(DBSearch::FromOQL($aLevel['oql']), $sCurrentLevelAlias);
|
||||
|
||||
|
||||
// Restricting to the allowed scope
|
||||
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oSearch->GetClass(), UR_ACTION_READ);
|
||||
$oSearch = ($oScopeSearch !== null) ? $oSearch->Intersect($oScopeSearch) : null;
|
||||
// - Allowing all data if necessary
|
||||
if ($oScopeSearch->IsAllDataAllowed())
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
|
||||
if ($oSearch !== null)
|
||||
{
|
||||
$aLevelsProperties[$sCurrentLevelAlias] = array(
|
||||
@@ -561,7 +610,15 @@ class BrowseBrickController extends BrickController
|
||||
$aRow[$key]['fields'] = array();
|
||||
foreach ($aLevelsProperties[$key]['fields'] as $aField)
|
||||
{
|
||||
$aRow[$key]['fields'][$aField['code']] = $value->Get($aField['code']);
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($value), $aField['code']);
|
||||
if ($oAttDef->GetEditClass() === 'Duration')
|
||||
{
|
||||
$aRow[$key]['fields'][$aField['code']] = $oAttDef->GetAsHTML($value->Get($aField['code']));
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRow[$key]['fields'][$aField['code']] = $oAttDef->GetValueLabel($value->Get($aField['code']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,5 +687,3 @@ class BrowseBrickController extends BrickController
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -37,8 +37,17 @@ class DefaultController
|
||||
// Doing it only for tile visible on home page to avoid unnecessary rendering
|
||||
if (($oBrick->GetVisibleHome() === true) && ($oBrick->GetTileControllerAction() !== null))
|
||||
{
|
||||
$oController = new \Combodo\iTop\Portal\Controller\ManageBrickController($oRequest, $oApp);
|
||||
$aData['aTilesRendering'][$oBrick->GetId()] = $oController->HomeAction($oRequest, $oApp);
|
||||
$aControllerActionParts = explode('::', $oBrick->GetTileControllerAction());
|
||||
if (count($aControllerActionParts) !== 2)
|
||||
{
|
||||
$oApp->abort(500, 'Tile controller action must be of form "\Namespace\ControllerClass::FunctionName" for brick "' . $oBrick->GetId() . '"');
|
||||
}
|
||||
|
||||
$sControllerName = $aControllerActionParts[0];
|
||||
$sControllerAction = $aControllerActionParts[1];
|
||||
|
||||
$oController = new $sControllerName($oRequest, $oApp, $oBrick->GetId());
|
||||
$aData['aTilesRendering'][$oBrick->GetId()] = $oController->$sControllerAction($oRequest, $oApp, $oBrick->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ use \MetaModel;
|
||||
use \AttributeDefinition;
|
||||
use \AttributeDate;
|
||||
use \AttributeDateTime;
|
||||
use \AttributeDuration;
|
||||
use \AttributeSubItem;
|
||||
use \DBSearch;
|
||||
use \DBObjectSearch;
|
||||
use \DBObjectSet;
|
||||
@@ -59,6 +61,15 @@ class ManageBrickController extends BrickController
|
||||
// Getting search value
|
||||
$sSearchValue = $oRequest->get('sSearchValue', null);
|
||||
|
||||
// Getting area columns properties
|
||||
$aColumnsAttrs = $oBrick->GetFields();
|
||||
// Adding friendlyname attribute to the list is not already in it
|
||||
$sTitleAttrCode = 'friendlyname';
|
||||
if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aColumnsAttrs))
|
||||
{
|
||||
$aColumnsAttrs = array_merge(array($sTitleAttrCode), $aColumnsAttrs);
|
||||
}
|
||||
|
||||
// Starting to build query
|
||||
$oQuery = DBSearch::FromOQL($oBrick->GetOql());
|
||||
|
||||
@@ -198,6 +209,11 @@ class ManageBrickController extends BrickController
|
||||
if ($oDistinctScopeQuery != null)
|
||||
{
|
||||
$oDistinctQuery = $oDistinctQuery->Intersect($oDistinctScopeQuery);
|
||||
// - Allowing all data if necessary
|
||||
if ($oDistinctScopeQuery->IsAllDataAllowed())
|
||||
{
|
||||
$oDistinctQuery->AllowAllData();
|
||||
}
|
||||
}
|
||||
// Adding grouping conditions
|
||||
$oFieldExp = new FieldExpression($sGroupingAreaAttCode, $sParentAlias);
|
||||
@@ -252,7 +268,19 @@ class ManageBrickController extends BrickController
|
||||
// Note : Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass
|
||||
// Note : We now get view scope instead of edit scope as we allowed users to view/edit objects in the brick regarding their rights
|
||||
$oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $aGroupingAreasValue['value'], UR_ACTION_READ);
|
||||
$oAreaQuery = ($oScopeQuery !== null) ? $oAreaQuery->Intersect($oScopeQuery) : null;
|
||||
if ($oScopeQuery !== null)
|
||||
{
|
||||
$oAreaQuery = $oAreaQuery->Intersect($oScopeQuery);
|
||||
// - Allowing all data if necessary
|
||||
if ($oScopeQuery->IsAllDataAllowed())
|
||||
{
|
||||
$oAreaQuery->AllowAllData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oAreaQuery = null;
|
||||
}
|
||||
|
||||
$aQueries[$sKey] = $oAreaQuery;
|
||||
}
|
||||
@@ -264,6 +292,7 @@ class ManageBrickController extends BrickController
|
||||
// - Check how many records there is.
|
||||
// - Update $sDataLoading with its new value regarding the number of record and the threshold
|
||||
$oCountSet = new DBObjectSet($oQuery);
|
||||
$oCountSet->OptimizeColumnLoad(array());
|
||||
$fThreshold = (float) MetaModel::GetModuleSetting($oApp['combodo.portal.instance.id'], 'lazy_loading_threshold');
|
||||
$sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL;
|
||||
unset($oCountSet);
|
||||
@@ -285,6 +314,7 @@ class ManageBrickController extends BrickController
|
||||
|
||||
// Getting total records number
|
||||
$oCountSet = new DBObjectSet($oQuery);
|
||||
$oCountSet->OptimizeColumnLoad(array($oQuery->GetClassAlias() => $aColumnsAttrs));
|
||||
$aData['recordsTotal'] = $oCountSet->Count();
|
||||
$aData['recordsFiltered'] = $oCountSet->Count();
|
||||
unset($oCountSet);
|
||||
@@ -296,6 +326,8 @@ class ManageBrickController extends BrickController
|
||||
{
|
||||
$oSet = new DBObjectSet($oQuery);
|
||||
}
|
||||
$oSet->OptimizeColumnLoad(array($oQuery->GetClassAlias() => $aColumnsAttrs));
|
||||
$oSet->SetOrderByClasses();
|
||||
$aSets[$sKey] = $oSet;
|
||||
}
|
||||
}
|
||||
@@ -306,15 +338,7 @@ class ManageBrickController extends BrickController
|
||||
{
|
||||
// Set properties
|
||||
$sCurrentClass = $sKey;
|
||||
$sTitleAttrCode = MetaModel::GetFriendlyNameAttributeCode($sCurrentClass);
|
||||
|
||||
// Getting area columns properties
|
||||
$aColumnsAttrs = $oBrick->GetFields();
|
||||
// Adding friendlyname attribute to the list is not already in it
|
||||
if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aColumnsAttrs))
|
||||
{
|
||||
$aColumnsAttrs = array_merge(array($sTitleAttrCode), $aColumnsAttrs);
|
||||
}
|
||||
|
||||
// Defining which attribute will open the edition form)
|
||||
$sMainActionAttrCode = $aColumnsAttrs[0];
|
||||
|
||||
@@ -367,7 +391,7 @@ class ManageBrickController extends BrickController
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sCurrentClass, $sItemAttr);
|
||||
if ($oAttDef->IsExternalKey())
|
||||
{
|
||||
@@ -387,6 +411,10 @@ class ManageBrickController extends BrickController
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeSubItem || $oAttDef instanceof AttributeDuration)
|
||||
{
|
||||
$sValue = $oAttDef->GetAsHTML($oCurrentRow->Get($sItemAttr));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = $oAttDef->GetValueLabel($oCurrentRow->Get($sItemAttr));
|
||||
@@ -446,5 +474,3 @@ class ManageBrickController extends BrickController
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -32,6 +32,7 @@ use \IssueLog;
|
||||
use \MetaModel;
|
||||
use \DBSearch;
|
||||
use \DBObjectSearch;
|
||||
use \FalseExpression;
|
||||
use \BinaryExpression;
|
||||
use \FieldExpression;
|
||||
use \VariableExpression;
|
||||
@@ -39,6 +40,8 @@ use \ListExpression;
|
||||
use \ScalarExpression;
|
||||
use \DBObjectSet;
|
||||
use \cmdbAbstractObject;
|
||||
use \AttributeEnum;
|
||||
use \AttributeFinalClass;
|
||||
use \UserRights;
|
||||
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
|
||||
use \Combodo\iTop\Portal\Helper\SecurityHelper;
|
||||
@@ -83,7 +86,7 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
|
||||
// Retrieving object
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
|
||||
if ($oObject === null)
|
||||
{
|
||||
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
|
||||
@@ -155,7 +158,7 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
|
||||
// Retrieving object
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
|
||||
if ($oObject === null)
|
||||
{
|
||||
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
|
||||
@@ -275,23 +278,26 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
|
||||
// Retrieving origin object
|
||||
$oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId);
|
||||
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
|
||||
|
||||
// Retrieving target object (We check if the method is a simple function or if it's part of a class in which case only static function are supported)
|
||||
if (!strpos($sMethodName, '::'))
|
||||
{
|
||||
$sTargetObject = $sMethodName($oOriginObject);
|
||||
$oTargetObject = $sMethodName($oOriginObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aMethodNameParts = explode('::', $sMethodName);
|
||||
$sTargetObject = $aMethodNameParts[0]::$aMethodNameParts[1]($oOriginObject);
|
||||
$sMethodClass = $aMethodNameParts[0];
|
||||
$sMethodName = $aMethodNameParts[1];
|
||||
$oTargetObject = $sMethodClass::$sMethodName($oOriginObject);
|
||||
}
|
||||
|
||||
// Preparing redirection
|
||||
// - Route
|
||||
$aRouteParams = array(
|
||||
'sObjectClass' => get_class($sTargetObject)
|
||||
'sObjectClass' => get_class($oTargetObject)
|
||||
);
|
||||
$sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
|
||||
// - Request
|
||||
@@ -327,7 +333,7 @@ class ObjectController extends AbstractController
|
||||
// }
|
||||
|
||||
// Retrieving object
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
|
||||
if ($oObject === null)
|
||||
{
|
||||
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
|
||||
@@ -335,6 +341,9 @@ class ObjectController extends AbstractController
|
||||
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
// Retrieving request parameters
|
||||
$sOperation = $oRequest->request->get('operation');
|
||||
|
||||
// Preparing a dedicated form for the stimulus application
|
||||
$aFormProperties = array(
|
||||
'id' => 'apply-stimulus',
|
||||
@@ -372,14 +381,39 @@ class ObjectController extends AbstractController
|
||||
'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
|
||||
);
|
||||
|
||||
// TODO : This is a ugly patch to avoid showing a modal with a readonly form to the user as it would prevent user from finishing the transition.
|
||||
// Instead, we apply the stimulus directly here and then go to the edited object.
|
||||
if ($sOperation === null)
|
||||
{
|
||||
if (isset($aData['form']['editable_fields_count']) && $aData['form']['editable_fields_count'] === 0)
|
||||
{
|
||||
$sOperation = 'redirect';
|
||||
|
||||
$oSubRequest = $oRequest;
|
||||
$oSubRequest->request->set('operation', 'submit');
|
||||
$oSubRequest->request->set('stimulus_code', null);
|
||||
|
||||
$aData = array('sMode' => 'apply_stimulus');
|
||||
$aData['form'] = $this->HandleForm($oSubRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
|
||||
// Redefining the array to be as simple as possible :
|
||||
$aData = array('redirection' =>
|
||||
array('url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Preparing response
|
||||
if ($oRequest->isXmlHttpRequest())
|
||||
{
|
||||
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
|
||||
if ($oRequest->request->get('operation') === null)
|
||||
if ($sOperation === null)
|
||||
{
|
||||
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
|
||||
}
|
||||
elseif ($sOperation === 'redirect')
|
||||
{
|
||||
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/modal/mode_loader.html.twig', $aData);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oResponse = $oApp->json($aData);
|
||||
@@ -428,7 +462,7 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
else
|
||||
{
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId);
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
|
||||
}
|
||||
|
||||
// Preparing transitions only if we are currently going through one
|
||||
@@ -473,9 +507,16 @@ class ObjectController extends AbstractController
|
||||
->SetMode($sMode)
|
||||
->SetActionRulesToken($sActionRulesToken)
|
||||
->SetRenderer($oFormRenderer)
|
||||
->SetFormProperties($aFormProperties)
|
||||
->Build();
|
||||
|
||||
->SetFormProperties($aFormProperties);
|
||||
|
||||
if ($sMode === 'apply_stimulus')
|
||||
{
|
||||
$aEditFormProperties = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, ObjectFormManager::ENUM_MODE_APPLY_STIMULUS);
|
||||
$oFormManager->MergeFormProperties($aEditFormProperties);
|
||||
}
|
||||
|
||||
$oFormManager->Build();
|
||||
|
||||
// Check the number of editable fields
|
||||
$aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
|
||||
}
|
||||
@@ -613,6 +654,8 @@ class ObjectController extends AbstractController
|
||||
|
||||
// Retrieving parameters
|
||||
$sQuery = $aRequestContent['sQuery'];
|
||||
$sFormPath = $aRequestContent['sFormPath'];
|
||||
$sFieldId = $aRequestContent['sFieldId'];
|
||||
|
||||
// Checking security layers
|
||||
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
|
||||
@@ -624,7 +667,8 @@ class ObjectController extends AbstractController
|
||||
// Retrieving host object for future DBSearch parameters
|
||||
if ($sHostObjectId !== null)
|
||||
{
|
||||
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -665,20 +709,58 @@ class ObjectController extends AbstractController
|
||||
// Building search query
|
||||
// - Retrieving target object class from attcode
|
||||
$oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
|
||||
$sTargetObjectClass = $oTargetAttDef->GetTargetClass();
|
||||
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
|
||||
{
|
||||
$oRequestTemplate = $oHostObject->Get($sTargetAttCode);
|
||||
$oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
|
||||
$sTargetObjectClass = $oTemplateFieldSearch->GetClass();
|
||||
}
|
||||
elseif ($oTargetAttDef->IsLinkSet())
|
||||
{
|
||||
throw new Exception('Search autocomplete cannot apply on AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' (' . $sHostObjectClass . '->' . $sTargetAttCode . ') given.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sTargetObjectClass = $oTargetAttDef->GetTargetClass();
|
||||
}
|
||||
// - Base query from meta model
|
||||
$oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
|
||||
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
|
||||
{
|
||||
$oSearch = $oTemplateFieldSearch;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
|
||||
}
|
||||
// - Adding query condition
|
||||
$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
|
||||
// - Intersecting with scope constraints
|
||||
$oSearch = $oSearch->Intersect($oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ));
|
||||
|
||||
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
|
||||
// It is the responsability of the template designer to write the right query so the user see only what he should.
|
||||
if ($oTargetAttDef->GetEditClass() !== 'CustomFields')
|
||||
{
|
||||
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
|
||||
$oSearch = $oSearch->Intersect($oScopeSearch);
|
||||
// - Allowing all data if necessary
|
||||
if ($oScopeSearch->IsAllDataAllowed())
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieving results
|
||||
// - Preparing object set
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%' . $sQuery . '%'));
|
||||
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
|
||||
// Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
|
||||
$oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
|
||||
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
|
||||
{
|
||||
$oSet->SetLimit(static::DEFAULT_COUNT_PER_PAGE_LIST);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
|
||||
}
|
||||
// - Retrieving objects
|
||||
while ($oItem = $oSet->Fetch())
|
||||
{
|
||||
@@ -729,7 +811,8 @@ class ObjectController extends AbstractController
|
||||
// Retrieving host object for future DBSearch parameters
|
||||
if ($sHostObjectId !== null)
|
||||
{
|
||||
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -795,6 +878,12 @@ class ObjectController extends AbstractController
|
||||
$sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
|
||||
}
|
||||
}
|
||||
elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
|
||||
{
|
||||
$oRequestTemplate = $oHostObject->Get($sTargetAttCode);
|
||||
$oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
|
||||
$sTargetObjectClass = $oTemplateFieldSearch->GetClass();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('Search from attribute can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
|
||||
@@ -803,16 +892,18 @@ class ObjectController extends AbstractController
|
||||
// - Retrieving class attribute list
|
||||
$aAttCodes = ApplicationHelper::GetLoadedListFromClass($oApp, $sTargetObjectClass, 'list');
|
||||
// - Adding friendlyname attribute to the list is not already in it
|
||||
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetObjectClass);
|
||||
$sTitleAttCode = 'friendlyname';
|
||||
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodes))
|
||||
{
|
||||
$aAttCodes = array_merge(array($sTitleAttCode), $aAttCodes);
|
||||
}
|
||||
|
||||
// - Retrieving scope search
|
||||
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
|
||||
// It is the responsability of the template designer to write the right query so the user see only what he should.
|
||||
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
|
||||
$aInternalParams = array();
|
||||
if ($oScopeSearch === null)
|
||||
if (($oScopeSearch === null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
|
||||
{
|
||||
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
|
||||
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
|
||||
@@ -827,6 +918,11 @@ class ObjectController extends AbstractController
|
||||
{
|
||||
$oSearch = $oScopeSearch;
|
||||
}
|
||||
elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
|
||||
{
|
||||
// Note : $oTemplateFieldSearch has been defined in the "Retrieving target object class from attcode" part, it is not available otherwise
|
||||
$oSearch = $oTemplateFieldSearch;
|
||||
}
|
||||
|
||||
// - Filtering objects to ignore
|
||||
if (($aObjectIdsToIgnore !== null) && (is_array($aObjectIdsToIgnore)))
|
||||
@@ -839,7 +935,7 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), 'NOT IN', new ListExpression($aExpressions)));
|
||||
}
|
||||
|
||||
|
||||
// - Adding query condition
|
||||
$aInternalParams['this'] = $oHostObject;
|
||||
if ($sQuery !== null)
|
||||
@@ -851,7 +947,35 @@ class ObjectController extends AbstractController
|
||||
$oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
|
||||
$sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
|
||||
// Building expression for the current attcode
|
||||
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
|
||||
// - For attributes that need conversion from their display value to storage value
|
||||
// Note : This is dirty hack that will need to be refactored in the OQL core in order to be nicer and to be extended to other types such as dates etc...
|
||||
if (($oAttDef instanceof AttributeEnum) || ($oAttDef instanceof AttributeFinalClass))
|
||||
{
|
||||
// Looking up storage value
|
||||
$aMatchedCodes = array();
|
||||
foreach ($oAttDef->GetAllowedValues() as $sValueCode => $sValueLabel)
|
||||
{
|
||||
if (stripos($sValueLabel, $sQuery) !== false)
|
||||
{
|
||||
$aMatchedCodes[] = $sValueCode;
|
||||
}
|
||||
}
|
||||
// Building expression
|
||||
if (!empty($aMatchedCodes))
|
||||
{
|
||||
$oEnumeratedListExpr = ListExpression::FromScalars($aMatchedCodes);
|
||||
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'IN', $oEnumeratedListExpr);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oBinExpr = new FalseExpression();
|
||||
}
|
||||
}
|
||||
// - For regular attributs
|
||||
else
|
||||
{
|
||||
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
|
||||
}
|
||||
// Adding expression to the full expression (all attcodes)
|
||||
if ($i === 0)
|
||||
{
|
||||
@@ -868,7 +992,17 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
|
||||
// - Intersecting with scope constraints
|
||||
$oSearch = $oSearch->Intersect($oScopeSearch);
|
||||
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
|
||||
// It is the responsability of the template designer to write the right query so the user see only what he should.
|
||||
if (($oScopeSearch !== null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
|
||||
{
|
||||
$oSearch = $oSearch->Intersect($oScopeSearch);
|
||||
// - Allowing all data if necessary
|
||||
if ($oScopeSearch->IsAllDataAllowed())
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieving results
|
||||
// - Preparing object set
|
||||
@@ -909,7 +1043,7 @@ class ObjectController extends AbstractController
|
||||
// Checking if we can view the object
|
||||
if ((SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass(), $oItem->Get($sAttCode))))
|
||||
{
|
||||
$aAttProperties['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oItem->GetKey()));
|
||||
$aAttProperties['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oItem->Get($sAttCode)));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -946,7 +1080,7 @@ class ObjectController extends AbstractController
|
||||
'sFormManagerData' => $sFormManagerData
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
if ($oRequest->isXmlHttpRequest())
|
||||
{
|
||||
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
|
||||
@@ -1001,7 +1135,8 @@ class ObjectController extends AbstractController
|
||||
// Retrieving host object for future DBSearch parameters
|
||||
if ($sHostObjectId !== null)
|
||||
{
|
||||
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1041,7 +1176,7 @@ class ObjectController extends AbstractController
|
||||
// // - Retrieving class attribute list
|
||||
// $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetObjectClass, 'list'));
|
||||
// // - Adding friendlyname attribute to the list is not already in it
|
||||
// $sTitleAttrCode = MetaModel::GetFriendlyNameAttributeCode($sTargetObjectClass);
|
||||
// $sTitleAttrCode = 'friendlyname';
|
||||
// if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aAttCodes))
|
||||
// {
|
||||
// $aAttCodes = array_merge(array($sTitleAttrCode), $aAttCodes);
|
||||
@@ -1092,6 +1227,11 @@ class ObjectController extends AbstractController
|
||||
// }
|
||||
// - Intersecting with scope constraints
|
||||
$oSearch = $oSearch->Intersect($oScopeSearch);
|
||||
// - Allowing all data if necessary
|
||||
if ($oScopeSearch->IsAllDataAllowed())
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
|
||||
// Retrieving results
|
||||
// - Preparing object set
|
||||
@@ -1246,7 +1386,8 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
$oResponse = $oApp->json($aData);
|
||||
// Note : The Content-Type header is set to 'text/plain' in order to be IE9 compatible. Otherwise ('application/json') IE9 will download the response as a JSON file to the user computer...
|
||||
$oResponse = $oApp->json($aData, 200, array('Content-Type' => 'text/plain'));
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
@@ -1307,7 +1448,12 @@ class ObjectController extends AbstractController
|
||||
}
|
||||
|
||||
// Building the search
|
||||
$bIgnoreSilos = $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT " . $sObjectClass . " WHERE id IN ('" . implode("','", $aObjectIds) . "')");
|
||||
if ($bIgnoreSilos === true)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$oSet->OptimizeColumnLoad($aObjectAttCodes);
|
||||
|
||||
|
||||
@@ -493,6 +493,9 @@ abstract class PortalBrick extends AbstractBrick
|
||||
$this->SetDecorationClassNavigationMenu($optionalNodeValue);
|
||||
}
|
||||
break;
|
||||
case 'tile_controller_action':
|
||||
$this->SetTileControllerAction($oBrickSubNode->GetText(static::DEFAULT_TILE_CONTROLLER_ACTION));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ use \MetaModel;
|
||||
use \CMDBSource;
|
||||
use \DBObject;
|
||||
use \DBObjectSet;
|
||||
use \DBSearch;
|
||||
use \DBObjectSearch;
|
||||
use \DBObjectSetComparator;
|
||||
use \InlineImage;
|
||||
@@ -49,6 +50,7 @@ class ObjectFormManager extends FormManager
|
||||
const ENUM_MODE_VIEW = 'view';
|
||||
const ENUM_MODE_EDIT = 'edit';
|
||||
const ENUM_MODE_CREATE = 'create';
|
||||
const ENUM_MODE_APPLY_STIMULUS = 'apply_stimulus';
|
||||
|
||||
protected $oApp;
|
||||
protected $oObject;
|
||||
@@ -92,7 +94,8 @@ class ObjectFormManager extends FormManager
|
||||
}
|
||||
else
|
||||
{
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $aJson['formobject_id'], true);
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $aJson['formobject_id'], true, true);
|
||||
}
|
||||
$oFormManager->SetObject($oObject);
|
||||
|
||||
@@ -482,6 +485,18 @@ class ObjectFormManager extends FormManager
|
||||
{
|
||||
$oField->SetReadOnly(true);
|
||||
}
|
||||
// - Else if it's must change, we force it as not readonly and not hidden
|
||||
elseif (($iFieldFlags & OPT_ATT_MUSTCHANGE) === OPT_ATT_MUSTCHANGE)
|
||||
{
|
||||
$oField->SetReadOnly(false);
|
||||
$oField->SetHidden(false);
|
||||
}
|
||||
// - Else if it's must prompt, we force it as not readonly and not hidden
|
||||
elseif (($iFieldFlags & OPT_ATT_MUSTPROMPT) === OPT_ATT_MUSTPROMPT)
|
||||
{
|
||||
$oField->SetReadOnly(false);
|
||||
$oField->SetHidden(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal field
|
||||
@@ -516,6 +531,61 @@ class ObjectFormManager extends FormManager
|
||||
$oField->SetInformationEndpoint($this->oApp['url_generator']->generate('p_object_get_informations_json'));
|
||||
}
|
||||
}
|
||||
// - Field that require to apply scope on its DM OQL
|
||||
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\SelectObjectField')))
|
||||
{
|
||||
if ($this->oApp !== null)
|
||||
{
|
||||
$oScopeOriginal = ($oField->GetSearch() !== null) ? $oField->GetSearch() : DBSearch::FromOQL($oAttDef->GetValuesDef()->GetFilterExpression());
|
||||
|
||||
$oScopeSearch = $this->oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oScopeOriginal->GetClass(), UR_ACTION_READ);
|
||||
if ($oScopeSearch === null)
|
||||
{
|
||||
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $oScopeOriginal->GetClass() . ' class.');
|
||||
$this->oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
$oScopeOriginal = $oScopeOriginal->Intersect($oScopeSearch);
|
||||
// Note : This is to skip the silo restriction on the final query
|
||||
if ($oScopeSearch->IsAllDataAllowed())
|
||||
{
|
||||
$oScopeOriginal->AllowAllData();
|
||||
}
|
||||
$oScopeOriginal->SetInternalParams(array('this' => $this->oObject));
|
||||
$oField->SetSearch($oScopeOriginal);
|
||||
}
|
||||
}
|
||||
// - Field that require processing on their subfields
|
||||
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\SubFormField')))
|
||||
{
|
||||
$oSubForm = $oField->GetForm();
|
||||
if ($oAttDef->GetEditClass() === 'CustomFields')
|
||||
{
|
||||
// Retrieving only user data fields (not the metadata fields of the template)
|
||||
if ($oSubForm->HasField('user_data'))
|
||||
{
|
||||
$oUserDataField = $oSubForm->GetField('user_data');
|
||||
$oUserDataForm = $oUserDataField->GetForm();
|
||||
foreach ($oUserDataForm->GetFields() as $oCustomField)
|
||||
{
|
||||
// - Field that require a search endpoint (OQL based dropdown list fields)
|
||||
if (in_array(get_class($oCustomField), array('Combodo\\iTop\\Form\\Field\\SelectObjectField')))
|
||||
{
|
||||
if ($this->oApp !== null)
|
||||
{
|
||||
|
||||
$sSearchEndpoint = $this->oApp['url_generator']->generate('p_object_search_generic', array(
|
||||
'sTargetAttCode' => $oAttDef->GetCode(),
|
||||
'sHostObjectClass' => get_class($this->oObject),
|
||||
'sHostObjectId' => ($this->oObject->IsNew()) ? null : $this->oObject->GetKey(),
|
||||
'ar_token' => $this->GetActionRulesToken(),
|
||||
));
|
||||
$oCustomField->SetSearchEndpoint($sSearchEndpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -539,7 +609,7 @@ class ObjectFormManager extends FormManager
|
||||
// Note : This snippet is inspired from AttributeLinkedSet::MakeFormField()
|
||||
$aAttCodesToDisplay = ApplicationHelper::GetLoadedListFromClass($this->oApp, $oField->GetTargetClass(), 'list');
|
||||
// - Adding friendlyname attribute to the list is not already in it
|
||||
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($oField->GetTargetClass());
|
||||
$sTitleAttCode = 'friendlyname';
|
||||
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay))
|
||||
{
|
||||
$aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
|
||||
@@ -625,6 +695,87 @@ class ObjectFormManager extends FormManager
|
||||
$this->oRenderer->SetForm($this->oForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merging $this->aFormProperties with $aFormPropertiesToMerge. Merge only layout for now
|
||||
*
|
||||
* @param array $aFormPropertiesToMerge
|
||||
* @throws Exception
|
||||
*/
|
||||
public function MergeFormProperties($aFormPropertiesToMerge)
|
||||
{
|
||||
if ($aFormPropertiesToMerge['layout'] !== null)
|
||||
{
|
||||
// Checking if we need to render the template from twig to html in order to parse the fields
|
||||
if ($aFormPropertiesToMerge['layout']['type'] === 'twig')
|
||||
{
|
||||
// Creating sandbox twig env. to load and test the custom form template
|
||||
$oTwig = new \Twig_Environment(new \Twig_Loader_String());
|
||||
$sRendered = $oTwig->render($aFormPropertiesToMerge['layout']['content'], array('oRenderer' => $this->oRenderer, 'oObject' => $this->oObject));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRendered = $aFormPropertiesToMerge['layout']['content'];
|
||||
}
|
||||
|
||||
// Parsing rendered template to find the fields
|
||||
$oHtmlDocument = new \DOMDocument();
|
||||
$oHtmlDocument->loadHTML('<root>' . $sRendered . '</root>');
|
||||
|
||||
// Adding fields to the list
|
||||
$oXPath = new \DOMXPath($oHtmlDocument);
|
||||
foreach ($oXPath->query('//div[@class="form_field"][@data-field-id]') as $oFieldNode)
|
||||
{
|
||||
$sFieldId = $oFieldNode->getAttribute('data-field-id');
|
||||
$sFieldFlags = $oFieldNode->getAttribute('data-field-flags');
|
||||
// $iFieldFlags = OPT_ATT_NORMAL;
|
||||
|
||||
// // Checking if field has form_path, if not, we add it
|
||||
// if (!$oFieldNode->hasAttribute('data-form-path'))
|
||||
// {
|
||||
// $oFieldNode->setAttribute('data-form-path', $oForm->GetId());
|
||||
// }
|
||||
// Merging only fields that are already in the form
|
||||
if (array_key_exists($sFieldId, $this->aFormProperties['fields']))
|
||||
{
|
||||
// Settings field flags from the data-field-flags attribute
|
||||
foreach (explode(' ', $sFieldFlags) as $sFieldFlag)
|
||||
{
|
||||
if ($sFieldFlag !== '')
|
||||
{
|
||||
$sConst = 'OPT_ATT_' . strtoupper(str_replace('_', '', $sFieldFlag));
|
||||
if (defined($sConst))
|
||||
{
|
||||
switch ($sConst)
|
||||
{
|
||||
case 'OPT_ATT_SLAVE':
|
||||
case 'OPT_ATT_HIDDEN':
|
||||
if (!array_key_exists($sFieldId, $this->aFormProperties['fields']))
|
||||
{
|
||||
$this->aFormProperties['fields'][$sFieldId] = array();
|
||||
}
|
||||
$this->aFormProperties['fields'][$sFieldId]['hidden'] = true;
|
||||
break;
|
||||
case 'OPT_ATT_READONLY':
|
||||
if (!array_key_exists($sFieldId, $this->aFormProperties['fields']))
|
||||
{
|
||||
$this->aFormProperties['fields'][$sFieldId] = array();
|
||||
}
|
||||
$this->aFormProperties['fields'][$sFieldId]['read_only'] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Flag "' . $sFieldFlag . '" is not valid for field [@data-field-id="' . $sFieldId . '"] in form[@id="' . $aFormPropertiesToMerge['id'] . '"]');
|
||||
throw new Exception('Flag "' . $sFieldFlag . '" is not valid for field [@data-field-id="' . $sFieldId . '"] in form[@id="' . $aFormPropertiesToMerge['id'] . '"]');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls all form fields OnCancel method in order to delegate them the cleanup;
|
||||
*
|
||||
@@ -727,6 +878,9 @@ class ObjectFormManager extends FormManager
|
||||
// Ending transaction with a commit as everything was fine
|
||||
CMDBSource::Query('COMMIT');
|
||||
|
||||
// Resetting caselog fields value, otherwise the value will stay in it after submit.
|
||||
$this->oForm->ResetCaseLogFields();
|
||||
|
||||
if ($bWasModified)
|
||||
{
|
||||
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
|
||||
@@ -791,7 +945,8 @@ class ObjectFormManager extends FormManager
|
||||
// LinkedSet
|
||||
if (!$oAttDef->IsIndirect())
|
||||
{
|
||||
$oLinkedObject = MetaModel::GetObject($sTargetClass, abs($iTargetId));
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oLinkedObject = MetaModel::GetObject($sTargetClass, abs($iTargetId), true, true);
|
||||
$oValueSet->AddObject($oLinkedObject);
|
||||
}
|
||||
// LinkedSetIndirect
|
||||
@@ -807,7 +962,8 @@ class ObjectFormManager extends FormManager
|
||||
// Existing relation
|
||||
else
|
||||
{
|
||||
$oLink = MetaModel::GetObject($sTargetClass, $iTargetId);
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oLink = MetaModel::GetObject($sTargetClass, $iTargetId, true, true);
|
||||
}
|
||||
$oValueSet->AddObject($oLink);
|
||||
}
|
||||
@@ -860,7 +1016,7 @@ class ObjectFormManager extends FormManager
|
||||
else
|
||||
{
|
||||
$this->oObject->Set($sAttCode, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->oObject->DoComputeValues();
|
||||
|
||||
@@ -202,6 +202,7 @@ class ApplicationHelper
|
||||
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
|
||||
})
|
||||
);
|
||||
|
||||
// A filter to format a string via the Dict::Format function
|
||||
// Usage in twig : {{ 'String:ToTranslate'|dict_format() }}
|
||||
$oApp['twig']->addFilter(new Twig_SimpleFilter('dict_format', function($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null)
|
||||
@@ -209,10 +210,12 @@ class ApplicationHelper
|
||||
return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
|
||||
})
|
||||
);
|
||||
|
||||
// Filters to enable base64 encode/decode
|
||||
// Usage in twig : {{ 'String to encode'|base64_encode }}
|
||||
$oApp['twig']->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
|
||||
$oApp['twig']->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
|
||||
|
||||
// Filters to enable json decode (encode already exists)
|
||||
// Usage in twig : {{ aSomeArray|json_decode }}
|
||||
$oApp['twig']->addFilter(new Twig_SimpleFilter('json_decode', function($sJsonString, $bAssoc = false)
|
||||
@@ -220,6 +223,21 @@ class ApplicationHelper
|
||||
return json_decode($sJsonString, $bAssoc);
|
||||
})
|
||||
);
|
||||
|
||||
// Filter to add itopversion to an url
|
||||
$oApp['twig']->addFilter(new Twig_SimpleFilter('add_itop_version', function($sUrl)
|
||||
{
|
||||
if (strpos($sUrl, '?') === false)
|
||||
{
|
||||
$sUrl = $sUrl . "?itopversion=" . ITOP_VERSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sUrl . "&itopversion=" . ITOP_VERSION;
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -560,7 +578,7 @@ class ApplicationHelper
|
||||
else
|
||||
{
|
||||
$bFound = false;
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
|
||||
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $sParentClass)
|
||||
{
|
||||
if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode]))
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
@@ -23,6 +23,7 @@ use \Exception;
|
||||
use \Silex\Application;
|
||||
use \DOMNodeList;
|
||||
use \DOMFormatException;
|
||||
use \UserRights;
|
||||
use \DBObject;
|
||||
use \DBSearch;
|
||||
use \DBObjectSet;
|
||||
@@ -42,6 +43,7 @@ class ContextManipulatorHelper
|
||||
const ENUM_RULE_CALLBACK_OPEN_EDIT = 'edit';
|
||||
const DEFAULT_RULE_CALLBACK_OPEN = self::ENUM_RULE_CALLBACK_OPEN_VIEW;
|
||||
|
||||
protected $oApp;
|
||||
protected $aRules;
|
||||
|
||||
public function __construct()
|
||||
@@ -59,7 +61,7 @@ class ContextManipulatorHelper
|
||||
public function Init(DOMNodeList $oNodes)
|
||||
{
|
||||
$this->aRules = array();
|
||||
|
||||
|
||||
// Iterating over the scope nodes
|
||||
foreach ($oNodes as $oRuleNode)
|
||||
{
|
||||
@@ -181,6 +183,11 @@ class ContextManipulatorHelper
|
||||
}
|
||||
}
|
||||
|
||||
public function SetApp($oApp)
|
||||
{
|
||||
$this->oApp = $oApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash array of rules
|
||||
*
|
||||
@@ -222,7 +229,7 @@ class ContextManipulatorHelper
|
||||
* ...
|
||||
* )
|
||||
* )
|
||||
*
|
||||
*
|
||||
* @param array $aData
|
||||
* @param DBObject $oObject
|
||||
*/
|
||||
@@ -290,6 +297,13 @@ class ContextManipulatorHelper
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for silos
|
||||
$oScopeSearch = $this->oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sSearchClass, UR_ACTION_READ);
|
||||
if ($oScopeSearch->IsAllDataAllowed())
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
|
||||
// Retrieving source object(s) and applying rules
|
||||
$oSet = new DBObjectSet($oSearch, array(), $aSearchParams);
|
||||
while ($oSourceObject = $oSet->Fetch())
|
||||
|
||||
@@ -36,6 +36,7 @@ class ScopeValidatorHelper
|
||||
const ENUM_TYPE_ALLOW = 'allow';
|
||||
const ENUM_TYPE_RESTRICT = 'restrict';
|
||||
const DEFAULT_GENERATED_CLASS = 'PortalScopesValues';
|
||||
const DEFAULT_IGNORE_SILOS = false;
|
||||
|
||||
protected $sCachePath;
|
||||
protected $sFilename;
|
||||
@@ -179,6 +180,9 @@ class ScopeValidatorHelper
|
||||
// Retrieving the edit query
|
||||
$oOqlEditNode = $oScopeNode->GetOptionalElement('oql_edit');
|
||||
$sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
|
||||
// Retrieving ignore allowed org flag
|
||||
$oIgnoreSilosNode = $oScopeNode->GetOptionalElement('ignore_silos');
|
||||
$bIgnoreSilos = ( ($oIgnoreSilosNode !== null) && ($oIgnoreSilosNode->GetText() === 'true') ) ? true : static::DEFAULT_IGNORE_SILOS;
|
||||
|
||||
// Retrieving profiles for the scope
|
||||
$oProfilesNode = $oScopeNode->GetOptionalElement('allowed_profiles');
|
||||
@@ -221,13 +225,20 @@ class ScopeValidatorHelper
|
||||
$oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ][$sOqlViewType]);
|
||||
$aFilters = array($oExistingFilter, $oViewFilter);
|
||||
$oResFilter = new DBUnionSearch($aFilters);
|
||||
|
||||
// Applying ignore_silos flag on result filter if necessary (As the union will remove it if it is not on all sub-queries)
|
||||
if ($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ]['ignore_silos'] === true)
|
||||
{
|
||||
$bIgnoreSilos = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oResFilter = $oViewFilter;
|
||||
}
|
||||
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ] = array(
|
||||
$sOqlViewType => $oResFilter->ToOQL()
|
||||
$sOqlViewType => $oResFilter->ToOQL(),
|
||||
'ignore_silos' => $bIgnoreSilos
|
||||
);
|
||||
// - Edit query
|
||||
if ($sOqlEdit !== null)
|
||||
@@ -264,7 +275,8 @@ class ScopeValidatorHelper
|
||||
$oResFilter = $oEditFilter;
|
||||
}
|
||||
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE] = array(
|
||||
$sOqlViewType => $oResFilter->ToOQL()
|
||||
$sOqlViewType => $oResFilter->ToOQL(),
|
||||
'ignore_silos' => $bIgnoreSilos
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -273,7 +285,7 @@ class ScopeValidatorHelper
|
||||
$aProfileClasses[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
|
||||
// For each class explicitly given in the scopes, we check if its child classes were also in the scope :
|
||||
// If not, we add them with the same OQL
|
||||
@@ -295,10 +307,14 @@ class ScopeValidatorHelper
|
||||
$aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
|
||||
foreach ($aTmpProfile as $sType => $sOql)
|
||||
{
|
||||
$oTmpFilter = DBSearch::FromOQL($sOql);
|
||||
$oTmpFilter->ChangeClass($sChildClass);
|
||||
// IF condition is just to skip the 'ignore_silos' flag
|
||||
if (in_array($sType, array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT)))
|
||||
{
|
||||
$oTmpFilter = DBSearch::FromOQL($sOql);
|
||||
$oTmpFilter->ChangeClass($sChildClass);
|
||||
|
||||
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
|
||||
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
|
||||
}
|
||||
}
|
||||
|
||||
$aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
|
||||
@@ -471,6 +487,7 @@ class ScopeValidatorHelper
|
||||
$oSearch = null;
|
||||
$aAllowSearches = array();
|
||||
$aRestrictSearches = array();
|
||||
$bIgnoreSilos = static::DEFAULT_IGNORE_SILOS;
|
||||
|
||||
// Checking the default mode
|
||||
if ($iAction === null)
|
||||
@@ -498,6 +515,11 @@ class ScopeValidatorHelper
|
||||
{
|
||||
$aRestrictSearches[] = DBSearch::FromOQL($aProfileMatrix['restrict']);
|
||||
}
|
||||
// If a profile should ignore allowed org, we set it for all its queries no matter the profile
|
||||
if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
|
||||
{
|
||||
$bIgnoreSilos = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,10 +536,47 @@ class ScopeValidatorHelper
|
||||
$oSearch = new DBUnionSearch($aAllowSearches);
|
||||
$oSearch = $oSearch->RemoveDuplicateQueries();
|
||||
}
|
||||
|
||||
if ($bIgnoreSilos === true)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
|
||||
return $oSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one of the $aProfiles has the ignore_silos flag set to true for the $sClass.
|
||||
*
|
||||
* @param array $aProfiles
|
||||
* @param string $sClass
|
||||
* @return boolean
|
||||
*/
|
||||
public function IsAllDataAllowedForScope($aProfiles, $sClass)
|
||||
{
|
||||
$bIgnoreSilos = false;
|
||||
|
||||
// Iterating on profiles to retrieving the different OQLs parts
|
||||
foreach ($aProfiles as $sProfile)
|
||||
{
|
||||
// Retrieving matrix informtions
|
||||
$iProfileId = $this->GetProfileIdFromProfileName($sProfile);
|
||||
|
||||
// Retrieving profile OQLs
|
||||
$sScopeValuesClass = $this->sGeneratedClass;
|
||||
$aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, static::ENUM_MODE_READ);
|
||||
if ($aProfileMatrix !== null)
|
||||
{
|
||||
// If a profile should ignore allowed org, we set it for all its queries no matter the profile
|
||||
if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
|
||||
{
|
||||
$bIgnoreSilos = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $bIgnoreSilos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the profile id from a string being either a constant or its name.
|
||||
*
|
||||
|
||||
@@ -112,7 +112,7 @@ class SecurityHelper
|
||||
// Checking if the cmdbAbstractObject exists if id is specified
|
||||
if ($sObjectId !== null)
|
||||
{
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
|
||||
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
|
||||
if ($oObject === null)
|
||||
{
|
||||
if ($oApp['debug'])
|
||||
|
||||
@@ -44,6 +44,7 @@ class UrlGenerator extends SymfonyUrlGenerator
|
||||
*/
|
||||
public function generate($name, $parameters = array(), $referenceType = SymfonyUrlGenerator::ABSOLUTE_PATH)
|
||||
{
|
||||
// Mandatory parameters
|
||||
$sExecModule = utils::ReadParam('exec_module', '', false, 'string');
|
||||
$sExecPage = utils::ReadParam('exec_page', '', false, 'string');
|
||||
if ($sExecModule !== '' && $sExecPage !== '')
|
||||
@@ -52,6 +53,18 @@ class UrlGenerator extends SymfonyUrlGenerator
|
||||
$parameters['exec_page'] = $sExecPage;
|
||||
}
|
||||
|
||||
// Optional parameters
|
||||
$sEnvSwitch = utils::ReadParam('env_switch', '', false, 'string');
|
||||
if ($sEnvSwitch !== '')
|
||||
{
|
||||
$parameters['env_switch'] = $sEnvSwitch;
|
||||
}
|
||||
$sDebug = utils::ReadParam('debug', '', false, 'string');
|
||||
if ($sDebug !== '')
|
||||
{
|
||||
$parameters['debug'] = $sDebug;
|
||||
}
|
||||
|
||||
return parent::generate($name, $parameters, $referenceType);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ class ContextManipulatorServiceProvider implements ServiceProviderInterface
|
||||
$oApp->flush();
|
||||
|
||||
$oContextManipulatorHelper = new ContextManipulatorHelper();
|
||||
$oContextManipulatorHelper->SetApp($oApp);
|
||||
|
||||
return $oContextManipulatorHelper;
|
||||
});
|
||||
|
||||
@@ -73,11 +73,11 @@
|
||||
// For the same reason, tooltip widget is created in "drawCallback" instead of here.
|
||||
if( (data.tooltip !== undefined) && (data.tooltip !== ''))
|
||||
{
|
||||
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').text(data.name).prop('outerHTML') );
|
||||
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').html(data.name).prop('outerHTML') );
|
||||
}
|
||||
else
|
||||
{
|
||||
cellElem.text(data.name);
|
||||
cellElem.html(data.name);
|
||||
}
|
||||
|
||||
// Building actions
|
||||
@@ -97,11 +97,11 @@
|
||||
break;
|
||||
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
|
||||
url = levelPrimaryAction.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
|
||||
url = addParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
|
||||
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
|
||||
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
|
||||
break;
|
||||
default:
|
||||
console.log('Action "'+levelPrimaryAction.type+'" not implemented');
|
||||
//console.log('Action "'+levelPrimaryAction.type+'" not implemented');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -150,11 +150,11 @@
|
||||
break;
|
||||
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
|
||||
url = action.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
|
||||
url = addParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
|
||||
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
|
||||
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
|
||||
break;
|
||||
default:
|
||||
console.log('Action "'+action.type+'" not implemented for secondary action');
|
||||
//console.log('Action "'+action.type+'" not implemented for secondary action');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,6 @@
|
||||
"type": "html",
|
||||
"data": oLevelsProperties[sKey].alias+".fields."+oLevelsProperties[sKey].fields[i].code
|
||||
});
|
||||
console.log(oLevelsProperties[sKey].fields[i].visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,6 +239,7 @@
|
||||
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::DEFAULT_COUNT_PER_PAGE_LIST') }},
|
||||
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
|
||||
"columns": getColumnsDefinition(),
|
||||
"order": [],
|
||||
"drawCallback": function(settings){
|
||||
// Tooltip has to been created here, as the render callback only returns a string, not an object.
|
||||
$(this).find('[data-toggle="tooltip"]').tooltip({container: 'body', html: true, trigger: 'hover', placement: 'right'}); // container option is necessary when in a table
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-8 col-sm-10 col-lg-11 text-right">
|
||||
<label>Filtrer :<input type="search" class="form-control input-sm" id="brick_search_field" placeholder="" aria-controls="brick_main_table" value="{{ sSearchValue }}"></label>
|
||||
<label>{{ 'Portal:Datatables:Language:Search'|dict_s }}<input type="search" class="form-control input-sm" id="brick_search_field" placeholder="" aria-controls="brick_main_table" value="{{ sSearchValue }}"></label>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-group" id="brick_content_tree" data-level-id="L">
|
||||
@@ -223,11 +223,11 @@
|
||||
break;
|
||||
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
|
||||
url = levelPrimaryAction.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
|
||||
url = addParameterToUrl(url, 'ar_token', item.action_rules_token[levelPrimaryAction.type]);
|
||||
url = AddParameterToUrl(url, 'ar_token', item.action_rules_token[levelPrimaryAction.type]);
|
||||
aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
|
||||
break;
|
||||
default:
|
||||
console.log('Action "'+levelPrimaryAction.type+'" not implemented for primary action');
|
||||
//console.log('Action "'+levelPrimaryAction.type+'" not implemented for primary action');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -275,11 +275,11 @@
|
||||
break;
|
||||
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
|
||||
url = action.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
|
||||
url = addParameterToUrl(url, 'ar_token', item.action_rules_token[action.type]);
|
||||
url = AddParameterToUrl(url, 'ar_token', item.action_rules_token[action.type]);
|
||||
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
|
||||
break;
|
||||
default:
|
||||
console.log('Action "'+action.type+'" not implemented for secondary action');
|
||||
//console.log('Action "'+action.type+'" not implemented for secondary action');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,24 +17,35 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block pMainContentHolder%}
|
||||
{% set iTableCount = 0 %}
|
||||
{% if aGroupingAreasData|length > 0 %}
|
||||
{% for aAreaData in aGroupingAreasData %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{{ aAreaData.sTitle }}</h3>
|
||||
{% if aAreaData.iItemsCount > 0 %}
|
||||
{% set iTableCount = iTableCount + 1 %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{{ aAreaData.sTitle }}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{# We decided not to show empty tables anymore #}
|
||||
{#
|
||||
{% if aAreaData.iItemsCount > 0 %}
|
||||
#}
|
||||
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive" width="100%"></table>
|
||||
{#
|
||||
{% else %}
|
||||
<div class="text-center">
|
||||
{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}
|
||||
</div>
|
||||
{% endif %}
|
||||
#}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if aAreaData.iItemsCount > 0 %}
|
||||
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive" width="100%"></table>
|
||||
{% else %}
|
||||
<div class="text-center">
|
||||
{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% endif %}
|
||||
|
||||
{% if iTableCount == 0 %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
|
||||
@@ -69,12 +80,12 @@
|
||||
{
|
||||
var tableProperties = columnsProperties[tableName];
|
||||
|
||||
if(tableProperties === undefined)
|
||||
if(tableProperties === undefined && window.console)
|
||||
{
|
||||
console.log('Could not retrieve columns properties for table "'+tableName+'"');
|
||||
return false;
|
||||
}
|
||||
if(rawData[tableName] === undefined)
|
||||
if(rawData[tableName] === undefined && window.console)
|
||||
{
|
||||
console.log('Could not retrieve data for table "'+tableName+'"');
|
||||
return false;
|
||||
@@ -103,7 +114,7 @@
|
||||
|
||||
// Preparing the cell data
|
||||
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
|
||||
cellElem.text(row.attributes[att_code].value);
|
||||
cellElem.html(row.attributes[att_code].value);
|
||||
// Building actions
|
||||
if(itemActions.length > 0)
|
||||
{
|
||||
@@ -120,7 +131,7 @@
|
||||
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
|
||||
break;
|
||||
default:
|
||||
console.log('Action "'+itemPrimaryAction+'" not implemented');
|
||||
//console.log('Action "'+itemPrimaryAction+'" not implemented');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -227,7 +238,6 @@
|
||||
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
|
||||
$('#table-{{ sAreaId }}_filter input').off().on('keyup', function(){
|
||||
var me = this;
|
||||
console.log('here');
|
||||
clearTimeout(oKeyTimeout);
|
||||
oKeyTimeout = setTimeout(function() {
|
||||
oTable{{ sAreaId }}.search(me.value.latinise()).draw();
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{% if form.buttons is defined and form.buttons.transitions is defined and form.buttons.transitions|length > 0 %}
|
||||
<div class="form_btn_transitions">
|
||||
{% for sStimulusCode, sStimulusLabel in form.buttons.transitions %}
|
||||
<button class="btn btn-default form_btn_transition" type="submit" name="stimulus_code" value="{{ sStimulusCode }}">{{ sStimulusLabel }}</button>
|
||||
<button class="btn btn-primary form_btn_transition" type="submit" name="stimulus_code" value="{{ sStimulusCode }}">{{ sStimulusLabel }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -67,60 +67,63 @@
|
||||
|
||||
// Sticky buttons handler
|
||||
{% if sMode != 'view' %}
|
||||
// Note : This pattern if to prevent performance issues
|
||||
// - Cloning buttons
|
||||
var oNormalRegularButtons_{{ sFormIdSanitized }} = $('#{{ sFormId }} .form_btn_regular');
|
||||
var oStickyRegularButtons_{{ sFormIdSanitized }} = oNormalRegularButtons_{{ sFormIdSanitized }}.clone(true, true);
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('sticky');
|
||||
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon').length > 0)
|
||||
if( $('#{{ sFormId }} .form_btn_regular button').length > 0 )
|
||||
{
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon')[0].outerHTML );
|
||||
}
|
||||
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon').length > 0)
|
||||
{
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon')[0].outerHTML );
|
||||
}
|
||||
|
||||
$('#{{ sFormId }}').closest({% if tIsModal == true %}'.modal'{% else %}'#main-content'{% endif %}).append(oStickyRegularButtons_{{ sFormIdSanitized }});
|
||||
|
||||
// - Global timeout for any
|
||||
var oScrollTimeout;
|
||||
// - Scroll handler
|
||||
scrollHandler_{{ sFormIdSanitized }} = function () {
|
||||
if($('#{{ sFormId }} .form_buttons').visible())
|
||||
// Note : This pattern if to prevent performance issues
|
||||
// - Cloning buttons
|
||||
var oNormalRegularButtons_{{ sFormIdSanitized }} = $('#{{ sFormId }} .form_btn_regular');
|
||||
var oStickyRegularButtons_{{ sFormIdSanitized }} = oNormalRegularButtons_{{ sFormIdSanitized }}.clone(true, true);
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('sticky');
|
||||
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon').length > 0)
|
||||
{
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('closed');
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon')[0].outerHTML );
|
||||
}
|
||||
else
|
||||
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon').length > 0)
|
||||
{
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.removeClass('closed');
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon')[0].outerHTML );
|
||||
}
|
||||
};
|
||||
// - Event binding for scroll
|
||||
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('scroll').on('scroll', function () {
|
||||
if (oScrollTimeout) {
|
||||
// Clear the timeout, if one is pending
|
||||
clearTimeout(oScrollTimeout);
|
||||
oScrollTimeout = null;
|
||||
}
|
||||
oScrollTimeout = setTimeout(scrollHandler_{{ sFormIdSanitized }}, 50);
|
||||
});
|
||||
// - Event binding for linkedset collapse
|
||||
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('shown.bs.collapse hidden.bs.collapse').on('shown.bs.collapse hidden.bs.collapse', function () {
|
||||
scrollHandler_{{ sFormIdSanitized }}();
|
||||
});
|
||||
// - Event binding for form building / updating
|
||||
// Note : We do not want to 'off' the event or it will remove listeners from the widget
|
||||
oFieldSet_{{ sFormIdSanitized }}.on('form_built', function(oEvent){
|
||||
scrollHandler_{{ sFormIdSanitized }}();
|
||||
});
|
||||
// - Initial test
|
||||
setTimeout(function(){ scrollHandler_{{ sFormIdSanitized }}(); }, 400);
|
||||
|
||||
// Remove sticky button when closing modal
|
||||
$('#{{ sFormId }}').closest('.modal').on('hide.bs.modal', function () {
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.remove();
|
||||
});
|
||||
|
||||
$('#{{ sFormId }}').closest({% if tIsModal == true %}'.modal'{% else %}'#main-content'{% endif %}).append(oStickyRegularButtons_{{ sFormIdSanitized }});
|
||||
|
||||
// - Global timeout for any
|
||||
var oScrollTimeout;
|
||||
// - Scroll handler
|
||||
scrollHandler_{{ sFormIdSanitized }} = function () {
|
||||
if($('#{{ sFormId }} .form_buttons').visible())
|
||||
{
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('closed');
|
||||
}
|
||||
else
|
||||
{
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.removeClass('closed');
|
||||
}
|
||||
};
|
||||
// - Event binding for scroll
|
||||
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('scroll').on('scroll', function () {
|
||||
if (oScrollTimeout) {
|
||||
// Clear the timeout, if one is pending
|
||||
clearTimeout(oScrollTimeout);
|
||||
oScrollTimeout = null;
|
||||
}
|
||||
oScrollTimeout = setTimeout(scrollHandler_{{ sFormIdSanitized }}, 50);
|
||||
});
|
||||
// - Event binding for linkedset collapse
|
||||
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('shown.bs.collapse hidden.bs.collapse').on('shown.bs.collapse hidden.bs.collapse', function () {
|
||||
scrollHandler_{{ sFormIdSanitized }}();
|
||||
});
|
||||
// - Event binding for form building / updating
|
||||
// Note : We do not want to 'off' the event or it will remove listeners from the widget
|
||||
oFieldSet_{{ sFormIdSanitized }}.on('form_built', function(oEvent){
|
||||
scrollHandler_{{ sFormIdSanitized }}();
|
||||
});
|
||||
// - Initial test
|
||||
setTimeout(function(){ scrollHandler_{{ sFormIdSanitized }}(); }, 400);
|
||||
|
||||
// Remove sticky button when closing modal
|
||||
$('#{{ sFormId }}').closest('.modal').on('hide.bs.modal', function () {
|
||||
oStickyRegularButtons_{{ sFormIdSanitized }}.remove();
|
||||
});
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if tIsModal == true %}
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
$(document).ready(function(){
|
||||
showTableLoader();
|
||||
|
||||
|
||||
// Note : Those options should be externalized in an library so we can use them on any DataTables for the portal.
|
||||
// We would just have to override / complete the necessary elements
|
||||
oTable = $('#{{ sTableId }}').DataTable({
|
||||
@@ -152,6 +152,8 @@
|
||||
"url": "{{ app.url_generator.generate('p_object_search_from_attribute', {'sTargetAttCode': sTargetAttCode, 'sHostObjectClass': sHostObjectClass, 'sHostObjectId': sHostObjectId, 'ar_token': sActionRulesToken})|raw }}",
|
||||
"type": "POST",
|
||||
"data": function(d){
|
||||
d.sFormPath = '{{ aSource.sFormPath }}';
|
||||
d.sFieldId = '{{ aSource.sFieldId }}';
|
||||
d.aObjectIdsToIgnore = {{ aSource.aObjectIdsToIgnore|json_encode()|raw }};
|
||||
d.iPageNumber = Math.floor(d.start/d.length) + 1;
|
||||
d.iCountPerPage = d.length;
|
||||
@@ -168,6 +170,24 @@
|
||||
// Retrieving values from source form
|
||||
d.current_values = $('[data-form-path="{{aSource.sFormPath}}"][data-field-id="{{aSource.sFieldId}}"]').closest('.portal_form_handler').portal_form_handler('getCurrentValues');
|
||||
{% endif %}
|
||||
},
|
||||
"error": function(oData, sError, sThrow){
|
||||
if(oData.responseJSON !== undefined && oData.responseJSON !== null)
|
||||
{
|
||||
var oResponse = oData.responseJSON;
|
||||
// If we encounter an error
|
||||
if(oResponse.exception !== undefined)
|
||||
{
|
||||
// Note : This could be refactored for a global use
|
||||
$('#{{ sTableId }}').closest('.modal').html( $('#modal-for-alert').html() );
|
||||
var oModalElem = $('#{{ sTableId }}').closest('.modal');
|
||||
oModalElem.find('.modal-title').html(oResponse.error_title);
|
||||
oModalElem.find('.modal-body .alert').html(oResponse.error_message)
|
||||
.removeClass('alert-success alert-info alert-warning alert-danger')
|
||||
.addClass('alert-danger');
|
||||
oModalElem.modal('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% if form.buttons is defined and form.buttons.links is defined %}
|
||||
<div class="form_btn_transitions">
|
||||
{% for aLink in form.buttons.links %}
|
||||
<a class="btn btn-default" href="{{ aLink.url }}">{{ aLink.label }}</a>
|
||||
<a class="btn btn-primary" href="{{ aLink.url }}">{{ aLink.label }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-default user_profile_picture">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Photo</h3>
|
||||
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Photo:Title'|dict_s }}</h3>
|
||||
</div>
|
||||
<div class="panel-body" style="position: relative;">
|
||||
<div class="form_alerts">
|
||||
@@ -189,11 +189,11 @@
|
||||
});
|
||||
// - Undo button
|
||||
/*$('#user-profile-wrapper .actions .btn_undo').on('click', function(oEvent){
|
||||
console.log('Picture undo trigger');
|
||||
//console.log('Picture undo trigger');
|
||||
});*/
|
||||
// - Reset button
|
||||
$('#user-profile-wrapper .actions .btn_reset').on('click', function(oEvent){
|
||||
console.log('Picture reset trigger');
|
||||
//console.log('Picture reset trigger');
|
||||
});
|
||||
|
||||
// Submit button
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="content_loader">
|
||||
<div class="icon glyphicon glyphicon-refresh"></div>
|
||||
<div class="message">
|
||||
{{ 'Page:PleaseWait'|dict_s }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,83 +22,84 @@
|
||||
{# This block can be used to add your own meta tags by extending the default template #}
|
||||
{% block pPageExtraMetas %}
|
||||
{% endblock %}
|
||||
<title>{% block pPageTitle %}{% if sPageTitle is defined and sPageTitle is not null %}{{ sPageTitle }} - iTop{% else %}{{ 'Page:DefaultTitle'|dict_s }}{% endif %}{% endblock %}</title>
|
||||
<link rel="shortcut icon" href="{{ app['combodo.absolute_url'] }}images/favicon.ico?itopversion=$ITOP_VERSION$" />
|
||||
<title>{% block pPageTitle %}{% if sPageTitle is defined and sPageTitle is not null %}{{ sPageTitle }} - {{ constant('ITOP_APPLICATION') }}{% else %}{{ 'Page:DefaultTitle'|dict_s }}{% endif %}{% endblock %}</title>
|
||||
<link rel="shortcut icon" href="{{ app['combodo.absolute_url'] ~ 'images/favicon.ico'|add_itop_version }}" />
|
||||
{% block pPageStylesheets %}
|
||||
{# First bootstrap core, lib themes, then bootstrap theme, portal adjustements #}
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap/css/bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
{# - Bootstrap Datetime picker #}
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
{# - Datatables #}
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/dataTables.bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/fixedHeader.bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/responsive.bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/scroller.bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/select.bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/select.dataTables.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/dataTables.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/fixedHeader.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/responsive.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/scroller.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/select.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/select.dataTables.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
{# - Font Combodo #}
|
||||
<link href="{{ app['combodo.absolute_url'] }}css/font-combodo/font-combodo.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.absolute_url'] ~ 'css/font-combodo/font-combodo.css'|add_itop_version }}" rel="stylesheet">
|
||||
{# - Font awesome #}
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/font-awesome/css/font-awesome.min.css'|add_itop_version }}" rel="stylesheet">
|
||||
{# - Misc libs #}
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/css/typeaheadjs.bootstrap.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.absolute_url'] }}css/magnific-popup.css" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/css/typeaheadjs.bootstrap.css'|add_itop_version }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.absolute_url'] ~ 'css/magnific-popup.css'|add_itop_version }}" rel="stylesheet">
|
||||
{# - Bootstrap theme #}
|
||||
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.bootstrap }}" rel="stylesheet" id="css_bootstrap_theme">
|
||||
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.bootstrap|add_itop_version }}" rel="stylesheet" id="css_bootstrap_theme">
|
||||
{# - Portal adjustments for BS theme #}
|
||||
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.portal }}" rel="stylesheet" id="css_portal">
|
||||
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.portal|add_itop_version }}" rel="stylesheet" id="css_portal">
|
||||
{# Custom CSS that is supposed to do adjustments to the portal #}
|
||||
{% if app['combodo.portal.instance.conf'].properties.themes.custom is defined %}
|
||||
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom }}" rel="stylesheet">
|
||||
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom|add_itop_version }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
{# Others CSS that will come after the theme/portal/custom, in an undefined order #}
|
||||
{% if app['combodo.portal.instance.conf'].properties.themes.others is defined %}
|
||||
{% for theme in app['combodo.portal.instance.conf'].properties.themes.others %}
|
||||
<link href="{{ theme }}" rel="stylesheet">
|
||||
<link href="{{ theme|add_itop_version }}" rel="stylesheet">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block pPageScripts %}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery/jquery-1.11.3.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery-ui-1.10.3.custom.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.magnific-popup.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.fileupload.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/latinise/latinise.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery/jquery-1.11.3.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery-ui-1.10.3.custom.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.magnific-popup.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.iframe-transport.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.fileupload.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap/js/bootstrap.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/latinise/latinise.min.js'|add_itop_version }}"></script>
|
||||
{# Visible.js to check if an element is visible on screen #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery-visible/js/jquery.visible.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-visible/js/jquery.visible.min.js'|add_itop_version }}"></script>
|
||||
{# Base64.js #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery-base64/js/jquery.base64.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-base64/js/jquery.base64.min.js'|add_itop_version }}"></script>
|
||||
{# Moment.js #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/moment/js/moment.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/moment/js/moment.min.js'|add_itop_version }}"></script>
|
||||
{# Datatables #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/jquery.dataTables.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.fixedHeader.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.responsive.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.scroller.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.select.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/datetime-moment.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/dataTables.accentNeutraliseForFilter.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/jquery.dataTables.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.bootstrap.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.fixedHeader.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.responsive.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.scroller.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.select.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/datetime-moment.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/dataTables.accentNeutraliseForFilter.js'|add_itop_version }}"></script>
|
||||
{# CKEditor files for HTML WYSIWYG #}
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/ckeditor/ckeditor.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/ckeditor/adapters/jquery.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/ckeditor/ckeditor.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/ckeditor/adapters/jquery.js'|add_itop_version }}"></script>
|
||||
{# Date-time picker for Bootstrap #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js'|add_itop_version }}"></script>
|
||||
{# Typeahead files for autocomplete #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/bloodhound.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/typeahead.bundle.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/typeahead.jquery.min.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/handlebars/js/handlebars.min-768ddbd.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/bloodhound.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/typeahead.bundle.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/typeahead.jquery.min.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/handlebars/js/handlebars.min-768ddbd.js'|add_itop_version }}"></script>
|
||||
{# Form files #}
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/form_handler.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/form_field.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/subform_field.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/field_set.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/form_handler.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/form_field.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/subform_field.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/field_set.js'|add_itop_version }}"></script>
|
||||
{# Form files for portal #}
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_handler.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_field.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_field_html.js"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_handler.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field.js'|add_itop_version }}"></script>
|
||||
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field_html.js'|add_itop_version }}"></script>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="{% block pPageBodyClass %}{% endblock %}">
|
||||
@@ -260,12 +261,7 @@
|
||||
<div class="modal fade" id="modal-for-all" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="content_loader">
|
||||
<div class="icon glyphicon glyphicon-refresh"></div>
|
||||
<div class="message">
|
||||
{{ 'Page:PleaseWait'|dict_s }}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -289,12 +285,7 @@
|
||||
|
||||
<div id="page_overlay" class="global_overlay">
|
||||
<div class="overlay_content">
|
||||
<div class="content_loader">
|
||||
<div class="icon glyphicon glyphicon-refresh"></div>
|
||||
<div class="message">
|
||||
{{ 'Page:PleaseWait'|dict_s }}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -305,19 +296,22 @@
|
||||
{
|
||||
return '{{ app['combodo.absolute_url'] }}';
|
||||
};
|
||||
var addParameterToUrl = function(sUrl, sParamName, sParamValue)
|
||||
var AddParameterToUrl = function(sUrl, sParamName, sParamValue)
|
||||
{
|
||||
sUrl += (sUrl.split('?')[1] ? '&':'?') + sParamName + '=' + sParamValue;
|
||||
return sUrl;
|
||||
};
|
||||
var contentLoaderTemplate = '<div class="content_loader"><div class="icon glyphicon glyphicon-refresh"></div><div class="message">{{ 'Page:PleaseWait'|dict_s }}</div></div>';
|
||||
var GetContentLoaderTemplate = function()
|
||||
{
|
||||
return '<div class="content_loader"><div class="icon glyphicon glyphicon-refresh"></div><div class="message">{{ 'Page:PleaseWait'|dict_s }}</div></div>';
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
{% block pPageReadyScripts %}
|
||||
// Hack to enable a same modal to load content from different urls
|
||||
$('body').on('hidden.bs.modal', '.modal#modal-for-all', function () {
|
||||
$(this).removeData('bs.modal');
|
||||
$(this).find('.modal-content').html(contentLoaderTemplate);
|
||||
$(this).find('.modal-content').html(GetContentLoaderTemplate());
|
||||
});
|
||||
// Hack to enable multiple modals by making sure the .modal-open class is set to the <body> when there is at least one modal open left
|
||||
$('body').on('hidden.bs.modal', function () {
|
||||
@@ -335,7 +329,7 @@
|
||||
$('body').on('loaded.bs.modal', function (oEvent) {
|
||||
var sModalContent = $(oEvent.target).find('.modal-content').html();
|
||||
|
||||
if( (sModalContent === '') || (sModalContent.replace(/[\n\r\t]+/g, '') === contentLoaderTemplate) )
|
||||
if( (sModalContent === '') || (sModalContent.replace(/[\n\r\t]+/g, '') === GetContentLoaderTemplate()) )
|
||||
{
|
||||
$(oEvent.target).find('.modal-content').html($('#modal-for-alert .modal-content').html());
|
||||
$(oEvent.target).find('.modal-content .modal-header .modal-title').text('{{ 'Error:HTTP:500'|dict_s }}');
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
{# modal/layout.html.twig #}
|
||||
{# Base modal layout, used to fill Bootstrap .modal-content #}
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">{% block pModalTitle %}{% endblock %}</h4>
|
||||
</div>
|
||||
<div class="modal-body">{% block pModalBody %}{% endblock %}</div>
|
||||
<div class="modal-footer">
|
||||
{% block pModalFooter %}
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Portal:ButtonClose'|dict_s }}</button>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block pModalContent %}
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">{% block pModalTitle %}{% endblock %}</h4>
|
||||
</div>
|
||||
<div class="modal-body">{% block pModalBody %}{% endblock %}</div>
|
||||
<div class="modal-footer">
|
||||
{% block pModalFooter %}
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Portal:ButtonClose'|dict_s }}</button>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{# itop-portal-base/portal/src/views/bricks/object/mode_loader.html.twig #}
|
||||
{# Modal loader layout #}
|
||||
{% extends 'itop-portal-base/portal/src/views/modal/layout.html.twig' %}
|
||||
|
||||
{% block pModalContent %}
|
||||
{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
|
||||
|
||||
{% if redirection is defined and redirection.url is defined %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready( function(){
|
||||
window.location = '{{ redirection.url|raw }}';
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -267,6 +267,10 @@ footer {
|
||||
.mfp-wrap {
|
||||
z-index: 1210;
|
||||
}
|
||||
.mfp-img {
|
||||
cursor: pointer;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
/********************/
|
||||
/* Typeahed setting */
|
||||
/********************/
|
||||
@@ -438,6 +442,10 @@ footer {
|
||||
.dataTables_wrapper {
|
||||
padding: 10px 10px;
|
||||
}
|
||||
.dataTable.table td img {
|
||||
max-width: 100%;
|
||||
height: initial !important;
|
||||
}
|
||||
#brick_content_toolbar {
|
||||
/* margin: 10px 0px 6px 0px; */
|
||||
padding: 10px;
|
||||
@@ -602,6 +610,11 @@ table .group-actions {
|
||||
color: #ea7d1e;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
/* InlineImage */
|
||||
.inline-image {
|
||||
cursor: pointer;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
/* CaseLog field */
|
||||
.caselog_field_entry {
|
||||
border: 1px solid #ddd;
|
||||
@@ -622,6 +635,7 @@ table .group-actions {
|
||||
font-size: 16px;
|
||||
border: 1px solid #a6a6a6;
|
||||
border-bottom-color: #979797;
|
||||
cursor: pointer;
|
||||
}
|
||||
.caselog_field_entry_button:hover {
|
||||
background-color: #ccc;
|
||||
@@ -796,6 +810,10 @@ table .group-actions {
|
||||
}
|
||||
}
|
||||
}
|
||||
/* BlobField */
|
||||
.form_fields .file_open_link {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.form_field .form-control-static img {
|
||||
max-width: 100% !important;
|
||||
height: initial !important;
|
||||
@@ -816,7 +834,8 @@ table .group-actions {
|
||||
@media (min-width: 768px) {
|
||||
/* Making regular button sticky */
|
||||
.form_buttons .form_btn_transitions {
|
||||
float: left !important;
|
||||
float: right !important;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.form_buttons .form_btn_regular {
|
||||
text-align: right;
|
||||
@@ -885,9 +904,17 @@ table .group-actions {
|
||||
border-color: #fbeed5;
|
||||
color: #c09853;
|
||||
}
|
||||
/* CKEditor : Misc */
|
||||
.cke_toolbox_collapser, .cke_toolbox_collapser .cke_arrow {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
/* DataTables : Selection inputs */
|
||||
.dataTable.table th span.row_input, .dataTable.table td span.row_input {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
/* Wiki text (hyperlinks) */
|
||||
.wiki_broken_link {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
@@ -282,6 +282,10 @@ footer{
|
||||
.mfp-wrap{
|
||||
z-index: 1210;
|
||||
}
|
||||
.mfp-img{
|
||||
cursor: pointer;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Typeahed setting */
|
||||
@@ -461,6 +465,10 @@ footer{
|
||||
.dataTables_wrapper{
|
||||
padding: 10px 10px;
|
||||
}
|
||||
.dataTable.table td img{
|
||||
max-width: 100%;
|
||||
height: initial !important;
|
||||
}
|
||||
#brick_content_toolbar{
|
||||
/* margin: 10px 0px 6px 0px; */
|
||||
padding: 10px;
|
||||
@@ -640,6 +648,11 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
color: $brand-primary;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
/* InlineImage */
|
||||
.inline-image{
|
||||
cursor: pointer;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
/* CaseLog field */
|
||||
.caselog_field_entry{
|
||||
border: 1px solid $gray-lighter;
|
||||
@@ -660,6 +673,7 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
font-size: 16px;
|
||||
border: 1px solid #a6a6a6;
|
||||
border-bottom-color: #979797;
|
||||
cursor: pointer;
|
||||
}
|
||||
.caselog_field_entry_button:hover{
|
||||
background-color: #cccccc;
|
||||
@@ -836,6 +850,10 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
}
|
||||
}
|
||||
}
|
||||
/* BlobField */
|
||||
.form_fields .file_open_link{
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.form_field .form-control-static img{
|
||||
max-width: 100% !important;
|
||||
@@ -857,7 +875,8 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
}
|
||||
@media (min-width: 768px){
|
||||
.form_buttons .form_btn_transitions{
|
||||
float: left !important;
|
||||
float: right !important;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.form_buttons .form_btn_regular{
|
||||
text-align: right;
|
||||
@@ -927,6 +946,11 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
border-color: $alert-warning-border;
|
||||
color: $alert-warning-text;
|
||||
}
|
||||
/* CKEditor : Misc */
|
||||
.cke_toolbox_collapser,
|
||||
.cke_toolbox_collapser .cke_arrow{
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
/* DataTables : Selection inputs */
|
||||
.dataTable.table th span.row_input,
|
||||
@@ -934,4 +958,8 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
/* Wiki text (hyperlinks) */
|
||||
.wiki_broken_link {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
@@ -62,6 +62,7 @@ if (!defined('DISABLE_DATA_LOCALIZER_PORTAL'))
|
||||
$bDebug = (isset($_REQUEST['debug']) && ($_REQUEST['debug'] === 'true') );
|
||||
|
||||
// Initializing Silex framework
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oApp = new Silex\Application();
|
||||
|
||||
// Registring optional silex components
|
||||
@@ -76,6 +77,7 @@ $oApp->register(new Silex\Provider\TwigServiceProvider(), array(
|
||||
'twig.path' => MODULESROOT
|
||||
));
|
||||
$oApp->register(new Silex\Provider\HttpFragmentServiceProvider());
|
||||
$oKPI->ComputeAndReport('Initialization of the Silex application');
|
||||
|
||||
// Configuring Silex application
|
||||
$oApp['debug'] = $bDebug;
|
||||
@@ -91,17 +93,27 @@ $oApp['combodo.portal.instance.routes'] = array();
|
||||
ApplicationHelper::RegisterExceptionHandler($oApp);
|
||||
|
||||
// Preparing portal foundations (Can't use Silex autoload through composer as we don't follow PSR conventions -filenames, functions-)
|
||||
$oKPI = new ExecutionKPI();
|
||||
ApplicationHelper::LoadControllers();
|
||||
ApplicationHelper::LoadRouters();
|
||||
ApplicationHelper::RegisterRoutes($oApp);
|
||||
ApplicationHelper::LoadBricks();
|
||||
ApplicationHelper::LoadFormManagers();
|
||||
ApplicationHelper::RegisterTwigExtensions($oApp);
|
||||
$oKPI->ComputeAndReport('Loading portal files (routers, controllers, ...)');
|
||||
|
||||
// Loading portal configuration from the module design
|
||||
$oKPI = new ExecutionKPI();
|
||||
ApplicationHelper::LoadPortalConfiguration($oApp);
|
||||
$oKPI->ComputeAndReport('Parsing portal configuration');
|
||||
// Loading current user
|
||||
ApplicationHelper::LoadCurrentUser($oApp);
|
||||
|
||||
// Running application
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oApp->run();
|
||||
$oKPI->ComputeAndReport('Page execution and rendering');
|
||||
|
||||
// Logging trace and stats
|
||||
DBSearch::RecordQueryTrace();
|
||||
ExecutionKPI::ReportStats();
|
||||
@@ -171,7 +171,13 @@ $(function()
|
||||
oModalElem.attr('id', '').appendTo('body');
|
||||
// Loading content
|
||||
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
|
||||
oModalElem.find('.modal-content').load(sUrl);
|
||||
oModalElem.find('.modal-content').load(sUrl, {
|
||||
// Passing formmanager data to the next page, just in case it needs it (eg. when applying stimulus)
|
||||
formmanager_class: me.options.formmanager_class,
|
||||
formmanager_data: JSON.stringify(me.options.formmanager_data)
|
||||
}
|
||||
);
|
||||
|
||||
oModalElem.modal('show');
|
||||
}
|
||||
else
|
||||
|
||||
@@ -37,4 +37,5 @@ $sDir = basename(__DIR__);
|
||||
define('PORTAL_MODULE_ID', $sDir);
|
||||
define('PORTAL_ID', $sDir);
|
||||
|
||||
ApplicationContext::SetUrlMakerClass('iTopPortalViewUrlMaker');
|
||||
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/index.php';
|
||||
|
||||
@@ -22,20 +22,26 @@
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
*/
|
||||
class iTopPortalUrlMaker implements iDBObjectURLMaker
|
||||
class iTopPortalEditUrlMaker implements iDBObjectURLMaker
|
||||
{
|
||||
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
/**
|
||||
* Generate an (absolute) URL to an object, either in view or edit mode
|
||||
* @param string $sClass The class of the object
|
||||
* @param int $iId The identifier of the object
|
||||
* @param string $sMode edit|view
|
||||
* @return string
|
||||
*/
|
||||
public static function PrepareObjectURL($sClass, $iId, $sMode)
|
||||
{
|
||||
require_once APPROOT . '/lib/silex/vendor/autoload.php';
|
||||
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/src/providers/urlgeneratorserviceprovider.class.inc.php';
|
||||
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/src/helpers/urlgeneratorhelper.class.inc.php';
|
||||
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/src/helpers/applicationhelper.class.inc.php';
|
||||
|
||||
|
||||
// Using a static var allows to preserve the object through function calls
|
||||
static $oApp = null;
|
||||
static $sPortalId = null;
|
||||
|
||||
|
||||
// Initializing Silex app
|
||||
if ($oApp === null)
|
||||
{
|
||||
@@ -49,15 +55,51 @@ class iTopPortalUrlMaker implements iDBObjectURLMaker
|
||||
// Retrieving portal id
|
||||
$sPortalId = basename(__DIR__);
|
||||
}
|
||||
|
||||
$sObjectQueryString = $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sClass, 'sObjectId' => $iId));
|
||||
$sPortalAbsoluteUrl = utils::GetAbsoluteUrlModulePage($sPortalId, 'index.php');
|
||||
$sUrl = str_replace('?', $sObjectQueryString . '?', $sPortalAbsoluteUrl);
|
||||
// The object is reachable in the specified mode (edit/view)
|
||||
switch($sMode)
|
||||
{
|
||||
case 'view':
|
||||
$sObjectQueryString = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $sClass, 'sObjectId' => $iId));
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
default:
|
||||
$sObjectQueryString = $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sClass, 'sObjectId' => $iId));
|
||||
}
|
||||
|
||||
$sPortalAbsoluteUrl = utils::GetAbsoluteUrlModulePage($sPortalId, 'index.php');
|
||||
if (strpos($sPortalAbsoluteUrl, '?') !== false)
|
||||
{
|
||||
$sUrl = substr($sPortalAbsoluteUrl, 0, strpos($sPortalAbsoluteUrl, '?')).$sObjectQueryString.substr($sPortalAbsoluteUrl, strpos($sPortalAbsoluteUrl, '?'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sPortalAbsoluteUrl.$sObjectQueryString;
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
{
|
||||
return static::PrepareObjectURL($sClass, $iId, 'edit');
|
||||
}
|
||||
}
|
||||
|
||||
DBObject::RegisterURLMakerClass('portal', 'iTopPortalUrlMaker');
|
||||
/**
|
||||
* Hyperlinks to the "view" of the object (vs edition)
|
||||
* @author denis
|
||||
*
|
||||
*/
|
||||
class iTopPortalViewUrlMaker extends iTopPortalEditUrlMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
{
|
||||
return static::PrepareObjectURL($sClass, $iId, 'view');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Default portal hyperlink (for notifications) is the edit hyperlink
|
||||
DBObject::RegisterURLMakerClass('portal', 'iTopPortalEditUrlMaker');
|
||||
|
||||
|
||||
@@ -1325,7 +1325,7 @@
|
||||
<code><![CDATA[ public function UpdateChildRequestLog()
|
||||
{
|
||||
$oLog = $this->Get('public_log');
|
||||
$sLogPublic = $oLog->GetModifiedEntry();
|
||||
$sLogPublic = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPublic != '')
|
||||
{
|
||||
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
|
||||
@@ -1343,7 +1343,7 @@
|
||||
|
||||
}
|
||||
$oLog = $this->Get('private_log');
|
||||
$sLogPrivate = $oLog->GetModifiedEntry();
|
||||
$sLogPrivate = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPrivate != '')
|
||||
{
|
||||
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
|
||||
|
||||
@@ -1338,7 +1338,7 @@
|
||||
<code><![CDATA[ public function UpdateChildRequestLog()
|
||||
{
|
||||
$oLog = $this->Get('public_log');
|
||||
$sLogPublic = $oLog->GetModifiedEntry();
|
||||
$sLogPublic = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPublic != '')
|
||||
{
|
||||
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
|
||||
@@ -1356,7 +1356,7 @@
|
||||
|
||||
}
|
||||
$oLog = $this->Get('private_log');
|
||||
$sLogPrivate = $oLog->GetModifiedEntry();
|
||||
$sLogPrivate = $oLog->GetModifiedEntry('html');
|
||||
if ($sLogPrivate != '')
|
||||
{
|
||||
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
|
||||
|
||||
@@ -698,7 +698,7 @@
|
||||
<code><![CDATA[ public function UpdateParentTicketLog()
|
||||
{
|
||||
$oLog = $this->Get('log');
|
||||
$sLog = $oLog->GetModifiedEntry();
|
||||
$sLog = $oLog->GetModifiedEntry('html');
|
||||
if ($sLog != '')
|
||||
{
|
||||
$oTicket = MetaModel::GetObject('Ticket', $this->Get('ticket_id'), false);
|
||||
@@ -998,7 +998,7 @@
|
||||
<default>fa fa-user fa-2x</default>
|
||||
</decoration_class>
|
||||
<form>
|
||||
<!-- Optionnal tag to list the fields -->
|
||||
<!-- Optionnal tag to list the fields. If empty only fields from <twig> tag will be displayed, if ommited fields from zlist details will. -->
|
||||
<fields />
|
||||
<!-- Optionnal tag to specify the form layout. Fields that are not positionned in the layout will be placed at the end of the form -->
|
||||
<twig>
|
||||
@@ -1052,7 +1052,7 @@
|
||||
<!-- Description text from attribute of above class [from the OQL] -->
|
||||
<tooltip_att>description</tooltip_att>
|
||||
<!-- Title of the level, will be display in lists and others browse modes -->
|
||||
<title>Service</title>
|
||||
<title>Class:Service</title>
|
||||
<!-- Optional tag to add attributes to the table by their code, can be specified for each level -->
|
||||
<!-- <fields /> -->
|
||||
<!-- Can be empty on intermediate levels, default is drilldown -->
|
||||
@@ -1066,7 +1066,7 @@
|
||||
<parent_att>service_id</parent_att>
|
||||
<name_att/>
|
||||
<tooltip_att>description</tooltip_att>
|
||||
<title>Sous-Service</title>
|
||||
<title>Class:ServiceSubcategory</title>
|
||||
<actions>
|
||||
<action id="view" xsi:type="view"/>
|
||||
<action id="create_from_this" xsi:type="create_from_this">
|
||||
@@ -1130,9 +1130,9 @@
|
||||
</fields>
|
||||
<!-- Optional tag to add attributes to the table by their code -->
|
||||
<grouping>
|
||||
<!-- Optionnal -->
|
||||
<!-- Mandatory -->
|
||||
<tabs>
|
||||
<!-- Optionnal. Grouping by tabs -->
|
||||
<!-- Mandatory. Grouping by tabs -->
|
||||
<!--<attribute>operational_status</attribute>-->
|
||||
<!-- attribute xor groups tag -->
|
||||
<groups>
|
||||
@@ -1145,7 +1145,7 @@
|
||||
<group id="resolved">
|
||||
<rank>2</rank>
|
||||
<title>Brick:Portal:OngoingRequests:Tab:Resolved</title>
|
||||
<condition><![CDATA[SELECT Ticket AS T WHERE org_id = :current_contact->org_id AND operational_status = 'resolved']]></condition>
|
||||
<condition><![CDATA[SELECT Ticket AS T WHERE operational_status = 'resolved']]></condition>
|
||||
</group>
|
||||
</groups>
|
||||
</tabs>
|
||||
@@ -1169,7 +1169,7 @@
|
||||
<decoration_class>
|
||||
<default>fc fc-closed-request fc-2x</default>
|
||||
</decoration_class>
|
||||
<oql><![CDATA[SELECT UserRequest WHERE org_id = :current_contact->org_id AND caller_id = :current_contact_id AND status = 'closed']]></oql>
|
||||
<oql><![CDATA[SELECT Ticket WHERE org_id = :current_contact->org_id AND caller_id = :current_contact_id AND operational_status = 'closed']]></oql>
|
||||
<!-- Can be either a class tag with the class name or an oql tag with the query -->
|
||||
<!-- <class>Ticket</class> -->
|
||||
<fields>
|
||||
@@ -1181,6 +1181,18 @@
|
||||
<field id="priority"/>
|
||||
<field id="caller_id"/>
|
||||
</fields>
|
||||
<grouping>
|
||||
<tabs>
|
||||
<groups>
|
||||
<group id="all">
|
||||
<rank>1</rank>
|
||||
<title>Brick:Portal:ClosedRequests:Title</title>
|
||||
<condition><![CDATA[SELECT Ticket]]></condition>
|
||||
</group>
|
||||
</groups>
|
||||
</tabs>
|
||||
<!-- Implicit grouping on y axis by finalclass -->
|
||||
</grouping>
|
||||
<data_loading>auto</data_loading>
|
||||
</brick>
|
||||
</bricks>
|
||||
@@ -1309,6 +1321,21 @@
|
||||
<mode id="view"/>
|
||||
</modes>
|
||||
</form>
|
||||
<form id="ticket-apply-stimulus">
|
||||
<class>Ticket</class>
|
||||
<fields />
|
||||
<twig>
|
||||
<div>
|
||||
<div class="form_field" data-field-id="team_id" data-field-flags="read_only">
|
||||
</div>
|
||||
<div class="form_field" data-field-id="agent_id" data-field-flags="read_only">
|
||||
</div>
|
||||
</div>
|
||||
</twig>
|
||||
<modes>
|
||||
<mode id="apply_stimulus"/>
|
||||
</modes>
|
||||
</form>
|
||||
<form id="service-view">
|
||||
<class>Service</class>
|
||||
<fields></fields>
|
||||
@@ -1359,6 +1386,13 @@
|
||||
</scope>
|
||||
</scopes>
|
||||
</class>
|
||||
<class id="Location">
|
||||
<scopes>
|
||||
<scope id="all">
|
||||
<oql_view><![CDATA[SELECT Location WHERE org_id = :current_contact->org_id]]></oql_view>
|
||||
</scope>
|
||||
</scopes>
|
||||
</class>
|
||||
<class id="Contact">
|
||||
<scopes>
|
||||
<scope id="all">
|
||||
@@ -1391,6 +1425,7 @@
|
||||
<scopes>
|
||||
<scope id="all">
|
||||
<oql_view><![CDATA[SELECT ServiceFamily AS sf JOIN Service AS s ON s.servicefamily_id = sf.id JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id]]></oql_view>
|
||||
<ignore_silos>true</ignore_silos>
|
||||
</scope>
|
||||
</scopes>
|
||||
</class>
|
||||
@@ -1398,6 +1433,7 @@
|
||||
<scopes>
|
||||
<scope id="all">
|
||||
<oql_view><![CDATA[SELECT Service AS s JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id AND s.status != 'obsolete']]></oql_view>
|
||||
<ignore_silos>true</ignore_silos>
|
||||
</scope>
|
||||
</scopes>
|
||||
</class>
|
||||
@@ -1405,6 +1441,7 @@
|
||||
<scopes>
|
||||
<scope id="all">
|
||||
<oql_view><![CDATA[SELECT ServiceSubcategory AS ssc JOIN Service AS s ON ssc.service_id=s.id JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id AND ssc.status != 'obsolete']]></oql_view>
|
||||
<ignore_silos>true</ignore_silos>
|
||||
</scope>
|
||||
</scopes>
|
||||
</class>
|
||||
|
||||
@@ -185,5 +185,14 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'Zielvorgabe (SLT) vom Typ TTO',
|
||||
'Class:ResponseTicketTTR/Interface:iMetricComputer' => 'Time To Resolve (Erstlösungszeit)',
|
||||
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'Zielvorgabe (SLT) vom Typ TTR',
|
||||
'portal:itop-portal' => 'Standard Portal', // This is the portal name that will be displayed in portal dispatcher (eg. URL in menus)
|
||||
'Brick:Portal:UserProfile:Title' => 'Mein Profile',
|
||||
'Brick:Portal:NewRequest:Title' => 'Neue Störung/Anfrage',
|
||||
'Brick:Portal:NewRequest:Title+' => '<p>Hilfe?</p><p>Wählen Sie einen Service aus und senden Sie Ihre Anfrage zum Service Desk.</p>',
|
||||
'Brick:Portal:OngoingRequests:Title' => 'Offene Störungen/Anfragen',
|
||||
'Brick:Portal:OngoingRequests:Title+' => '<p>Hier können Sie Ihre laufenden Anfragen und Störungsmeldungen ansehen,</p><p>den Verlauf verfolgen, Kommentare und Anhänge hinzufügen und gelöste Anfragen schließen.</p>',
|
||||
'Brick:Portal:OngoingRequests:Tab:OnGoing' => 'Offen',
|
||||
'Brick:Portal:OngoingRequests:Tab:Resolved' => 'Gelöst',
|
||||
'Brick:Portal:ClosedRequests:Title' => 'Geschlossene Störungen/Anfragen',
|
||||
));
|
||||
?>
|
||||
@@ -231,6 +231,16 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'Goal based on a SLT of type TTO~~',
|
||||
'Class:ResponseTicketTTR/Interface:iMetricComputer' => 'Time To Resolve~~',
|
||||
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'Goal based on a SLT of type TTR~~',
|
||||
'portal:itop-portal' => 'Portal estándar',
|
||||
'Page:DefaultTitle' => 'ITop - Portal de usuarios',
|
||||
'Brick:Portal:UserProfile:Title' => 'Mi perfil',
|
||||
'Brick:Portal:NewRequest:Title' => 'Nueva solicitud',
|
||||
'Brick:Portal:NewRequest:Title+' => '¿Necesita ayuda? Elija del catálogo de servicios y envíe su solicitud a nuestros equipos de soporte.',
|
||||
'Brick:Portal:OngoingRequests:Title' => 'Solicitudes en curso',
|
||||
'Brick:Portal:OngoingRequests:Title+' => 'Revise sus peticiones en curso. Compruebe el progreso, agregue comentarios, adjunte documentos, entienda la solución. </ P>',
|
||||
'Brick:Portal:OngoingRequests:Tab:OnGoing' => 'En marcha',
|
||||
'Brick:Portal:OngoingRequests:Tab:Resolved' => 'Resuelto',
|
||||
'Brick:Portal:ClosedRequests:Title' => 'Solicitudes cerradas',
|
||||
));
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -260,7 +260,7 @@ class _Ticket extends cmdbAbstractObject
|
||||
|
||||
case 'Contact':
|
||||
// Only link Contacts which are not already linked to the ticket
|
||||
if (!array_key_exists($iKey, $aContactsToRoleCode) || ($aCIsToImpactCode[$iKey] != 'do_not_notify'))
|
||||
if (!array_key_exists($iKey, $aContactsToRoleCode) || ($aContactsToRoleCode[$iKey] != 'do_not_notify'))
|
||||
{
|
||||
$oNewLink = new lnkContactToTicket();
|
||||
$oNewLink->Set('contact_id', $iKey);
|
||||
|
||||
@@ -502,11 +502,11 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'UI:ResetPwd-Ready' => 'La contraseña ha sido cambiada.',
|
||||
'UI:ResetPwd-Login' => 'Click aquí para conectarse ',
|
||||
'UI:Login:About' => '',
|
||||
'UI:Login:ChangeYourPassword' => 'Cambie su Contraseña',
|
||||
'UI:Login:OldPasswordPrompt' => 'Contraseña Actual',
|
||||
'UI:Login:NewPasswordPrompt' => 'Contraseña Nueva',
|
||||
'UI:Login:RetypeNewPasswordPrompt' => 'Confirme Contraseña Nueva',
|
||||
'UI:Login:IncorrectOldPassword' => 'Error: la Contraseña Anterior es Incorrecta',
|
||||
'UI:Login:ChangeYourPassword' => 'Cambie su Contraseña',
|
||||
'UI:Login:OldPasswordPrompt' => 'Contraseña Actual',
|
||||
'UI:Login:NewPasswordPrompt' => 'Contraseña Nueva',
|
||||
'UI:Login:RetypeNewPasswordPrompt' => 'Confirme Contraseña Nueva',
|
||||
'UI:Login:IncorrectOldPassword' => 'Error: la Contraseña Anterior es Incorrecta',
|
||||
'UI:LogOffMenu' => 'Cerrar Sesión',
|
||||
'UI:LogOff:ThankYou' => 'Gracias por usar iTop',
|
||||
'UI:LogOff:ClickHereToLoginAgain' => 'Click aquí para conectarse nuevamente',
|
||||
|
||||
@@ -1,6 +1,125 @@
|
||||
CKEditor 4 Changelog
|
||||
====================
|
||||
|
||||
## CKEditor 4.6
|
||||
|
||||
New Features:
|
||||
|
||||
* [#14569](http://dev.ckeditor.com/ticket/14569): Added a new, flat, default CKEditor skin called [Moono-Lisa](http://ckeditor.com/addon/moono-lisa). Refreshed default colors available in the [Color Button](http://ckeditor.com/addon/colorbutton) plugin ([Text Color and Background Color](http://docs.ckeditor.com/#!/guide/dev_colorbutton) feature).
|
||||
* [#14707](http://dev.ckeditor.com/ticket/14707): Added a new [Copy Formatting](http://ckeditor.com/addon/copyformatting) feature to enable easy copying of styles between your document parts.
|
||||
* Introduced the completely rewritten [Paste from Word](http://ckeditor.com/addon/pastefromword) plugin:
|
||||
* Backward incompatibility: The [`config.pasteFromWordRemoveFontStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveFontStyles) option now defaults to `false`. This option will be deprecated in the future. Use [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_acf) to replicate the effect of setting it to `true`.
|
||||
* Backward incompatibility: The [`config.pasteFromWordNumberedHeadingToList`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordNumberedHeadingToList) and [`config.pasteFromWordRemoveStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveStyles) options were dropped and no longer have any effect on pasted content.
|
||||
* Major improvements in preservation of list numbering, styling and indentation (nested lists with multiple levels).
|
||||
* Major improvements in document structure parsing that fix plenty of issues with distorted or missing content after paste.
|
||||
* Added new translation: Occitan. Thanks to Cédric Valmary!
|
||||
* [#10015](http://dev.ckeditor.com/ticket/10015): Keyboard shortcuts (relevant to the operating system in use) will now be displayed in tooltips and context menus.
|
||||
* [#13794](http://dev.ckeditor.com/ticket/13794): The [Upload Image](http://ckeditor.com/addon/uploadimage) feature now uses `uploaded.width/height` if set.
|
||||
* [#12541](http://dev.ckeditor.com/ticket/12541): Added the [Upload File](http://ckeditor.com/addon/uploadfile) plugin that lets you upload a file by drag&dropping it into the editor content.
|
||||
* [#14449](http://dev.ckeditor.com/ticket/14449): Introduced the [Balloon Panel](http://ckeditor.com/addon/balloonpanel) plugin that lets you create stylish floating UI elements for the editor.
|
||||
* [#12077](https://dev.ckeditor.com/ticket/12077): Added support for the HTML5 `download` attribute in link (`<a>`) elements. Selecting the "Force Download" checkbox in the [Link](http://ckeditor.com/addon/link) dialog will cause the linked file to be downloaded automatically. Thanks to [sbusse](https://github.com/sbusse)!
|
||||
* [#13518](http://dev.ckeditor.com/ticket/13518): Introduced the [`additionalRequestParameters`](http://docs.ckeditor.com/#!/api/CKEDITOR.fileTools.uploadWidgetDefinition-property-additionalRequestParameters) property for file uploads to make it possible to send additional information about the uploaded file to the server.
|
||||
* [#14889](http://dev.ckeditor.com/ticket/14889): Added the [`config.image2_altRequired`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_altRequired) option for the [Enhanced Image](http://ckeditor.com/addon/image2) plugin to allow making alternative text a mandatory field. Thanks to [Andrey Fedoseev](https://github.com/andreyfedoseev)!
|
||||
|
||||
Fixed Issues:
|
||||
|
||||
* [#9991](http://dev.ckeditor.com/ticket/9991): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) should only normalize input data.
|
||||
* [#7209](http://dev.ckeditor.com/ticket/7209): Fixed: Lists with 3 levels not [pasted from Word](http://ckeditor.com/addon/pastefromword) correctly.
|
||||
* [#14335](http://dev.ckeditor.com/ticket/14335): Fixed: Pasting a numbered list starting with a value different from "1" from Microsoft Word does not work correctly.
|
||||
* [#14542](http://dev.ckeditor.com/ticket/14542): Fixed: Copying a numbered list from Microsoft Word does not preserve list formatting.
|
||||
* [#14544](http://dev.ckeditor.com/ticket/14544): Fixed: Copying a nested list from Microsoft Word results in an empty list.
|
||||
* [#14660](http://dev.ckeditor.com/ticket/14660): Fixed: [Pasting text from Word](http://ckeditor.com/addon/pastefromword) breaks the styling in some cases.
|
||||
* [#14867](http://dev.ckeditor.com/ticket/14867): [Firefox] Fixed: Text gets stripped when [pasting content from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#2507](http://dev.ckeditor.com/ticket/2507): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) does not detect pasting a part of a paragraph.
|
||||
* [#3336](http://dev.ckeditor.com/ticket/3336): Fixed: Extra blank row added on top of the content [pasted from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#6115](http://dev.ckeditor.com/ticket/6115): Fixed: When Right-to-Left text direction is applied to a table [pasted from Word](http://ckeditor.com/addon/pastefromword), borders are missing on one side.
|
||||
* [#6342](http://dev.ckeditor.com/ticket/6342): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) filters out a basic text style when it is [configured to use attributes](http://docs.ckeditor.com/#!/guide/dev_basicstyles-section-custom-basic-text-style-definition).
|
||||
* [#6457](http://dev.ckeditor.com/ticket/6457): [IE] Fixed: [Pasting from Word](http://ckeditor.com/addon/pastefromword) is extremely slow.
|
||||
* [#6789](http://dev.ckeditor.com/ticket/6789): Fixed: The `mso-list: ignore` style is not handled properly when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#7262](http://dev.ckeditor.com/ticket/7262): Fixed: Lists in preformatted body disappear when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#7662](http://dev.ckeditor.com/ticket/7662): [Opera] Fixed: Extra empty number/bullet shown in the editor body when editing a multi-level list [pasted from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#7807](http://dev.ckeditor.com/ticket/7807): Fixed: Last item in a list not converted to a `<li>` element after [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#7950](http://dev.ckeditor.com/ticket/7950): [IE] Fixed: Content [from Word pasted](http://ckeditor.com/addon/pastefromword) differently than in other browsers.
|
||||
* [#7982](http://dev.ckeditor.com/ticket/7982): Fixed: Multi-level lists get split into smaller ones when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#8231](http://dev.ckeditor.com/ticket/8231): [WebKit, Opera] Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) inserts empty paragraphs.
|
||||
* [#8266](http://dev.ckeditor.com/ticket/8266): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) inserts a blank line at the top.
|
||||
* [#8341](http://dev.ckeditor.com/ticket/8341), [#7646](http://dev.ckeditor.com/ticket/7646): Fixed: Faulty removal of empty `<span>` elements in [Paste from Word](http://ckeditor.com/addon/pastefromword) content cleanup breaking content formatting.
|
||||
* [#8754](http://dev.ckeditor.com/ticket/8754): [Firefox] Fixed: Incorrect pasting of multiple nested lists in [Paste from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#8983](http://dev.ckeditor.com/ticket/8983): Fixed: Alignment lost when [pasting from Word](http://ckeditor.com/addon/pastefromword) with [`config.enterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode) set to [`CKEDITOR.ENTER_BR`](http://docs.ckeditor.com/#!/api/CKEDITOR-property-ENTER_BR).
|
||||
* [#9331](http://dev.ckeditor.com/ticket/9331): [IE] Fixed: [Pasting text from Word](http://ckeditor.com/addon/pastefromword) creates a simple Caesar cipher.
|
||||
* [#9422](http://dev.ckeditor.com/ticket/9422): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) leaves an unwanted `color:windowtext` style.
|
||||
* [#10011](http://dev.ckeditor.com/ticket/10011): [IE9-10] Fixed: [`config.pasteFromWordRemoveFontStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveFontStyles) is ignored under certain conditions.
|
||||
* [#10643](http://dev.ckeditor.com/ticket/10643): Fixed: Differences between using <kbd>Ctrl+V</kbd> and pasting from the [Paste from Word](http://ckeditor.com/addon/pastefromword) dialog.
|
||||
* [#10784](http://dev.ckeditor.com/ticket/10784): Fixed: Lines missing when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#11294](http://dev.ckeditor.com/ticket/11294): [IE10] Fixed: Font size is not preserved when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#11627](http://dev.ckeditor.com/ticket/11627): Fixed: Missing words when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#12784](http://dev.ckeditor.com/ticket/12784): Fixed: Bulleted list with custom bullets gets changed to a numbered list when [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#13174](http://dev.ckeditor.com/ticket/13174): Fixed: Data loss after [pasting from Word](http://ckeditor.com/addon/pastefromword).
|
||||
* [#13828](http://dev.ckeditor.com/ticket/13828): Fixed: Widget classes should be added to the wrapper rather than the widget element.
|
||||
* [#13829](http://dev.ckeditor.com/ticket/13829): Fixed: No class in [Widget](http://ckeditor.com/addon/widget) wrapper to identify the widget type.
|
||||
* [#13519](http://dev.ckeditor.com/ticket/13519): Server response received when uploading files should be more flexible.
|
||||
|
||||
Other Changes:
|
||||
|
||||
* Updated [SCAYT](http://ckeditor.com/addon/scayt) (Spell Check As You Type) and [WebSpellChecker](http://ckeditor.com/addon/wsc) plugins:
|
||||
* Support for the new default Moono-Lisa skin.
|
||||
* [#121](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/121): Fixed: [Basic Styles](http://ckeditor.com/addon/basicstyles) do not work when SCAYT is enabled.
|
||||
* [#125](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/125): Fixed: Inline styles are not continued when writing multiple lines of styled text with SCAYT enabled.
|
||||
* [#127](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/127): Fixed: Uncaught TypeError after enabling SCAYT in the CKEditor `<div>` element.
|
||||
* [#128](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/128): Fixed: Error thrown after enabling SCAYT caused by conflicts with RequireJS.
|
||||
|
||||
## CKEditor 4.5.11
|
||||
|
||||
**Security Updates:**
|
||||
|
||||
* [Severity: minor] Fixed the `target="_blank"` vulnerability reported by James Gaskell.
|
||||
|
||||
Issue summary: If a victim had access to a spoofed version of ckeditor.com via HTTP (e.g. due to DNS spoofing, using a hacked public network or mailicious hotspot), then when using a link to the ckeditor.com website it was possible for the attacker to change the current URL of the opening page, even if the opening page was protected with SSL.
|
||||
|
||||
An upgrade is recommended.
|
||||
|
||||
New Features:
|
||||
|
||||
* [#14747](http://dev.ckeditor.com/ticket/14747): The [Enhanced Image](http://ckeditor.com/addon/image2) caption now supports the link `target` attribute.
|
||||
* [#7154](http://dev.ckeditor.com/ticket/7154): Added support for the "Display Text" field to the [Link](http://ckeditor.com/addon/link) dialog. Thanks to [Ryan Guill](https://github.com/ryanguill)!
|
||||
|
||||
Fixed Issues:
|
||||
|
||||
* [#13362](http://dev.ckeditor.com/ticket/13362): [Blink, WebKit] Fixed: Active widget element is not cached when it is losing focus and it is inside an editable element.
|
||||
* [#13755](http://dev.ckeditor.com/ticket/13755): [Edge] Fixed: Pasting images does not work.
|
||||
* [#13548](http://dev.ckeditor.com/ticket/13548): [IE] Fixed: Clicking the [elements path](http://ckeditor.com/addon/elementspath) disables Cut and Copy icons.
|
||||
* [#13812](http://dev.ckeditor.com/ticket/13812): Fixed: When aborting file upload the placeholder for image is left.
|
||||
* [#14659](http://dev.ckeditor.com/ticket/14659): [Blink] Fixed: Content scrolled to the top after closing the dialog in a [`<div>`-based editor](http://ckeditor.com/addon/divarea).
|
||||
* [#14825](http://dev.ckeditor.com/ticket/14825): [Edge] Fixed: Focusing the editor causes unwanted scrolling due to dropped support for the `setActive` method.
|
||||
|
||||
## CKEditor 4.5.10
|
||||
|
||||
Fixed Issues:
|
||||
|
||||
* [#10750](http://dev.ckeditor.com/ticket/10750): Fixed: The editor does not escape the `font-style` family property correctly, removing quotes and whitespace from font names.
|
||||
* [#14413](http://dev.ckeditor.com/ticket/14413): Fixed: The [Auto Grow](http://ckeditor.com/addon/autogrow) plugin with the [`config.autoGrow_onStartup`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-autoGrow_onStartup) option set to `true` does not work properly for an editor that is not visible.
|
||||
* [#14451](http://dev.ckeditor.com/ticket/14451): Fixed: Numeric element ID not escaped properly. Thanks to [Jakub Chalupa](https://github.com/chaluja7)!
|
||||
* [#14590](http://dev.ckeditor.com/ticket/14590): Fixed: Additional line break appearing after inline elements when switching modes. Thanks to [dpidcock](https://github.com/dpidcock)!
|
||||
* [#14539](https://dev.ckeditor.com/ticket/14539): Fixed: JAWS reads "selected Blank" instead of "selected <widget name>" when selecting a widget.
|
||||
* [#14701](http://dev.ckeditor.com/ticket/14701): Fixed: More precise labels for [Enhanced Image](http://ckeditor.com/addon/image2) and [Placeholder](http://ckeditor.com/addon/placeholder) widgets.
|
||||
* [#14667](http://dev.ckeditor.com/ticket/14667): [IE] Fixed: Removing background color from selected text removes background color from the whole paragraph.
|
||||
* [#14252](http://dev.ckeditor.com/ticket/14252): [IE] Fixed: Styles drop-down list does not always reflect the current style of the text line.
|
||||
* [#14275](http://dev.ckeditor.com/ticket/14275): [IE9+] Fixed: `onerror` and `onload` events are not used in browsers it could have been used when loading scripts dynamically.
|
||||
|
||||
## CKEditor 4.5.9
|
||||
|
||||
Fixed Issues:
|
||||
|
||||
* [#10685](http://dev.ckeditor.com/ticket/10685): Fixed: Unreadable toolbar icons after updating to the new editor version. Fixed with [6876179](https://github.com/ckeditor/ckeditor-dev/commit/6876179db4ee97e786b07b8fd72e6b4120732185) in [ckeditor-dev](https://github.com/ckeditor/ckeditor-dev) and [6c9189f4](https://github.com/ckeditor/ckeditor-presets/commit/6c9189f46392d2c126854fe8889b820b8c76d291) in [ckeditor-presets](https://github.com/ckeditor/ckeditor-presets).
|
||||
* [#14573](https://dev.ckeditor.com/ticket/14573): Fixed: Missing [Widget](http://ckeditor.com/addon/widget) drag handler CSS when there are multiple editor instances.
|
||||
* [#14620](https://dev.ckeditor.com/ticket/14620): Fixed: Setting both the `min-height` style for the `<body>` element and the `height` style for the `<html>` element breaks the [Auto Grow](http://ckeditor.com/addon/autogrow) plugin.
|
||||
* [#14538](http://dev.ckeditor.com/ticket/14538): Fixed: Keyboard focus goes into an embedded `<iframe>` element.
|
||||
* [#14602](http://dev.ckeditor.com/ticket/14602): Fixed: The [`dom.element.removeAttribute()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-removeAttribute) method does not remove all attributes if no parameter is given.
|
||||
* [#8679](http://dev.ckeditor.com/ticket/8679): Fixed: Better focus indication and ability to style the selected color in the [color picker dialog](http://ckeditor.com/addon/colordialog).
|
||||
* [#11697](http://dev.ckeditor.com/ticket/11697): Fixed: Content is replaced ignoring the letter case setting in the [Find and Replace](http://ckeditor.com/addon/find) dialog window.
|
||||
* [#13886](http://dev.ckeditor.com/ticket/13886): Fixed: Invalid handling of the [`CKEDITOR.style`](http://docs.ckeditor.com/#!/api/CKEDITOR.style) instance with the `styles` property by [`CKEDITOR.filter`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter).
|
||||
* [#14535](http://dev.ckeditor.com/ticket/14535): Fixed: CSS syntax corrections. Thanks to [mdjdenormandie](https://github.com/mdjdenormandie)!
|
||||
|
||||
## CKEditor 4.5.8
|
||||
|
||||
New Features:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
CKEditor 4
|
||||
==========
|
||||
|
||||
Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
|
||||
Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
|
||||
http://ckeditor.com - See LICENSE.md for license information.
|
||||
|
||||
CKEditor is a text editor to be used inside web pages. It's not a replacement
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
* (1) http://ckeditor.com/builder
|
||||
* Visit online builder to build CKEditor from scratch.
|
||||
*
|
||||
* (2) http://ckeditor.com/builder/a3aa36a1c49fac96ad54315f97e9e05c
|
||||
* (2) http://ckeditor.com/builder/d5ff452dc2f39a022cdbb070ac7e5e57
|
||||
* Visit online builder to build CKEditor, starting with the same setup as before.
|
||||
*
|
||||
* (3) http://ckeditor.com/builder/download/a3aa36a1c49fac96ad54315f97e9e05c
|
||||
* (3) http://ckeditor.com/builder/download/d5ff452dc2f39a022cdbb070ac7e5e57
|
||||
* Straight download link to the latest version of CKEditor (Optimized) with the same setup as before.
|
||||
*
|
||||
* NOTE:
|
||||
@@ -63,11 +63,13 @@ var CKBUILDER_CONFIG = {
|
||||
'entities' : 1,
|
||||
'filebrowser' : 1,
|
||||
'floatingspace' : 1,
|
||||
'font' : 1,
|
||||
'format' : 1,
|
||||
'horizontalrule' : 1,
|
||||
'htmlwriter' : 1,
|
||||
'image' : 1,
|
||||
'indentlist' : 1,
|
||||
'justify' : 1,
|
||||
'link' : 1,
|
||||
'list' : 1,
|
||||
'magicline' : 1,
|
||||
|
||||
1337
js/ckeditor/ckeditor.js
vendored
1337
js/ckeditor/ckeditor.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -4,21 +4,26 @@
|
||||
*/
|
||||
|
||||
CKEDITOR.editorConfig = function( config ) {
|
||||
// Define changes to default configuration here.
|
||||
// For complete reference see:
|
||||
// http://docs.ckeditor.com/#!/api/CKEDITOR.config
|
||||
|
||||
// The toolbar groups arrangement, optimized for two toolbar rows.
|
||||
config.toolbarGroups = [
|
||||
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
|
||||
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker', 'editing' ] },
|
||||
{ name: 'links', groups: [ 'links' ] },
|
||||
{ name: 'insert', groups: [ 'insert' ] },
|
||||
{ name: 'forms', groups: [ 'forms' ] },
|
||||
{ name: 'tools', groups: [ 'tools' ] },
|
||||
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
|
||||
{ name: 'others', groups: [ 'others' ] },
|
||||
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
|
||||
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
|
||||
{ name: 'links' },
|
||||
{ name: 'insert' },
|
||||
{ name: 'forms' },
|
||||
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
|
||||
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
|
||||
{ name: 'others' },
|
||||
{ name: 'tools' },
|
||||
'/',
|
||||
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
|
||||
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi', 'paragraph' ] },
|
||||
{ name: 'styles', groups: [ 'styles' ] },
|
||||
{ name: 'colors', groups: [ 'colors' ] },
|
||||
{ name: 'about', groups: [ 'about' ] }
|
||||
{ name: 'colors' },
|
||||
{ name: 'styles' },
|
||||
{ name: 'about' }
|
||||
];
|
||||
|
||||
config.removeButtons = 'Subscript,Superscript,Scayt,Anchor,Source,Outdent,Indent,Blockquote,About,PasteFromWord';
|
||||
@@ -26,7 +31,13 @@ CKEDITOR.editorConfig = function( config ) {
|
||||
config.resize_enabled = false;
|
||||
config.toolbarCanCollapse = true;
|
||||
config.toolbarStartupExpanded = false;
|
||||
|
||||
// Set the most common block elements.
|
||||
config.format_tags = 'p;h1;h2;h3;pre';
|
||||
|
||||
// Simplify the dialog windows.
|
||||
config.removeDialogTabs = 'image:advanced;link:advanced';
|
||||
|
||||
// Enable the browser spell checking
|
||||
config.disableNativeSpellChecker = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ body
|
||||
/* Remove the background color to make it transparent */
|
||||
background-color: #fff;
|
||||
|
||||
margin: 5px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.cke_editable
|
||||
{
|
||||
font-size: 12px;
|
||||
line-height: 1.0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
|
||||
/* Fix for missing scrollbars with RTL texts. (#10488) */
|
||||
word-wrap: break-word;
|
||||
@@ -138,3 +138,76 @@ p {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
/* Widget Styles */
|
||||
.code-featured
|
||||
{
|
||||
border: 5px solid red;
|
||||
}
|
||||
|
||||
.math-featured
|
||||
{
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 2px rgba(200, 0, 0, 1);
|
||||
background-color: rgba(255, 0, 0, 0.05);
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.image-clean
|
||||
{
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.image-clean > figcaption
|
||||
{
|
||||
font-size: .9em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.image-grayscale
|
||||
{
|
||||
background-color: white;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.image-grayscale img, img.image-grayscale
|
||||
{
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.embed-240p
|
||||
{
|
||||
max-width: 426px;
|
||||
max-height: 240px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
.embed-360p
|
||||
{
|
||||
max-width: 640px;
|
||||
max-height: 360px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
.embed-480p
|
||||
{
|
||||
max-width: 854px;
|
||||
max-height: 480px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
.embed-720p
|
||||
{
|
||||
max-width: 1280px;
|
||||
max-height: 720px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
.embed-1080p
|
||||
{
|
||||
max-width: 1920px;
|
||||
max-height: 1080px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 43 B |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user