mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-18 23:44:19 +01:00
Compare commits
234 Commits
3.0.0-beta
...
3.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e0ae67803 | ||
|
|
095c975ec6 | ||
|
|
9e1e5a8a47 | ||
|
|
2c026fa891 | ||
|
|
00d65aeb45 | ||
|
|
5702f603ac | ||
|
|
e3dc1b77cc | ||
|
|
c304f70ff4 | ||
|
|
53fd41e748 | ||
|
|
7577fbb8bf | ||
|
|
0fc912b357 | ||
|
|
c475e66176 | ||
|
|
cd1ba097cb | ||
|
|
fdca4d4cc3 | ||
|
|
4379b4d908 | ||
|
|
5b42f67a99 | ||
|
|
0b9ccc8e67 | ||
|
|
2d98ca2318 | ||
|
|
92add2bbfe | ||
|
|
f15bdb75ca | ||
|
|
a66830de17 | ||
|
|
714294e1b4 | ||
|
|
e3d2c1d761 | ||
|
|
59d95cc14b | ||
|
|
ddc5bbd1bb | ||
|
|
9bbee0d603 | ||
|
|
be1ef5b452 | ||
|
|
cbdc48b7e1 | ||
|
|
bb5679959e | ||
|
|
cdbb4470fc | ||
|
|
252d752bf5 | ||
|
|
99d0c05c1c | ||
|
|
5926e9d110 | ||
|
|
c8dd8c3806 | ||
|
|
70b07721e6 | ||
|
|
bd050dfe69 | ||
|
|
9eb477ce83 | ||
|
|
a742b6c610 | ||
|
|
fcf769666e | ||
|
|
72e628c5d5 | ||
|
|
bf28602ae6 | ||
|
|
b8a0d899f4 | ||
|
|
d335736cfc | ||
|
|
80f7d07378 | ||
|
|
0be6a8aef4 | ||
|
|
0fe857071d | ||
|
|
05981c8af8 | ||
|
|
0abea749fa | ||
|
|
78af6fdc84 | ||
|
|
1e97b5a8c0 | ||
|
|
0214243b63 | ||
|
|
d30871ac59 | ||
|
|
d247ea915d | ||
|
|
0e0aed1ba4 | ||
|
|
bc770ef3d5 | ||
|
|
56a4fb0b42 | ||
|
|
3139628dd8 | ||
|
|
7d9b19cd9e | ||
|
|
b3cb95d2f1 | ||
|
|
a6765cdc3a | ||
|
|
97d52fb539 | ||
|
|
ad936d7a2f | ||
|
|
090eb9a560 | ||
|
|
e4f58b5b98 | ||
|
|
c99a22c9f7 | ||
|
|
29cf20beaf | ||
|
|
e325d535ae | ||
|
|
11cfdb2a17 | ||
|
|
66720b9731 | ||
|
|
234f46cafa | ||
|
|
27c3ce0389 | ||
|
|
9d0e2fa64a | ||
|
|
e211633fed | ||
|
|
29967aa41a | ||
|
|
3ebb1cfc66 | ||
|
|
6c2221b8b6 | ||
|
|
c0b3aed12c | ||
|
|
53efc9f75e | ||
|
|
0d566f2e47 | ||
|
|
b294e3c734 | ||
|
|
6704f9eccf | ||
|
|
90cb6e1d49 | ||
|
|
c0aa4f2d69 | ||
|
|
01984c24c0 | ||
|
|
1da1e0b1bd | ||
|
|
39bcd3e4cd | ||
|
|
156cce6098 | ||
|
|
3130e95f4f | ||
|
|
e28f704f3e | ||
|
|
5c6c59941a | ||
|
|
bc9d47933e | ||
|
|
07a10e4146 | ||
|
|
e8a21870ad | ||
|
|
2e132d5c53 | ||
|
|
3359609349 | ||
|
|
2966759466 | ||
|
|
25395405e5 | ||
|
|
b589e2d001 | ||
|
|
27f3619cf5 | ||
|
|
616fc436d0 | ||
|
|
4f2f765207 | ||
|
|
9931fa1a6b | ||
|
|
a13f2750ea | ||
|
|
243d105f59 | ||
|
|
7783ba570e | ||
|
|
5a9fa2ac32 | ||
|
|
619e3de5a8 | ||
|
|
83064d68c7 | ||
|
|
f3c11e72cf | ||
|
|
c9e887a264 | ||
|
|
49fd482389 | ||
|
|
96de4e1796 | ||
|
|
44f413583c | ||
|
|
91104002ea | ||
|
|
f98ba1594c | ||
|
|
431dc5532b | ||
|
|
df473ae313 | ||
|
|
40ce74cffa | ||
|
|
7598c18ad6 | ||
|
|
d38b655f5f | ||
|
|
f4345ef312 | ||
|
|
13b548e95d | ||
|
|
3a988ab499 | ||
|
|
14a5f87d62 | ||
|
|
8dae459b12 | ||
|
|
8dc10424e8 | ||
|
|
54a6573948 | ||
|
|
1d5e0b6fe9 | ||
|
|
acb8a377dd | ||
|
|
d86f489c89 | ||
|
|
2c154b601d | ||
|
|
7dad079688 | ||
|
|
ace2eb6dab | ||
|
|
25f3c1cbc4 | ||
|
|
8f7e7c136d | ||
|
|
71a7e060e4 | ||
|
|
c450f1d02f | ||
|
|
4431762c10 | ||
|
|
97170892e6 | ||
|
|
5137d634e3 | ||
|
|
146a95baec | ||
|
|
a7ac9126a2 | ||
|
|
ee544b646d | ||
|
|
344cce9fdd | ||
|
|
bfcfcdd4cc | ||
|
|
4410bf7e1f | ||
|
|
d1fda1dbc6 | ||
|
|
5c702be641 | ||
|
|
8a9ad78e9c | ||
|
|
778be8abce | ||
|
|
0760adcef6 | ||
|
|
29ca291860 | ||
|
|
93d0e4ae7c | ||
|
|
61bc07b598 | ||
|
|
00ee36f938 | ||
|
|
1aa5185c93 | ||
|
|
834ac00d37 | ||
|
|
957cb87b8d | ||
|
|
d0813f6607 | ||
|
|
cf4673d284 | ||
|
|
e3422f5fd9 | ||
|
|
d7a0878a39 | ||
|
|
076f0e00a7 | ||
|
|
eb04ecb5b4 | ||
|
|
3323d5532b | ||
|
|
d68d8204f2 | ||
|
|
4a50b95069 | ||
|
|
fd9e4f413c | ||
|
|
dca3fc996c | ||
|
|
c58c1bbf1d | ||
|
|
524919b946 | ||
|
|
e15953524a | ||
|
|
68f3c53faa | ||
|
|
82e3ddaacd | ||
|
|
dfbbee2a1c | ||
|
|
4843545171 | ||
|
|
4e58b8616a | ||
|
|
1a79dcd773 | ||
|
|
a4104d4315 | ||
|
|
4e1db7d7e2 | ||
|
|
8e6379a112 | ||
|
|
da217a1cb3 | ||
|
|
a683634a05 | ||
|
|
2f1b5d2736 | ||
|
|
c9bf784fce | ||
|
|
4ef10ae7c9 | ||
|
|
5174250ff1 | ||
|
|
7b6ac202c6 | ||
|
|
d960183403 | ||
|
|
ece3e0490d | ||
|
|
1562cb1f38 | ||
|
|
11a22abfd5 | ||
|
|
5254c9a633 | ||
|
|
f7a35072f5 | ||
|
|
b5f5780f35 | ||
|
|
f9064084f9 | ||
|
|
67afbd1d8d | ||
|
|
d6b9172e26 | ||
|
|
8e1e71c740 | ||
|
|
ebbf6e56be | ||
|
|
bd67b71f3d | ||
|
|
69ad10785b | ||
|
|
9aead898e2 | ||
|
|
a48ebfefba | ||
|
|
e2a6e6b846 | ||
|
|
7ca689e190 | ||
|
|
d8f36a8aa9 | ||
|
|
e279799bf8 | ||
|
|
a117906ff6 | ||
|
|
c76d4f12fd | ||
|
|
b16529e337 | ||
|
|
67e9212008 | ||
|
|
35b70bfc00 | ||
|
|
418312bca5 | ||
|
|
4748717e50 | ||
|
|
d90b1a3d82 | ||
|
|
76a237aad4 | ||
|
|
3694108f42 | ||
|
|
1a7755365c | ||
|
|
8cf75f826f | ||
|
|
a1da086a64 | ||
|
|
5d1d6d07a6 | ||
|
|
e6a38a8055 | ||
|
|
3b3f1806ce | ||
|
|
e46743af2a | ||
|
|
9048d09bf6 | ||
|
|
3cd03729b9 | ||
|
|
ff760dacbe | ||
|
|
94f662c71a | ||
|
|
c7d87ad5b0 | ||
|
|
ad9726b64c | ||
|
|
e32e275f40 | ||
|
|
195056492e | ||
|
|
5691ca0327 |
@@ -280,7 +280,7 @@ ij_javascript_while_brace_force = always
|
||||
ij_javascript_while_on_new_line = false
|
||||
ij_javascript_wrap_comments = false
|
||||
|
||||
[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
|
||||
[{*.ctp, *.hphp, *.inc, *.module, *.php, *.php4, *.php5, *.phtml}]
|
||||
indent_style = tab
|
||||
ij_continuation_indent_size = 4
|
||||
ij_smart_tabs = true
|
||||
@@ -289,8 +289,8 @@ ij_php_align_assignments = false
|
||||
ij_php_align_class_constants = false
|
||||
ij_php_align_group_field_declarations = false
|
||||
ij_php_align_inline_comments = false
|
||||
ij_php_align_key_value_pairs = false
|
||||
ij_php_align_multiline_array_initializer_expression = false
|
||||
ij_php_align_key_value_pairs = true
|
||||
ij_php_align_multiline_array_initializer_expression = true
|
||||
ij_php_align_multiline_binary_operation = false
|
||||
ij_php_align_multiline_chained_methods = false
|
||||
ij_php_align_multiline_extends_list = false
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
* `curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe`
|
||||
* this is a Windows port : https://stedolan.github.io/jq/
|
||||
*
|
||||
* Known bug on Windows :
|
||||
* Licenses added from Composer contains a path in the product node (N°3870)
|
||||
* `<product scope="lib">C:\Dev\wamp64\www\itop-dev\.make\license/../..//lib/symfony/console</product>`
|
||||
*
|
||||
* Licenses sources :
|
||||
* * `composer licenses --format json` (see https://getcomposer.org/doc/03-cli.md#licenses)
|
||||
* * keep every existing nodes with `/licenses/license[11]/product/@scope` not in ['lib', 'datamodels']
|
||||
@@ -70,39 +66,83 @@ function get_license_nodes($file_path)
|
||||
$xp = new DOMXPath($dom);
|
||||
|
||||
$licenseList = $xp->query('/licenses/license');
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
|
||||
usort($licenses, 'sort_by_product');
|
||||
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
/** @noinspection SuspiciousAssignmentsInspection */
|
||||
function fix_product_name(DOMNode &$oProductNode)
|
||||
{
|
||||
$sProductNameOrig = $oProductNode->nodeValue;
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//lib/symfony/polyfill-ctype`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameOrig, 'lib/');
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameFixed, 'vendor/');
|
||||
|
||||
$oProductNode->nodeValue = $sProductNameFixed;
|
||||
}
|
||||
|
||||
function remove_dir_from_string($sString, $sNeedle)
|
||||
{
|
||||
if (strpos($sString, $sNeedle) === false) {
|
||||
return $sString;
|
||||
}
|
||||
|
||||
$sStringTmp = strstr($sString, $sNeedle);
|
||||
$sStringFixed = str_replace($sNeedle, '', $sStringTmp);
|
||||
|
||||
// DEBUG trace O:)
|
||||
// echo "$sNeedle = $sString => $sStringFixed\n";
|
||||
|
||||
return $sStringFixed;
|
||||
}
|
||||
|
||||
$old_licenses = get_license_nodes($xmlFilePath);
|
||||
|
||||
//generate file with updated licenses
|
||||
$generated_license_file_path = __DIR__."/provfile.xml";
|
||||
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
|
||||
echo "- Generating licences...";
|
||||
exec("bash ".__DIR__."/gen-community-license.sh $iTopFolder > ".$generated_license_file_path);
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Get licenses nodes...";
|
||||
$new_licenses = get_license_nodes($generated_license_file_path);
|
||||
exec("rm -f ". $generated_license_file_path);
|
||||
unlink($generated_license_file_path);
|
||||
|
||||
foreach ($old_licenses as $b) {
|
||||
$aProductNode = get_product_node($b);
|
||||
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
|
||||
{
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels") {
|
||||
$new_licenses[] = $b;
|
||||
}
|
||||
}
|
||||
|
||||
usort($new_licenses, 'sort_by_product');
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Overwritting Combodo license file...";
|
||||
$new_dom = new DOMDocument("1.0");
|
||||
$new_dom->formatOutput = true;
|
||||
$root = $new_dom->createElement("licenses");
|
||||
$new_dom->appendChild($root);
|
||||
|
||||
foreach ($new_licenses as $b) {
|
||||
$node = $new_dom->importNode($b,true);
|
||||
$root->appendChild($new_dom->importNode($b,true));
|
||||
$node = $new_dom->importNode($b, true);
|
||||
|
||||
// N°3870 fix when running script in Windows
|
||||
// fix should be in gen-community-license.sh but it is easier to do it here !
|
||||
if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
|
||||
$oProductNodeOrig = get_product_node($node);
|
||||
fix_product_name($oProductNodeOrig);
|
||||
}
|
||||
|
||||
$root->appendChild($node);
|
||||
}
|
||||
|
||||
$new_dom->save($xmlFilePath);
|
||||
$new_dom->save($xmlFilePath);
|
||||
echo "OK!\n";
|
||||
@@ -202,8 +202,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
$sParams .= $sName.'='.urlencode($value).'&'; // Always add a trailing &
|
||||
}
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1';
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
$oPage->add_early_script(<<<JS
|
||||
if (!sessionStorage.getItem('$sSessionStorageKey'))
|
||||
{
|
||||
sessionStorage.setItem('$sSessionStorageKey', 1);
|
||||
@@ -213,7 +212,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
{
|
||||
sessionStorage.removeItem('$sSessionStorageKey');
|
||||
}
|
||||
EOF
|
||||
JS
|
||||
);
|
||||
|
||||
$oObj->Reload();
|
||||
@@ -662,7 +661,7 @@ HTML
|
||||
}
|
||||
|
||||
$oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false));
|
||||
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-blocklist--medallion');
|
||||
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion');
|
||||
$oPage->AddUiBlock($oClassIcon);
|
||||
|
||||
$sDisplayValue = ''; // not used
|
||||
@@ -740,7 +739,7 @@ HTML
|
||||
);
|
||||
}
|
||||
$oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false));
|
||||
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-blocklist--medallion');
|
||||
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion');
|
||||
$oPage->AddUiBlock($oClassIcon);
|
||||
$oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false);
|
||||
$oBlock->Display($oPage, 'rel_'.$sAttCode, $aParams);
|
||||
@@ -797,7 +796,7 @@ HTML
|
||||
|
||||
foreach($aNotificationClasses as $sNotifClass) {
|
||||
$oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sNotifClass, false));
|
||||
$oClassIcon->SetDescription(MetaModel::GetName($sNotifClass))->AddCSSClass('ibo-blocklist--medallion');
|
||||
$oClassIcon->SetDescription(MetaModel::GetName($sNotifClass))->AddCSSClass('ibo-block-list--medallion');
|
||||
$oPage->AddUiBlock($oClassIcon);
|
||||
|
||||
$oBlock = new DisplayBlock($aNotifSearches[$sNotifClass], 'list', false);
|
||||
@@ -1036,9 +1035,32 @@ HTML
|
||||
*/
|
||||
public function DisplayDetails(WebPage $oPage, $bEditMode = false, $sMode = self::ENUM_OBJECT_MODE_VIEW)
|
||||
{
|
||||
// N°3786: As this can now be call recursively from the self::ReloadAndDisplay(), we need to make sure we don't fall into an infinite loop
|
||||
static $bBlockReentrance = false;
|
||||
|
||||
$sClass = get_class($this);
|
||||
$iKey = $this->GetKey();
|
||||
|
||||
if ($sMode === static::ENUM_OBJECT_MODE_VIEW)
|
||||
{
|
||||
// The concurrent access lock makes sense only for already existing objects
|
||||
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
|
||||
if ($LockEnabled)
|
||||
{
|
||||
$aLockInfo = iTopOwnershipLock::IsLocked($sClass, $iKey);
|
||||
if ($aLockInfo['locked'] === true && $aLockInfo['owner']->GetKey() == UserRights::GetUserId() && $bBlockReentrance === false)
|
||||
{
|
||||
// If the object is locked by the current user, it's worth trying again, since
|
||||
// the lock may be released by 'onunload' which is called AFTER loading the current page.
|
||||
//$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId();
|
||||
$bBlockReentrance = true;
|
||||
self::ReloadAndDisplay($oPage, $this, array('operation' => 'details'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Object's details
|
||||
$oObjectDetails = ObjectFactory::MakeDetails($this);
|
||||
|
||||
@@ -1123,25 +1145,11 @@ HTML
|
||||
*/
|
||||
public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
|
||||
{
|
||||
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null;
|
||||
|
||||
$bViewLink = true;
|
||||
$sSelectMode = 'none';
|
||||
$iListId = $sTableId;
|
||||
$sClassAlias = $oSet->GetClassAlias();
|
||||
$sClassName = $oSet->GetClass();
|
||||
$sZListName = 'list';
|
||||
$aClassAliases = array($sClassAlias => $sClassName);
|
||||
$aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
|
||||
|
||||
$oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId);
|
||||
$oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList));
|
||||
$oSettings->iDefaultPageSize = 0;
|
||||
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
|
||||
|
||||
return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink,
|
||||
$aExtraParams);
|
||||
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : utils::GetUniqueId();;
|
||||
$aExtraParams['view_link'] = true;
|
||||
$aExtraParams['select_mode'] = 'none';
|
||||
|
||||
return DataTableUIBlockFactory::MakeForObject($oPage, $sTableId, $oSet, $aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2403,6 +2411,7 @@ HTML;
|
||||
case 'CustomFields':
|
||||
$sHTMLValue .= '<div id="'.$iId.'_console_form">';
|
||||
$sHTMLValue .= '<div id="'.$iId.'_field_set">';
|
||||
$sHTMLValue .= '</div></div>';
|
||||
$sHTMLValue .= '<div>'.$sReloadSpan.'</div>'; // No validation span for this one: it does handle its own validation!
|
||||
$sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" value=\"\"/>\n";
|
||||
|
||||
@@ -2533,14 +2542,13 @@ EOF
|
||||
$sDisplayValueForHtml = utils::EscapeHtml($sDisplayValue);
|
||||
|
||||
// Adding tooltip so we can read the whole value when its very long (eg. URL)
|
||||
$sTip = '';
|
||||
$sTip = '';
|
||||
if (!empty($sDisplayValue)) {
|
||||
$sTip = 'data-tooltip-content="'.$sDisplayValueForHtml.'"';
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$('#{$iId}').on('keyup', function(evt, sFormId){
|
||||
var sVal = $('#{$iId}').val();
|
||||
var oTippy = this._tippy;
|
||||
let sVal = $('#{$iId}').val();
|
||||
const oTippy = this._tippy;
|
||||
|
||||
if(sVal === '')
|
||||
{
|
||||
@@ -2553,7 +2561,7 @@ EOF
|
||||
}
|
||||
oTippy.setContent(sVal);
|
||||
});
|
||||
EOF
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3096,55 +3104,45 @@ EOF
|
||||
|
||||
// The list of candidate fields is made of the ordered list of "details" attributes + other attributes
|
||||
$aAttributes = array();
|
||||
foreach($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode)
|
||||
{
|
||||
foreach ($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) {
|
||||
$aAttributes[$sAttCode] = true;
|
||||
}
|
||||
foreach(MetaModel::GetAttributesList($sClass) as $sAttCode)
|
||||
{
|
||||
if (!array_key_exists($sAttCode, $aAttributes))
|
||||
{
|
||||
foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) {
|
||||
if (!array_key_exists($sAttCode, $aAttributes)) {
|
||||
$aAttributes[$sAttCode] = true;
|
||||
}
|
||||
}
|
||||
// Order the fields based on their dependencies, set the fields for which there is only one possible value
|
||||
// and perform this in the order of dependencies to avoid dead-ends
|
||||
$aDeps = array();
|
||||
foreach($aAttributes as $sAttCode => $trash)
|
||||
{
|
||||
foreach ($aAttributes as $sAttCode => $trash) {
|
||||
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
|
||||
}
|
||||
$aList = $this->OrderDependentFields($aDeps);
|
||||
|
||||
foreach($aList as $sAttCode)
|
||||
{
|
||||
$bExistFieldToDisplay = false;
|
||||
foreach ($aList as $sAttCode) {
|
||||
// Consider only the "expected" fields for the target state
|
||||
if (array_key_exists($sAttCode, $aExpectedAttributes))
|
||||
{
|
||||
if (array_key_exists($sAttCode, $aExpectedAttributes)) {
|
||||
$iExpectCode = $aExpectedAttributes[$sAttCode];
|
||||
// Prompt for an attribute if
|
||||
// - the attribute must be changed or must be displayed to the user for confirmation
|
||||
// - or the field is mandatory and currently empty
|
||||
if (($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ||
|
||||
(($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == '')))
|
||||
{
|
||||
(($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == ''))) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$aArgs = array('this' => $this);
|
||||
// If the field is mandatory, set it to the only possible value
|
||||
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY))
|
||||
{
|
||||
if ($oAttDef->IsExternalKey())
|
||||
{
|
||||
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY)) {
|
||||
if ($oAttDef->IsExternalKey()) {
|
||||
/** @var DBObjectSet $oAllowedValues */
|
||||
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '',
|
||||
$this->Get($sAttCode));
|
||||
if ($oAllowedValues->CountWithLimit(2) == 1)
|
||||
{
|
||||
if ($oAllowedValues->CountWithLimit(2) == 1) {
|
||||
$oRemoteObj = $oAllowedValues->Fetch();
|
||||
$this->Set($sAttCode, $oRemoteObj->GetKey());
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
{
|
||||
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
|
||||
if (is_array($aAllowedValues) && count($aAllowedValues) == 1)
|
||||
@@ -3180,8 +3178,7 @@ EOF
|
||||
$bExcludeRawValue = false;
|
||||
foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude)
|
||||
{
|
||||
if (is_a($sAttDefClass, $sAttDefClassToExclude, true))
|
||||
{
|
||||
if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) {
|
||||
$bExcludeRawValue = true;
|
||||
break;
|
||||
}
|
||||
@@ -3191,88 +3188,105 @@ EOF
|
||||
$aDetails[] = $aAttrib;
|
||||
$aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex;
|
||||
$iFieldIndex++;
|
||||
$bExistFieldToDisplay = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oPage->set_title($sActionLabel);
|
||||
$oPage->add(<<<HTML
|
||||
<!-- Beginning of object-transition -->
|
||||
<div class="object-transition" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode" data-object-current-state="$sCurrentState" data-object-target-state="$sTargetState">
|
||||
if ($bExistFieldToDisplay) {
|
||||
$oPage->set_title($sActionLabel);
|
||||
$oPage->add(<<<HTML
|
||||
<!-- Beginning of object-transition -->
|
||||
<div class="object-transition" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode" data-object-current-state="$sCurrentState" data-object-target-state="$sTargetState">
|
||||
HTML
|
||||
);
|
||||
);
|
||||
|
||||
// Page title and subtitles
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetName()));
|
||||
if (!empty($sActionDetails)) {
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
|
||||
}
|
||||
// Page title and subtitles
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetRawName()));
|
||||
if (!empty($sActionDetails)) {
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
|
||||
}
|
||||
|
||||
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
|
||||
$oPage->AddUiBlock($oFormContainer);
|
||||
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
|
||||
$oFormContainer->AddSubBlock($oForm);
|
||||
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
|
||||
$oPage->AddUiBlock($oFormContainer);
|
||||
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
|
||||
$oFormContainer->AddSubBlock($oForm);
|
||||
|
||||
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
|
||||
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
|
||||
|
||||
if ($sOwnershipToken !== null) {
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
|
||||
}
|
||||
if ($sOwnershipToken !== null) {
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
|
||||
}
|
||||
|
||||
// Note: Remove the table is we want fields to occupy the whole width of the container
|
||||
$oForm->AddHtml('<table><tr><td>');
|
||||
$oForm->AddHtml($oPage->GetDetails($aDetails));
|
||||
$oForm->AddHtml('</td></tr></table>');
|
||||
// Note: Remove the table is we want fields to occupy the whole width of the container
|
||||
$oForm->AddHtml('<table><tr><td>');
|
||||
$oForm->AddHtml($oPage->GetDetails($aDetails));
|
||||
$oForm->AddHtml('</td></tr></table>');
|
||||
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oForm->AddHtml($oAppContext->GetForForm());
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oForm->AddHtml($oAppContext->GetForForm());
|
||||
|
||||
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel');
|
||||
$oCancelButton->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
|
||||
$oForm->AddSubBlock($oCancelButton);
|
||||
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel');
|
||||
$oCancelButton->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
|
||||
$oForm->AddSubBlock($oCancelButton);
|
||||
|
||||
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
|
||||
$oForm->AddSubBlock($oSubmitButton);
|
||||
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
|
||||
$oForm->AddSubBlock($oSubmitButton);
|
||||
|
||||
$oPage->add(<<<HTML
|
||||
<!-- End of object-transition -->
|
||||
</div>
|
||||
$oPage->add(<<<HTML
|
||||
<!-- End of object-transition -->
|
||||
</div>
|
||||
HTML
|
||||
);
|
||||
);
|
||||
|
||||
$iFieldsCount = count($aFieldsMap);
|
||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||
$iFieldsCount = count($aFieldsMap);
|
||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
// Initializes the object once at the beginning of the page...
|
||||
var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus');
|
||||
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
|
||||
oWizardHelper.SetFieldsCount($iFieldsCount);
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
// Initializes the object once at the beginning of the page...
|
||||
var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus');
|
||||
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
|
||||
oWizardHelper.SetFieldsCount($iFieldsCount);
|
||||
EOF
|
||||
);
|
||||
$sJSToken = json_encode($sOwnershipToken);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Starts the validation when the page is ready
|
||||
CheckFields('apply_stimulus', false);
|
||||
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
);
|
||||
$sJSToken = json_encode($sOwnershipToken);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Starts the validation when the page is ready
|
||||
CheckFields('apply_stimulus', false);
|
||||
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
EOF
|
||||
);
|
||||
);
|
||||
|
||||
if ($sOwnershipToken !== null)
|
||||
{
|
||||
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
|
||||
if ($sOwnershipToken !== null) {
|
||||
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
|
||||
}
|
||||
|
||||
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
|
||||
$sTempId = utils::GetUploadTempId($iTransactionId);
|
||||
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
|
||||
} else {
|
||||
//we can directly apply the stimuli
|
||||
$bApplyStimulus = $this->ApplyStimulus($sStimulus); // will write the object in the DB
|
||||
if (!$bApplyStimulus) {
|
||||
throw new ApplicationException(Dict::S('UI:FailedToApplyStimuli'));
|
||||
} else {
|
||||
if ($sOwnershipToken !== null) {
|
||||
// Release the concurrent lock, if any
|
||||
iTopOwnershipLock::ReleaseLock($sClass, $iKey, $sOwnershipToken);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
|
||||
$sTempId = utils::GetUploadTempId($iTransactionId);
|
||||
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet)
|
||||
@@ -3587,7 +3601,7 @@ HTML;
|
||||
* @api
|
||||
* @overwritable-hook
|
||||
*
|
||||
* @param $sFinalClass string The actual class of the objects for which to display the menu
|
||||
* @param string $sFinalClass The actual class of the objects for which to display the menu
|
||||
*
|
||||
* @return array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the
|
||||
* actions menu
|
||||
@@ -5343,6 +5357,20 @@ EOF
|
||||
$sJSOk = json_encode(Dict::S('UI:Button:Ok'));
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
// Prepare reusable modal
|
||||
const oOwnershipLockModal = $('<div></div>').dialog({
|
||||
title: $sJSTitle,
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
minWidth: 600,
|
||||
buttons:[{
|
||||
text: $sJSOk,
|
||||
class: 'ibo-is-alternative',
|
||||
click: function() { $(this).dialog('close'); }
|
||||
}],
|
||||
close: function() { $(this).dialog('close'); }
|
||||
});
|
||||
// Start periodic handler
|
||||
let hOwnershipLockHandlerInterval = window.setInterval(function() {
|
||||
if (window.bInSubmit || window.bInCancel) return;
|
||||
|
||||
@@ -5352,18 +5380,8 @@ EOF
|
||||
if ($('.lock_owned').length == 0)
|
||||
{
|
||||
$('.ui-layout-content').prepend('<div class="header_message message_error lock_owned">'+data.message+'</div>');
|
||||
$('<div>'+data.popup_message+'</div>').dialog({
|
||||
title: $sJSTitle,
|
||||
modal: true,
|
||||
autoOpen: true,
|
||||
minWidth: 600,
|
||||
buttons:[{
|
||||
text: {$sJSOk},
|
||||
class: 'ibo-is-alternative',
|
||||
click: function() { $(this).dialog('close'); }
|
||||
}],
|
||||
close: function() { $(this).remove(); }
|
||||
});
|
||||
oOwnershipLockModal.text(data.popup_message);
|
||||
oOwnershipLockModal.dialog('open');
|
||||
}
|
||||
$('.object-details form .ibo-toolbar .ibo-button:not([name="cancel"])').prop('disabled', true);
|
||||
clearInterval(hOwnershipLockHandlerInterval);
|
||||
@@ -5373,18 +5391,8 @@ EOF
|
||||
if ($('.lock_owned').length == 0)
|
||||
{
|
||||
$('.ui-layout-content').prepend('<div class="header_message message_error lock_owned">'+data.message+'</div>');
|
||||
$('<div>'+data.popup_message+'</div>').dialog({
|
||||
title: $sJSTitle,
|
||||
modal: true,
|
||||
autoOpen: true,
|
||||
minWidth: 600,
|
||||
buttons:[{
|
||||
text: $sJSOk,
|
||||
class: 'ibo-is-alternative',
|
||||
click: function() { $(this).dialog('close'); }
|
||||
}],
|
||||
close: function() { $(this).remove(); }
|
||||
});
|
||||
oOwnershipLockModal.text(data.popup_message);
|
||||
oOwnershipLockModal.dialog('open');
|
||||
}
|
||||
$('.object-details form .ibo-toolbar .ibo-button:not([name="cancel"])').prop('disabled', true);
|
||||
clearInterval(hOwnershipLockHandlerInterval);
|
||||
|
||||
@@ -906,18 +906,22 @@ class DashletObjectList extends Dashlet
|
||||
$sShowMenu = $this->aProperties['menu'] ? '1' : '0';
|
||||
$oFilter = $this->GetDBSearch($aExtraParams);
|
||||
$sClass = $oFilter->GetClass();
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle))
|
||||
->AddCSSClass('ibo-datatable-panel');
|
||||
//$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle))
|
||||
// ->AddCSSClass('ibo-datatable-panel');
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list');
|
||||
$aParams = array(
|
||||
'menu' => $sShowMenu,
|
||||
'table_id' => self::APPUSERPREFERENCES_PREFIX.$this->sId,
|
||||
'surround_with_panel' => false,
|
||||
'surround_with_panel' => true,
|
||||
'max_height' => '500px',
|
||||
"panel_title" => Dict::S($sTitle),
|
||||
"panel_class" => $sClass,
|
||||
);
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
|
||||
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
//$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
|
||||
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
@@ -984,6 +988,7 @@ HTML;
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
$oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
|
||||
@@ -1020,6 +1025,7 @@ HTML;
|
||||
|
||||
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $sOQL);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
$oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
|
||||
@@ -1256,15 +1262,21 @@ abstract class DashletGroupBy extends Dashlet
|
||||
break;
|
||||
}
|
||||
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle));
|
||||
//$oPanel = \Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory::MakeStandard();
|
||||
//PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle));
|
||||
|
||||
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
|
||||
$oBlock = new DisplayBlock($oFilter, $sType);
|
||||
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
//$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
$aExtraParams["surround_with_panel"] = true;
|
||||
$aExtraParams["panel_title"] = Dict::S($sTitle);
|
||||
$aExtraParams["panel_class"] = $sClass;
|
||||
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
if ($bEditMode) {
|
||||
$oPanel->AddHtml('<div class="dashlet-blocker"></div>');
|
||||
$oPanel->AddHtml('<div class="ibo-dashlet-blocker dashlet-blocker"></div>');
|
||||
}
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
|
||||
@@ -1363,10 +1375,10 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
// Group by field: build the list of possible values (attribute codes + ...)
|
||||
$aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
|
||||
|
||||
@@ -1620,16 +1632,14 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
if (!is_null($sOQL))
|
||||
{
|
||||
if (!is_null($sOQL)) {
|
||||
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
|
||||
$aGroupBy = $this->GetGroupByOptions($sOQL);
|
||||
$oField->SetAllowedValues($aGroupBy);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Creating a form for reading parameters!
|
||||
$oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
|
||||
}
|
||||
@@ -2173,6 +2183,7 @@ class DashletHeaderDynamic extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletHeaderDynamic:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
try
|
||||
|
||||
@@ -10,6 +10,7 @@ use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Pill\PillFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
|
||||
@@ -171,6 +172,8 @@ class DisplayBlock
|
||||
/**positive or negative*/
|
||||
'max_height',
|
||||
/** string Max. height of the list, if not specified will occupy all the available height no matter the pagination */
|
||||
'localize_values',
|
||||
/** param for export.php */
|
||||
], DataTableUIBlockFactory::GetAllowedParams()),
|
||||
'list_search' => array_merge([
|
||||
'update_history',
|
||||
@@ -189,6 +192,8 @@ class DisplayBlock
|
||||
/** string */
|
||||
'open',
|
||||
/** bool open by default the search */
|
||||
'submit_on_load',
|
||||
/** bool submit the search on loading page */
|
||||
'class', /** class name */
|
||||
'search_header_force_dropdown', /** Html for <select> to choose the class to search */
|
||||
'this',
|
||||
@@ -198,6 +203,8 @@ class DisplayBlock
|
||||
/** string search root class */
|
||||
'open',
|
||||
/** bool open the search panel by default */
|
||||
'submit_on_load',
|
||||
/** bool submit the search on loading page */
|
||||
'result_list_outer_selector',
|
||||
/** string js selector of the search result display */
|
||||
'search_header_force_dropdown',
|
||||
@@ -257,6 +264,12 @@ class DisplayBlock
|
||||
'withJSRefreshCallBack',
|
||||
/** true if dashboard page */
|
||||
'from_dashboard_page',
|
||||
/** bool true if list may be render in panel block */
|
||||
'surround_with_panel',
|
||||
/** string title of panel block */
|
||||
'panel_title',
|
||||
/** string class for panel block style */
|
||||
'panel_class',
|
||||
];
|
||||
|
||||
if (isset($aAllowedParams[$sStyle])) {
|
||||
@@ -1026,10 +1039,12 @@ JS
|
||||
$sHyperlink = $aCount['link'];
|
||||
$sCountLabel = $aCount['label'];
|
||||
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
|
||||
->SetUrl($sHyperlink)
|
||||
->SetTooltip($sStateLabel)
|
||||
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span>")
|
||||
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">$sStateLabel</span>");
|
||||
if ($sHyperlink != '-') {
|
||||
$oPill->SetUrl($sHyperlink);
|
||||
}
|
||||
$oBlock->AddSubBlock($oPill);
|
||||
}
|
||||
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
|
||||
@@ -1173,11 +1188,20 @@ JS
|
||||
),
|
||||
);
|
||||
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
|
||||
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
|
||||
$aOption['dom'] = 'pl';
|
||||
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
|
||||
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oBlock->AddSubTitleBlock(new Html($sTitle));
|
||||
$oDataTable = DataTableUIBlockFactory::MakeForStaticData("", $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
|
||||
$oBlock->AddSubBlock($oDataTable);
|
||||
} else {
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Simply count the number of elements in the set
|
||||
@@ -1186,7 +1210,12 @@ JS
|
||||
if (isset($aExtraParams['format'])) {
|
||||
$sFormat = $aExtraParams['format'];
|
||||
}
|
||||
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oBlock->AddSubBlock(new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>'));
|
||||
} else {
|
||||
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
|
||||
}
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
@@ -1273,6 +1302,22 @@ JS
|
||||
$oBlock->bNotAuthorized = true;
|
||||
}
|
||||
} else {
|
||||
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history']) {
|
||||
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
|
||||
// Limit the size of the URL (N°1585 - request uri too long)
|
||||
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) {
|
||||
$oBlock->sEventAttachedData = json_encode(array(
|
||||
'filter' => $sSearchFilter,
|
||||
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
|
||||
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
|
||||
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
|
||||
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
|
||||
'breadcrumb_icon' => 'fas fa-search',
|
||||
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// The list is made of only 1 class of objects, actions on the list are possible
|
||||
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
|
||||
$oBlock->AddSubBlock(cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams));
|
||||
@@ -1299,25 +1344,16 @@ JS
|
||||
$oBlock->bCreateNew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history']) {
|
||||
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
|
||||
// Limit the size of the URL (N°1585 - request uri too long)
|
||||
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) {
|
||||
$oBlock->sEventAttachedData = json_encode(array(
|
||||
'filter' => $sSearchFilter,
|
||||
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
|
||||
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
|
||||
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
|
||||
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
|
||||
'breadcrumb_icon' => 'fas fa-search',
|
||||
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
|
||||
));
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oPanel->AddSubBlock($oBlock);
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
@@ -1506,6 +1542,13 @@ JS
|
||||
|
||||
$oBlock->sUrl = $sUrl;
|
||||
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oPanel->AddSubBlock($oBlock);
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
@@ -1587,6 +1630,13 @@ JS
|
||||
$oBlock->sURLForRefresh = str_replace("'", "\'", $sUrl);
|
||||
break;
|
||||
}
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oPanel->AddSubBlock($oBlock);
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
@@ -2158,9 +2208,9 @@ class MenuBlock extends DisplayBlock
|
||||
// Extract favorite actions from their menus
|
||||
$aFavoriteRegularActions = [];
|
||||
$aFavoriteTransitionActions = [];
|
||||
$aCallSpec = [$sClass, 'GetShortcutActions'];
|
||||
if (is_callable($aCallSpec)) {
|
||||
$aShortcutActions = call_user_func($aCallSpec, $sClass);
|
||||
if (is_callable([$sClass, 'GetShortcutActions'])) {
|
||||
/** @var cmdbAbstractObject $sClass */
|
||||
$aShortcutActions = $sClass::GetShortcutActions($sClass);
|
||||
foreach ($aShortcutActions as $key) {
|
||||
// Regular actions
|
||||
if (isset($aRegularActions[$key])) {
|
||||
|
||||
@@ -105,7 +105,7 @@ class DesignerForm
|
||||
foreach($aFields as $oField) {
|
||||
$aRow = $oField->Render($oP, $sFormId);
|
||||
if ($oField->IsVisible()) {
|
||||
$sValidation = '<span class="prop_apply ibo-prop--apply">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
|
||||
$sValidation = '<span class="prop_apply ibo-prop--apply ibo-button ibo-is-alternative">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
|
||||
$sField = $aRow['value'].$sValidation;
|
||||
$aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
|
||||
} else {
|
||||
@@ -203,51 +203,41 @@ class DesignerForm
|
||||
$sActionUrl = addslashes($this->sSubmitTo);
|
||||
$sJSSubmitParams = json_encode($this->aSubmitParams);
|
||||
$sFormId = $this->GetFormId();
|
||||
if ($this->oParentForm == null)
|
||||
{
|
||||
if ($this->oParentForm == null) {
|
||||
$sReturn = '<form id="'.$sFormId.'" onsubmit="return false;">';
|
||||
$sReturn .= '<table class="prop_table">';
|
||||
$sReturn .= '<thead><tr><th class="prop_header">'.Dict::S('UI:Form:Property').'</th><th class="prop_header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="prop_header"> </th></tr></thead><tbody>';
|
||||
$sReturn .= '<thead><tr><th class="ibo-prop-header">'.Dict::S('UI:Form:Property').'</th><th class="ibo-prop-header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="ibo-prop-header"> </th></tr></thead><tbody>';
|
||||
}
|
||||
|
||||
$sHiddenFields = '';
|
||||
foreach($this->aFieldSets as $sLabel => $aFields)
|
||||
{
|
||||
foreach ($this->aFieldSets as $sLabel => $aFields) {
|
||||
$aDetails = array();
|
||||
if ($sLabel != '')
|
||||
{
|
||||
if ($sLabel != '') {
|
||||
$sReturn .= $this->StartRow().'<th colspan="4">'.$sLabel.'</th>'.$this->EndRow();
|
||||
}
|
||||
|
||||
|
||||
foreach($aFields as $oField)
|
||||
{
|
||||
foreach ($aFields as $oField) {
|
||||
$aRow = $oField->Render($oP, $sFormId, 'property');
|
||||
if ($oField->IsVisible())
|
||||
{
|
||||
if ($oField->IsVisible()) {
|
||||
$sFieldId = $this->GetFieldId($oField->GetCode());
|
||||
$sValidation = $this->GetValidationArea($sFieldId, '<span data-tooltip-content="Apply"><i class="fas fa-check"></i></span>');
|
||||
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply">'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span data-tooltip-content="Revert"><i class="fas fa-times"></i></span></td>'.$this->EndRow();
|
||||
|
||||
$sPath = $this->GetHierarchyPath().'/'.$oField->GetCode();
|
||||
|
||||
if (is_null($aRow['label']))
|
||||
{
|
||||
$sValidation = $this->GetValidationArea($sFieldId, '<span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></span>');
|
||||
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply ibo-button ibo-is-alternative" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel ibo-button ibo-is-alternative"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></span></td>'
|
||||
.$this->EndRow();
|
||||
|
||||
if (is_null($aRow['label'])) {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value ibo-field--value" colspan="2">'.$aRow['value'];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label ibo-field--label">'.$aRow['label'].'</td><td class="prop_value ibo-field--value">'.$aRow['value'];
|
||||
}
|
||||
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField))
|
||||
{
|
||||
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField)) {
|
||||
$sReturn .= $sValidationFields;
|
||||
}
|
||||
$sNotifyParentSelectorJS = is_null($sNotifyParentSelector) ? 'null' : "'".addslashes($sNotifyParentSelector)."'";
|
||||
$sAutoApply = $oField->IsAutoApply() ? 'true' : 'false';
|
||||
$sHandlerEquals = $oField->GetHandlerEquals();
|
||||
$sHandlerGetValue = $oField->GetHandlerGetValue();
|
||||
|
||||
|
||||
$sWidgetClass = $oField->GetWidgetClass();
|
||||
$sJSExtraParams = '';
|
||||
if (count($oField->GetWidgetExtraParams()) > 0)
|
||||
@@ -1423,28 +1413,32 @@ class DesignerIconSelectionField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$idx = 0;
|
||||
foreach($this->aAllowedValues as $index => $aValue)
|
||||
{
|
||||
if ($aValue['value'] == $this->defaultValue)
|
||||
{
|
||||
$idxFallback = 0;
|
||||
foreach ($this->aAllowedValues as $index => $aValue) {
|
||||
if ($aValue['value'] == $this->defaultValue) {
|
||||
$idx = $index;
|
||||
break;
|
||||
}
|
||||
//fallback if url of default value contains ../
|
||||
//for contact, icon is http://localhost/env-production/itop-structure/../../images/icons/icons8-customer.svg => not found http://localhost/images/icons/icons8-customer.svg
|
||||
if (basename($aValue['value']) == basename($this->defaultValue)) {
|
||||
$idxFallback = $index;
|
||||
}
|
||||
}
|
||||
if ($idx == 0) {
|
||||
$idx = $idxFallback;
|
||||
}
|
||||
$sJSItems = json_encode($this->aAllowedValues);
|
||||
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
|
||||
if (!$this->IsReadOnly())
|
||||
{
|
||||
if (!$this->IsReadOnly()) {
|
||||
$sDefaultValue = ($this->defaultValue !== '') ? $this->defaultValue : $this->aAllowedValues[$idx]['value'];
|
||||
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/>";
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" /> '.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').'</span></span>';
|
||||
}
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
|
||||
@@ -1459,18 +1453,21 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$aFolderList = [
|
||||
APPROOT.'env-'.utils::GetCurrentEnvironment() => utils::GetAbsoluteUrlModulesRoot(),
|
||||
APPROOT.'images/icons' => utils::GetAbsoluteUrlAppRoot().'images/icons',
|
||||
];
|
||||
if (count(self::$aAllIcons) == 0) {
|
||||
foreach ($aFolderList as $sFolderPath => $sUrlPrefix) {
|
||||
$aIcons = self::FindIconsOnDisk($sFolderPath);
|
||||
ksort($aIcons);
|
||||
|
||||
if (count(self::$aAllIcons) == 0)
|
||||
{
|
||||
self::$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
|
||||
ksort(self::$aAllIcons);
|
||||
foreach ($aIcons as $sFilePath) {
|
||||
self::$aAllIcons[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => $sUrlPrefix.$sFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
$aValues = array();
|
||||
foreach(self::$aAllIcons as $sFilePath)
|
||||
{
|
||||
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
|
||||
}
|
||||
$this->SetAllowedValues($aValues);
|
||||
$this->SetAllowedValues(self::$aAllIcons);
|
||||
}
|
||||
|
||||
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
@@ -1501,26 +1498,29 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
SetupUtils::builddir(dirname($sCacheFile));
|
||||
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
|
||||
}
|
||||
|
||||
return $aFiles;
|
||||
}
|
||||
|
||||
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '', &$aFilesSpecs = [])
|
||||
{
|
||||
$aResult = array();
|
||||
$aResult = [];
|
||||
// Populate automatically the list of icon files
|
||||
if ($hDir = @opendir($sBaseDir.'/'.$sDir))
|
||||
{
|
||||
while (($sFile = readdir($hDir)) !== false)
|
||||
{
|
||||
if ($hDir = @opendir($sBaseDir.'/'.$sDir)) {
|
||||
while (($sFile = readdir($hDir)) !== false) {
|
||||
$aMatches = array();
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
|
||||
{
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile)) {
|
||||
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
|
||||
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
|
||||
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath, $aFilesSpecs));
|
||||
}
|
||||
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
|
||||
$sSize = filesize($sBaseDir.'/'.$sDir.'/'.$sFile);
|
||||
if (isset($aFilesSpecs[$sFile]) && $aFilesSpecs[$sFile] == $sSize) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match("/\.(png|jpg|jpeg|gif|svg)$/i", $sFile, $aMatches)) // png, jp(e)g, gif and svg are considered valid
|
||||
{
|
||||
$aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
|
||||
$aFilesSpecs[$sFile] = $sSize;
|
||||
}
|
||||
}
|
||||
closedir($hDir);
|
||||
@@ -1645,27 +1645,23 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
|
||||
$this->aCSSClasses[] = 'formSelector';
|
||||
|
||||
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
if (count($this->aCSSClasses) > 0) {
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
|
||||
if ($this->IsSorted())
|
||||
{
|
||||
if ($this->IsSorted()) {
|
||||
uasort($this->aSubForms, array(get_class($this), 'SortOnFormLabel'));
|
||||
}
|
||||
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
|
||||
if ($this->IsReadOnly()) {
|
||||
$sDisplayValue = '';
|
||||
$sHiddenValue = '';
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
foreach ($this->aSubForms as $iKey => $aFormData) {
|
||||
if ($iKey == $this->defaultValue) // Default value is actually the index
|
||||
{
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
@@ -1674,12 +1670,9 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
}
|
||||
}
|
||||
$sHtml = "<span $sCSSClasses>".$sDisplayValue.$sHiddenValue."</span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
foreach ($this->aSubForms as $iKey => $aFormData) {
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
$sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
|
||||
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
|
||||
@@ -1687,22 +1680,19 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
}
|
||||
$sHtml .= "</select>";
|
||||
}
|
||||
|
||||
if ($sRenderMode == 'property')
|
||||
{
|
||||
$sHtml .= '</td><td class="prop_icon prop_apply"><span title="Apply" class="ui-icon ui-icon-circle-check"/></td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
|
||||
|
||||
if ($sRenderMode == 'property') {
|
||||
$sHtml .= '</td><td class="prop_icon prop_apply ibo-prop--apply ibo-button ibo-is-alternative"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></span></td><td class="prop_icon prop_cancel ibo-prop--cancel ibo-button ibo-is-alternative"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></span></td></tr>';
|
||||
}
|
||||
foreach($this->aSubForms as $sKey => $aFormData)
|
||||
{
|
||||
foreach ($this->aSubForms as $sKey => $aFormData) {
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sStyle = (($sKey == $this->defaultValue) && $this->oForm->IsDisplayed()) ? '' : 'style="display:none"';
|
||||
$oSubForm = $aFormData['form'];
|
||||
$oSubForm->SetParentForm($this->oForm);
|
||||
$oSubForm->CopySubmitParams($this->oForm);
|
||||
$oSubForm->SetPrefix($this->oForm->GetPrefix().$sKey.'_');
|
||||
|
||||
if ($sRenderMode == 'property')
|
||||
{
|
||||
|
||||
if ($sRenderMode == 'property') {
|
||||
// Note: Managing the visibility of nested subforms had several implications
|
||||
// 1) Attributes are displayed in a table and we have to group them in as many tbodys as necessary to hide/show the various options depending on the current selection
|
||||
// 2) It is not possible to nest tbody tags. Therefore, it is not possible to manage the visibility the same way as it is done for the dialog mode (using nested divs).
|
||||
|
||||
@@ -1094,9 +1094,13 @@ class LoginWebPage extends NiceWebPage
|
||||
if (isset($_SESSION['auth_user']))
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
$sIssue = $_SESSION['pwd_issue'] ?? null;
|
||||
unset($_SESSION['pwd_issue']);
|
||||
$bFailedLogin = ($sIssue != null); // Force the "failed login" flag to display the "issue" message
|
||||
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayChangePwdForm();
|
||||
$oPage->DisplayChangePwdForm($bFailedLogin, $sIssue);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -1128,22 +1128,11 @@ class OQLMenuNode extends MenuNode
|
||||
//$sIcon = MetaModel::GetClassIcon($oSearch->GetClass(), false);
|
||||
|
||||
if ($bSearchPane) {
|
||||
$aParams = array_merge(array('open' => $bSearchOpen, 'table_id' => $sUsageId), $aExtraParams);
|
||||
$aParams = array_merge(['open' => $bSearchOpen, 'table_id' => $sUsageId, 'submit_on_load' => true], $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, 0);
|
||||
}
|
||||
|
||||
//$oPage->add("<p class=\"page-header\">$sIcon ".utils::HtmlEntities(Dict::S($sTitle))."</p>");
|
||||
$oPage->add("<div class='sf_results_area' data-target='search_results'>");
|
||||
$oTitle = TitleUIBlockFactory::MakeForPage($sTitle);
|
||||
$oPage->AddUiBlock($oTitle);
|
||||
|
||||
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, $sUsageId);
|
||||
|
||||
$oPage->add("</div>");
|
||||
|
||||
if ($bEnableBreadcrumb && ($oPage instanceof iTopWebPage)) {
|
||||
// Breadcrumb
|
||||
//$iCount = $oBlock->GetDisplayedCount();
|
||||
@@ -1209,7 +1198,8 @@ class SearchMenuNode extends MenuNode
|
||||
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
|
||||
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$aParams = array_merge(array('table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
|
||||
$sUsageId = 'Menu_'.utils::GetSafeId($this->GetMenuId());
|
||||
$aParams = array_merge(array('table_id' =>$sUsageId), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, 0);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class ThemeHandler
|
||||
{
|
||||
const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
|
||||
|
||||
/** @var \CompileCSSService */
|
||||
private static $oCompileCSSService;
|
||||
|
||||
public static function GetAppRootWithSlashes()
|
||||
@@ -315,11 +316,6 @@ class ThemeHandler
|
||||
// Loading files to import and stylesheet to compile, also getting most recent modification time on overall files
|
||||
$sTmpThemeScssContent = '';
|
||||
$oFindStylesheetObject = new FindStylesheetObject();
|
||||
if (isset($aThemeParameters['variable_imports'])) {
|
||||
foreach ($aThemeParameters['variable_imports'] as $sImport) {
|
||||
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aThemeParameters['utility_imports'])) {
|
||||
foreach ($aThemeParameters['utility_imports'] as $sImport) {
|
||||
@@ -337,6 +333,12 @@ class ThemeHandler
|
||||
$sTmpThemeScssContent .= '@import "'.$sStylesheet.'";'."\n";
|
||||
}
|
||||
|
||||
if (isset($aThemeParameters['variable_imports'])) {
|
||||
foreach ($aThemeParameters['variable_imports'] as $sImport) {
|
||||
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
|
||||
}
|
||||
}
|
||||
|
||||
$iStyleLastModified = $oFindStylesheetObject->GetLastModified();
|
||||
|
||||
$aIncludedImages=static::GetIncludedImages($aThemeParametersWithVersion, $oFindStylesheetObject->GetAllStylesheetPaths(), $sThemeId);
|
||||
|
||||
@@ -82,6 +82,12 @@ class UIExtKeyWidget
|
||||
$aArgs = [], $bSearchMode = false, &$sInputType = ''
|
||||
)
|
||||
{
|
||||
// we will only use key & name, so let's reduce fields loaded !
|
||||
$aAttToLoad = [
|
||||
$sClass => [], // nothing, id and friendlyname are automatically added by the API
|
||||
];
|
||||
$oAllowedValues->OptimizeColumnLoad($aAttToLoad);
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
|
||||
@@ -246,7 +252,7 @@ class UIExtKeyWidget
|
||||
}
|
||||
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\" tabindex=\"0\"></select>";
|
||||
$sJsonOptions = json_encode($aOptions);
|
||||
$sJsonOptions = str_replace('\\', '\\\\', json_encode($aOptions));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
||||
@@ -976,7 +982,7 @@ JS
|
||||
public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
|
||||
{
|
||||
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
|
||||
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="overflow:auto;background:#fff;margin-bottom:5px;" id="tree_'.$this->iId.'">');
|
||||
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">');
|
||||
$oPage->add('<table style="width:100%"><tr><td>');
|
||||
if (is_null($sFilter))
|
||||
{
|
||||
|
||||
@@ -538,7 +538,7 @@ class utils
|
||||
*/
|
||||
public static function ReadMultipleSelection($oFullSetFilter)
|
||||
{
|
||||
$aSelectedObj = utils::ReadParam('selectObject[]', array());
|
||||
$aSelectedObj = utils::ReadParam('selectObject', array());
|
||||
$sSelectionMode = utils::ReadParam('selectionMode', '');
|
||||
if ($sSelectionMode != '') {
|
||||
// Paginated selection
|
||||
@@ -2638,9 +2638,21 @@ class utils
|
||||
if(!empty($aMentionsAllowedClasses)) {
|
||||
$aDefaultConf['mentions'] = [];
|
||||
|
||||
foreach($aMentionsAllowedClasses as $sMentionChar => $sMentionClass) {
|
||||
foreach($aMentionsAllowedClasses as $sMentionMarker => $sMentionScope) {
|
||||
// Retrieve mention class
|
||||
// - First test if the conf is a simple Datamodel class
|
||||
if (MetaModel::IsValidClass($sMentionScope)) {
|
||||
$sMentionClass = $sMentionScope;
|
||||
}
|
||||
// - Otherwise it must be a valid OQL
|
||||
else {
|
||||
$oTmpSearch = DBSearch::FromOQL($sMentionScope);
|
||||
$sMentionClass = $oTmpSearch->GetClass();
|
||||
unset($oTmpSearch);
|
||||
}
|
||||
|
||||
// Note: Endpoints are defaults only and should be overloaded by other GUIs such as the end-users portal
|
||||
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&target_class='.$sMentionClass.'&needle={encodedQuery}';
|
||||
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&marker='.$sMentionMarker.'&needle={encodedQuery}';
|
||||
$sMentionItemUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.$sMentionClass.'&id={id}';
|
||||
|
||||
$sMentionItemPictureTemplate = (empty(MetaModel::GetImageAttributeCode($sMentionClass))) ? '' : <<<HTML
|
||||
@@ -2650,12 +2662,12 @@ HTML;
|
||||
<li class="ibo-vendors-ckeditor--autocomplete-item" data-id="{id}">{$sMentionItemPictureTemplate}<span class="ibo-vendors-ckeditor--autocomplete-item-title">{friendlyname}</span></li>
|
||||
HTML;
|
||||
$sMentionOutputTemplate = <<<HTML
|
||||
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionChar}{friendlyname}</a>
|
||||
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionMarker}{friendlyname}</a>
|
||||
HTML;
|
||||
|
||||
$aDefaultConf['mentions'][] = [
|
||||
'feed' => $sMentionEndpoint,
|
||||
'marker' => $sMentionChar,
|
||||
'marker' => $sMentionMarker,
|
||||
'minChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'),
|
||||
'itemTemplate' => $sMentionItemTemplate,
|
||||
'outputTemplate' => $sMentionOutputTemplate,
|
||||
@@ -2814,15 +2826,30 @@ HTML;
|
||||
* Check if iTop is in a development environment (VCS vs build number)
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 2.6.0 method creation
|
||||
* @since 3.0.0 add the `developer_mode.enabled` config parameter
|
||||
*
|
||||
* @use `developer_mode.enabled` config parameter
|
||||
* @use ITOP_REVISION
|
||||
*/
|
||||
public static function IsDevelopmentEnvironment()
|
||||
{
|
||||
if (! defined('ITOP_REVISION')) {
|
||||
$oConfig = utils::GetConfig();
|
||||
$bIsDevEnvInConfig = $oConfig->Get('developer_mode.enabled');
|
||||
if ($bIsDevEnvInConfig === true) {
|
||||
return true;
|
||||
}
|
||||
if ($bIsDevEnvInConfig === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!defined('ITOP_REVISION')) {
|
||||
//defensive behaviour: by default we are not in dev environment
|
||||
//can happen even in production (unattended install for example) or with exotic use of iTop
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return ITOP_REVISION === 'svn';
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"ext-mysqli": "*",
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "6.3.5",
|
||||
"nikic/php-parser": "^3.1",
|
||||
"nikic/php-parser": "^4.12.0",
|
||||
"pear/archive_tar": "1.4.13",
|
||||
"pelago/emogrifier": "2.1.0",
|
||||
"scssphp/scssphp": "1.0.6",
|
||||
@@ -50,11 +50,7 @@
|
||||
"classmap": [
|
||||
"core",
|
||||
"application",
|
||||
"sources/application",
|
||||
"sources/Composer",
|
||||
"sources/Controller",
|
||||
"sources/Form",
|
||||
"sources/Renderer"
|
||||
"sources"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"core/dbobjectsearch.class.php",
|
||||
|
||||
23
composer.lock
generated
23
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "62e394b1ef30b4e716e3e3e519de11dd",
|
||||
"content-hash": "75f17b71005971207815906ec7e9cf67",
|
||||
"packages": [
|
||||
{
|
||||
"name": "combodo/tcpdf",
|
||||
@@ -68,24 +68,25 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v3.1.5",
|
||||
"version": "v4.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce"
|
||||
"reference": "6608f01670c3cc5079e18c1dab1104e002579143"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
|
||||
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
|
||||
"reference": "6608f01670c3cc5079e18c1dab1104e002579143",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-tokenizer": "*",
|
||||
"php": ">=5.5"
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|~5.0"
|
||||
"ircmaxell/php-yacc": "^0.0.7",
|
||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/php-parse"
|
||||
@@ -93,7 +94,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
"dev-master": "4.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -115,7 +116,11 @@
|
||||
"parser",
|
||||
"php"
|
||||
],
|
||||
"time": "2018-02-28T20:30:58+00:00"
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
|
||||
},
|
||||
"time": "2021-07-21T10:44:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
|
||||
@@ -388,7 +388,12 @@ class AsyncSendEmail extends AsyncTask
|
||||
return "Bug - the email should be sent in synchronous mode";
|
||||
|
||||
case EMAIL_SEND_ERROR:
|
||||
return "Failed: ".implode(', ', $aIssues);
|
||||
if (is_array($aIssues)) {
|
||||
$sMessage = "Sending eMail failed: ".implode(', ', $aIssues);
|
||||
} else {
|
||||
$sMessage = "Sending eMail failed.";
|
||||
}
|
||||
throw new Exception($sMessage);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -4159,13 +4159,13 @@ class AttributeText extends AttributeString
|
||||
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
|
||||
$sValue = self::RenderWikiHtml($sValue);
|
||||
|
||||
return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
|
||||
return "<div $sStyle>$sValue</div>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = self::RenderWikiHtml($sValue, true /* wiki only */);
|
||||
|
||||
return "<div class=\"HTML\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
|
||||
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,4 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2021 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Persistent class (internal) cmdbChange
|
||||
*
|
||||
@@ -24,6 +6,7 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
|
||||
|
||||
/**
|
||||
* A change as requested/validated at once by user, may groups many atomic changes
|
||||
@@ -53,7 +36,7 @@ class CMDBChange extends DBObject
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"User", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum('interactive,csv-interactive,csv-import.php,webservice-soap,webservice-rest,synchro-data-source,email-processing,custom-extension'), "sql"=>"origin", "default_value"=>"interactive", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum(implode(',', [CMDBChangeOrigin::INTERACTIVE, CMDBChangeOrigin::CSV_INTERACTIVE, CMDBChangeOrigin::CSV_IMPORT, CMDBChangeOrigin::WEBSERVICE_SOAP, CMDBChangeOrigin::WEBSERVICE_REST, CMDBChangeOrigin::SYNCHRO_DATA_SOURCE, CMDBChangeOrigin::EMAIL_PROCESSING, CMDBChangeOrigin::CUSTOM_EXTENSION])), "sql"=>"origin", "default_value"=>CMDBChangeOrigin::INTERACTIVE, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -606,7 +606,7 @@ class CMDBSource
|
||||
{
|
||||
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
|
||||
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
|
||||
IssueLog::Trace("$sShortSQL", 'cmdbsource');
|
||||
IssueLog::Trace("$sShortSQL", LogChannels::CMDB_SOURCE);
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
@@ -695,12 +695,11 @@ class CMDBSource
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
$bHasExistingTransactions = self::IsInsideTransaction();
|
||||
if (!$bHasExistingTransactions)
|
||||
{
|
||||
IssueLog::Trace("START TRANSACTION $sCaller", 'cmdbsource');
|
||||
if (!$bHasExistingTransactions) {
|
||||
IssueLog::Trace("START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
|
||||
self::DBQuery('START TRANSACTION');
|
||||
} else {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", 'cmdbsource');
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
|
||||
}
|
||||
|
||||
self::AddTransactionLevel();
|
||||
@@ -720,21 +719,20 @@ class CMDBSource
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
if (!self::IsInsideTransaction()) {
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource');
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
|
||||
self::RemoveLastTransactionLevel();
|
||||
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource');
|
||||
if (self::IsInsideTransaction()) {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
|
||||
return;
|
||||
}
|
||||
IssueLog::Trace("COMMIT $sCaller", 'cmdbsource');
|
||||
IssueLog::Trace("COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
self::DBQuery('COMMIT');
|
||||
}
|
||||
|
||||
@@ -755,20 +753,19 @@ class CMDBSource
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
if (!self::IsInsideTransaction()) {
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource');
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
self::RemoveLastTransactionLevel();
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource');
|
||||
if (self::IsInsideTransaction()) {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource');
|
||||
IssueLog::Trace("ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
self::DBQuery('ROLLBACK');
|
||||
}
|
||||
|
||||
@@ -904,11 +901,12 @@ class CMDBSource
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
* @param int $iMode
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function QueryToArray($sSql)
|
||||
public static function QueryToArray($sSql, $iMode = MYSQLI_BOTH)
|
||||
{
|
||||
$aData = array();
|
||||
$oKPI = new ExecutionKPI();
|
||||
@@ -927,7 +925,7 @@ class CMDBSource
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||
}
|
||||
|
||||
while ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
|
||||
while ($aRow = $oResult->fetch_array($iMode))
|
||||
{
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
|
||||
@@ -1237,6 +1237,30 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.prefilter_state_changes_on_logs' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether the "State changes" filter should be set by default on all log tabs.',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.prefilter_edits_on_logs' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether the "Edits" filter should be set by default on all log tabs.',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.hide_avatars' => [
|
||||
'type' => 'array',
|
||||
'description' => 'GUIs IDs ("backoffice", "itop-portal" for the standard end-users portal, ...) in which the user avatars should be hidden and replaced if possible by their initials (eg. array("backoffice", "itop-portal", "another-portal-id"))',
|
||||
'default' => [],
|
||||
'value' => [],
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.show_author_name_below_entries' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to show the author friendlyname next to the date on the last entry.',
|
||||
@@ -1263,9 +1287,9 @@ class Config
|
||||
],
|
||||
'mentions.allowed_classes' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete (eg. "@" => "Person")',
|
||||
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value can be either a DM class or a valid OQL (eg. "@" => "Person", "?" => "SELECT FAQ WHERE status = \'published\'")',
|
||||
'default' => [
|
||||
'@' => 'Person',
|
||||
'@' => 'SELECT Person WHERE status = \'active\'',
|
||||
],
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
@@ -1399,6 +1423,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'developer_mode.enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true then unlocks dev env functionalities, see \utils::IsDevelopmentEnvironment',
|
||||
'default' => null,
|
||||
'value' => null,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'theme.enable_precompilation' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If false, theme compilation will not use any precompiled file setup optimization.)',
|
||||
|
||||
@@ -1277,13 +1277,20 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// If the object if not issued from a query but constructed programmatically
|
||||
// the label may be empty. In this case run a query to get the object's friendly name
|
||||
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey, false);
|
||||
if (is_object($oTmpObj))
|
||||
{
|
||||
$sObjOql = 'SELECT '.$sObjClass.' WHERE id='.$sObjKey;
|
||||
$oObjFilter = DBSearch::FromOQL($sObjOql);
|
||||
$oSet = new DBObjectSet($oObjFilter);
|
||||
|
||||
// we will only use id and friendlyname, so let's remove other fields !
|
||||
$aAttToLoad = [
|
||||
$sObjClass => [],
|
||||
];
|
||||
$oSet->OptimizeColumnLoad($aAttToLoad);
|
||||
|
||||
$oTmpObj = $oSet->Fetch();
|
||||
if (is_object($oTmpObj)) {
|
||||
$sHtmlLabel = $oTmpObj->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// May happen in case the target object is not in the list of allowed values for this attribute
|
||||
$sHtmlLabel = "<em>$sObjClass::$sObjKey</em>";
|
||||
}
|
||||
@@ -1859,9 +1866,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
/** @var \AttributeExternalKey $oAtt */
|
||||
$sTargetClass = $oAtt->GetTargetClass();
|
||||
$oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/);
|
||||
if (is_null($oTargetObj))
|
||||
{
|
||||
if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) {
|
||||
return "Target object not found ($sTargetClass::$toCheck)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,22 +63,23 @@ else
|
||||
|
||||
abstract class DBSearch
|
||||
{
|
||||
/** @internal */
|
||||
/** @internal */
|
||||
const JOIN_POINTING_TO = 0;
|
||||
/** @internal */
|
||||
/** @internal */
|
||||
const JOIN_REFERENCED_BY = 1;
|
||||
|
||||
protected $m_bNoContextParameters = false;
|
||||
/** @var array For {@see iQueryModifier} impl */
|
||||
protected $m_aModifierProperties = array();
|
||||
protected $m_bArchiveMode = false;
|
||||
protected $m_bShowObsoleteData = true;
|
||||
|
||||
/**
|
||||
* DBSearch constructor.
|
||||
*
|
||||
* @api
|
||||
* @see DBSearch::FromOQL()
|
||||
*/
|
||||
/**
|
||||
* DBSearch constructor.
|
||||
*
|
||||
* @api
|
||||
* @see DBSearch::FromOQL()
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->Init();
|
||||
|
||||
@@ -242,7 +242,7 @@ class DeletionPlan
|
||||
|
||||
public function SetDeletionIssues($oObject, $aIssues, $bSecurityIssue)
|
||||
{
|
||||
if (count($aIssues) > 0)
|
||||
if (count($aIssues ?? []) > 0)
|
||||
{
|
||||
$sClass = get_class($oObject);
|
||||
$iId = $oObject->GetKey();
|
||||
|
||||
@@ -1448,7 +1448,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
$oP->add("<div class=\"not-printable\">\n");
|
||||
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_CYAN, 'ds_flash');
|
||||
$oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]);
|
||||
|
||||
$oUiSearchBlock->SetIsCollapsible(true);
|
||||
$oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html(
|
||||
<<<EOF
|
||||
<div id="ds_flash" class="search_box ibo-display-graph--search-box">
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Parser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
|
||||
@@ -80,38 +82,49 @@ class iTopConfigParser
|
||||
* @param \PhpParser\Parser $oParser
|
||||
* @param $sConfig
|
||||
*
|
||||
* @return \Combodo\iTop\Config\Validator\ConfigNodesVisitor
|
||||
* @return void
|
||||
*/
|
||||
private function BrowseFile(\PhpParser\Parser $oParser, $sConfig)
|
||||
private function BrowseFile(Parser $oParser, $sConfig)
|
||||
{
|
||||
$prettyPrinter = new Standard();
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
$aNodes = $oParser->parse($sConfig);
|
||||
}
|
||||
catch (\Error $e)
|
||||
{
|
||||
catch (\Error $e) {
|
||||
$sMessage = Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
|
||||
$this->oException = new \Exception($sMessage, 0, $e);
|
||||
}
|
||||
|
||||
foreach ($aNodes as $oAssignation)
|
||||
{
|
||||
if (! $oAssignation instanceof Assign)
|
||||
{
|
||||
foreach ($aNodes as $sKey => $oNode) {
|
||||
// With PhpParser 3 we had an Assign node at root
|
||||
// In PhpParser 4 the root node is now an Expression
|
||||
|
||||
if (false === ($oNode instanceof \PhpParser\Node\Stmt\Expression)) {
|
||||
continue;
|
||||
}
|
||||
/** @var \PhpParser\Node\Stmt\Expression $oNode */
|
||||
|
||||
if (false === ($oNode->expr instanceof Assign)) {
|
||||
continue;
|
||||
}
|
||||
/** @var Assign $oAssignation */
|
||||
$oAssignation = $oNode->expr;
|
||||
|
||||
if (false === ($oAssignation->var instanceof Variable)) {
|
||||
continue;
|
||||
}
|
||||
if (false === ($oAssignation->expr instanceof PhpParser\Node\Expr\Array_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sCurrentRootVar = $oAssignation->var->name;
|
||||
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap))
|
||||
{
|
||||
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap)) {
|
||||
continue;
|
||||
}
|
||||
$aCurrentRootVarMap =& $this->aVarsMap[$sCurrentRootVar];
|
||||
|
||||
foreach ($oAssignation->expr->items as $oItem)
|
||||
{
|
||||
foreach ($oAssignation->expr->items as $oItem) {
|
||||
$sValue = $prettyPrinter->prettyPrintExpr($oItem->value);
|
||||
$aCurrentRootVarMap[$oItem->key->value] = $sValue;
|
||||
}
|
||||
|
||||
@@ -577,7 +577,7 @@ JS
|
||||
oEditor.on( 'instanceReady', function() {
|
||||
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').length == 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen" style="background-image:url(\\'$sAbsoluteUrlAppRoot/images/full-screen.png\\')"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button editor-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').on('click', function() {
|
||||
oEditor.execCommand('maximize');
|
||||
if ($(this).closest('.cke_maximized').length != 0)
|
||||
|
||||
@@ -545,6 +545,7 @@ class LogChannels
|
||||
public const DEADLOCK = 'DeadLock';
|
||||
public const INLINE_IMAGE = 'InlineImage';
|
||||
public const PORTAL = 'portal';
|
||||
public const CMDB_SOURCE = 'cmdbsource';
|
||||
}
|
||||
|
||||
|
||||
@@ -676,11 +677,22 @@ abstract class LogAPI
|
||||
* @param string $sChannel
|
||||
*
|
||||
* @return string one of the LEVEL_* const value : the one configured it if exists, otherwise default log level for this channel
|
||||
* Config can be done globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
|
||||
* Or per channel : `'log_level_min' => ['InlineImage' => LogAPI::LEVEL_TRACE, 'UserRequest' => LogAPI::LEVEL_TRACE],`
|
||||
* Config can be set :
|
||||
* * globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
|
||||
* * per channel :
|
||||
* ```
|
||||
* 'log_level_min' => [
|
||||
* '' => LogAPI::LEVEL_ERROR, // default log level for channels not listed below
|
||||
* 'InlineImage' => LogAPI::LEVEL_TRACE,
|
||||
* 'UserRequest' => LogAPI::LEVEL_TRACE
|
||||
* ],
|
||||
* ```
|
||||
*
|
||||
* @uses \LogAPI::GetConfig()
|
||||
* @uses `log_level_min` config parameter
|
||||
* @uses \LogAPI::GetLevelDefault
|
||||
*
|
||||
* @link https://www.itophub.io/wiki/page?id=3_0_0%3Aadmin%3Alog iTop log reference
|
||||
*/
|
||||
protected static function GetMinLogLevel($sChannel)
|
||||
{
|
||||
@@ -704,7 +716,12 @@ abstract class LogAPI
|
||||
}
|
||||
|
||||
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) {
|
||||
return $sLogLevelMin[$sChannel];
|
||||
return $sLogLevelMin[static::CHANNEL_DEFAULT];
|
||||
}
|
||||
|
||||
// Even though the *self*::CHANNEL_DEFAULT is set to '' in the current class (LogAPI), the test below is necessary as the CHANNEL_DEFAULT constant can be (and is!) overloaded in derivated classes, don't remove this test to factorize it with the previous one.
|
||||
if (isset($sLogLevelMin[''])) {
|
||||
return $sLogLevelMin[''];
|
||||
}
|
||||
|
||||
return static::GetLevelDefault();
|
||||
@@ -824,6 +841,7 @@ class DeadLockLog extends LogAPI
|
||||
class DeprecatedCallsLog extends LogAPI
|
||||
{
|
||||
public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method';
|
||||
public const ENUM_CHANNEL_PHP_LIBMETHOD = 'deprecated-php-libmethod';
|
||||
public const ENUM_CHANNEL_FILE = 'deprecated-file';
|
||||
public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD;
|
||||
|
||||
@@ -832,12 +850,79 @@ class DeprecatedCallsLog extends LogAPI
|
||||
/** @var \FileLog we want our own instance ! */
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
/**
|
||||
* @param string|null $sTargetFile
|
||||
*
|
||||
*@uses \set_error_handler() to catch deprecated notices
|
||||
*
|
||||
* @since 3.0.0 N°3002 logs deprecated notices in called code
|
||||
*/
|
||||
public static function Enable($sTargetFile = null): void
|
||||
{
|
||||
if (empty($sTargetFile)) {
|
||||
$sTargetFile = APPROOT.'log/deprecated-calls.log';
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
|
||||
try {
|
||||
$bIsLogLevelEnabled = static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD);
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
$bIsLogLevelEnabled = false;
|
||||
}
|
||||
if ($bIsLogLevelEnabled) {
|
||||
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will catch a message for all E_DEPRECATED and E_USER_DEPRECATED errors.
|
||||
* This handler is set in DeprecatedCallsLog::Enable
|
||||
*
|
||||
* @param int $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.0.0 N°3002
|
||||
* @noinspection SpellCheckingInspection
|
||||
*/
|
||||
public static function DeprecatedNoticesErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool
|
||||
{
|
||||
if (
|
||||
(\E_USER_DEPRECATED !== $errno)
|
||||
&& (\E_DEPRECATED !== $errno)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
|
||||
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call
|
||||
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
|
||||
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
|
||||
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
|
||||
// We are generating a trigger_error ourselves, we don't want to trace them !
|
||||
return false;
|
||||
}
|
||||
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
|
||||
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
|
||||
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
|
||||
|
||||
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
|
||||
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
|
||||
$sCallerObject = $aStack[3]['class'];
|
||||
$sCallerMethod = $aStack[3]['function'];
|
||||
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
|
||||
}
|
||||
|
||||
if (!empty($errstr)) {
|
||||
$sMessage .= ' : '.$errstr;
|
||||
}
|
||||
|
||||
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_LIBMETHOD);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static function GetLevelDefault(): string
|
||||
@@ -897,16 +982,17 @@ class DeprecatedCallsLog extends LogAPI
|
||||
}
|
||||
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
|
||||
$sDeprecatedObject = $aStack[1]['class'];
|
||||
$sDeprecatedMethod = $aStack[1]['function'];
|
||||
$sCallerFile = $aStack[1]['file'];
|
||||
$sCallerLine = $aStack[1]['line'];
|
||||
$iStackDeprecatedMethodLevel = 1; // level 0 = current method, level 1 = method containing the `NotifyDeprecatedPhpMethod` call
|
||||
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
|
||||
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
|
||||
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
|
||||
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
|
||||
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
|
||||
|
||||
if (array_key_exists(2, $aStack)) {
|
||||
$sCallerObject = $aStack[2]['class'];
|
||||
$sCallerMethod = $aStack[2]['function'];
|
||||
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 2 = caller of the deprecated method
|
||||
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
|
||||
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'];
|
||||
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'];
|
||||
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
|
||||
}
|
||||
|
||||
@@ -914,12 +1000,12 @@ class DeprecatedCallsLog extends LogAPI
|
||||
$sMessage .= ' : '.$sAdditionalMessage;
|
||||
}
|
||||
|
||||
static::Warning($sMessage, static::ENUM_CHANNEL_PHP_METHOD);
|
||||
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
|
||||
}
|
||||
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
|
||||
{
|
||||
if (utils::IsDevelopmentEnvironment()) {
|
||||
if (true === utils::IsDevelopmentEnvironment()) {
|
||||
trigger_error($sMessage, E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
|
||||
@@ -1116,7 +1116,6 @@ abstract class MetaModel
|
||||
*/
|
||||
final public static function GetFilterCodeOrigin($sClass, $sAttCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
self::_check_subclass($sClass);
|
||||
|
||||
return self::$m_aFilterOrigins[$sClass][$sAttCode];
|
||||
@@ -6957,7 +6956,7 @@ abstract class MetaModel
|
||||
* @param int $iKey id value of the object to retrieve
|
||||
* @param bool $bMustBeFound see throws ArchivedObjectException
|
||||
* @param bool $bAllowAllData if true then user rights will be bypassed - use with care!
|
||||
* @param null $aModifierProperties
|
||||
* @param array $aModifierProperties properties for {@see iQueryModifier} impl
|
||||
*
|
||||
* @return \DBObject null if : (the object is not found) or (archive mode disabled and object is archived and
|
||||
* $bMustBeFound=false)
|
||||
@@ -6977,12 +6976,9 @@ abstract class MetaModel
|
||||
|
||||
if (!utils::IsArchiveMode() && $oObject->IsArchived())
|
||||
{
|
||||
if ($bMustBeFound)
|
||||
{
|
||||
if ($bMustBeFound) {
|
||||
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6990,6 +6986,35 @@ abstract class MetaModel
|
||||
return $oObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param int $iKey
|
||||
*
|
||||
* @return bool True if the object of $sClass and $iKey exists in the DB -no matter the current user restrictions-, false otherwise meaning:
|
||||
* - It could be in memory for now and is not persisted yet
|
||||
* - It is neither in memory nor DB
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
* @since 3.0.0 N°4173
|
||||
*/
|
||||
public static function IsObjectInDB(string $sClass, int $iKey): bool
|
||||
{
|
||||
// Note: We take the root class to ensure that there is a corresponding table in the DB
|
||||
// as some intermediate classes can have no table in the DB.
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
|
||||
$sTable = MetaModel::DBGetTable($sRootClass);
|
||||
$sKeyCol = MetaModel::DBGetKey($sRootClass);
|
||||
$sEscapedKey = CMDBSource::Quote($iKey);
|
||||
|
||||
$sQuery = "SELECT count(*) FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
|
||||
$iCount = (int) CMDBSource::QueryToScalar($sQuery);
|
||||
|
||||
return $iCount === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4
|
||||
* module compatibility, see N.1108)
|
||||
@@ -7244,7 +7269,6 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
// $aValues is an array of $sAttCode => $value
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aValues);
|
||||
if (!self::DBIsReadOnly()) {
|
||||
|
||||
@@ -504,21 +504,38 @@ class ormCaseLog {
|
||||
$sHtml .= '</td></tr></table>';
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new entry to the log or merge the given text into the currently modified entry
|
||||
* Add a new entry to the log or merge the given text into the currently modified entry
|
||||
* and updates the internal index
|
||||
* @param $sText string The text of the new entry
|
||||
*
|
||||
* @param string $sText The text of the new entry
|
||||
* @param string $sOnBehalfOf Display this name instead of current user name
|
||||
* @param null|int $iOnBehalfOfId Use this UserId to author this Entry. If $sOnBehalfOf equals '', it'll be replaced by this User friendlyname
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*
|
||||
* @since 3.0.0 New $iOnBehalfOfId parameter
|
||||
* @since 3.0.0 May throw \ArchivedObjectException exception
|
||||
*/
|
||||
public function AddLogEntry($sText, $sOnBehalfOf = '')
|
||||
public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = null)
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize($sText);
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat());
|
||||
if ($sOnBehalfOf == '')
|
||||
{
|
||||
if ($sOnBehalfOf == '' && $iOnBehalfOfId === null) {
|
||||
$sOnBehalfOf = UserRights::GetUserFriendlyName();
|
||||
$iUserId = UserRights::GetUserId();
|
||||
}
|
||||
elseif ($iOnBehalfOfId !== null) {
|
||||
$iUserId = $iOnBehalfOfId;
|
||||
/* @var User $oUser */
|
||||
$oUser = MetaModel::GetObject('User', $iUserId, false, true);
|
||||
if ($oUser !== null && $sOnBehalfOf === '') {
|
||||
$sOnBehalfOf = $oUser->GetFriendlyName();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iUserId = null;
|
||||
|
||||
@@ -388,15 +388,18 @@ class SQLObjectQuery extends SQLQuery
|
||||
{
|
||||
if (count($this->__aSelectedIdFields) > 0)
|
||||
{
|
||||
$aCountFields = array();
|
||||
foreach ($this->__aSelectedIdFields as $sFieldExpr)
|
||||
{
|
||||
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
|
||||
$aCountFields = [];
|
||||
$aCountI = [];
|
||||
$i = 0;
|
||||
foreach ($this->__aSelectedIdFields as $sFieldExpr) {
|
||||
$aCountFields[] = "COALESCE($sFieldExpr, 0) AS idCount$i"; // Null values are excluded from the count
|
||||
$aCountI[] = 'idCount'.$i++;
|
||||
}
|
||||
$sCountFields = implode(', ', $aCountFields);
|
||||
$sCountI = implode('+ ', $aCountI);
|
||||
// Count can be limited for performance reason, in this case the total amount is not important,
|
||||
// we only need to know if the number of entries is greater than a certain amount.
|
||||
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _alderaan_";
|
||||
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _alderaan_ WHERE $sCountI>0";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -333,77 +333,135 @@ abstract class User extends cmdbAbstractObject
|
||||
{
|
||||
parent::DoCheckToWrite();
|
||||
|
||||
// Note: This MUST be factorized later: declare unique keys (set of columns) in the data model
|
||||
$oAddon = UserRights::GetModuleInstance();
|
||||
$aChanges = $this->ListChanges();
|
||||
if (array_key_exists('login', $aChanges))
|
||||
{
|
||||
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0)
|
||||
{
|
||||
if (array_key_exists('login', $aChanges)) {
|
||||
// Check login uniqueness
|
||||
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0) {
|
||||
$sNewLogin = $aChanges['login'];
|
||||
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
|
||||
if (!$this->IsNew())
|
||||
{
|
||||
if (!$this->IsNew()) {
|
||||
$oSearch->AddCondition('id', $this->GetKey(), '!=');
|
||||
}
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin));
|
||||
if ($oSet->Count() > 0)
|
||||
{
|
||||
if ($oSet->Count() > 0) {
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that this user has at least one profile assigned when profiles have changed
|
||||
if (array_key_exists('profile_list', $aChanges))
|
||||
{
|
||||
$oSet = $this->Get('profile_list');
|
||||
if ($oSet->Count() == 0)
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
|
||||
|
||||
// A User cannot disable himself
|
||||
if ($this->IsCurrentUser()) {
|
||||
if (isset($aChanges['status']) && ($this->Get('status') == 'disabled')) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:StatusChangeIsNotAllowed');
|
||||
}
|
||||
}
|
||||
|
||||
// Check that this user has at least one profile assigned when profiles have changed
|
||||
if (array_key_exists('profile_list', $aChanges)) {
|
||||
/** @var \DBObjectSet $oSet */
|
||||
$oSet = $this->Get('profile_list');
|
||||
if ($oSet->Count() == 0) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
|
||||
}
|
||||
|
||||
// A user cannot add to themself a profile denying the access to the backoffice
|
||||
$aForbiddenProfiles = PortalDispatcherData::GetData('backoffice')['deny'];
|
||||
if ($this->IsCurrentUser()) {
|
||||
$oSet->Rewind();
|
||||
$aProfiles = [];
|
||||
while ($oUserProfile = $oSet->Fetch()) {
|
||||
$sProfile = $oUserProfile->Get('profile');
|
||||
if (in_array($sProfile, $aForbiddenProfiles)) {
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:ProfileNotAllowed', $sProfile);
|
||||
}
|
||||
$aProfiles[$oUserProfile->Get('profileid')] = $sProfile;
|
||||
}
|
||||
|
||||
if (!in_array(ADMIN_PROFILE_NAME, $aProfiles)) {
|
||||
// Check if the user is yet allowed to modify Users
|
||||
if (method_exists($oAddon, 'ResetCache')) {
|
||||
$aCurrentProfiles = $_SESSION['profile_list'] ?? null;
|
||||
// Set the current profiles into a session variable (not yet in the database)
|
||||
$_SESSION['profile_list'] = $aProfiles;
|
||||
|
||||
$oAddon->ResetCache();
|
||||
if (!$oAddon->IsActionAllowed($this, 'User', UR_ACTION_MODIFY, null)) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
|
||||
}
|
||||
$oAddon->ResetCache();
|
||||
|
||||
if (is_null($aCurrentProfiles)) {
|
||||
unset($_SESSION['profile_list']);
|
||||
} else {
|
||||
$_SESSION['profile_list'] = $aCurrentProfiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only administrators can manage administrators
|
||||
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator())
|
||||
{
|
||||
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator()) {
|
||||
$this->m_aCheckIssues[] = Dict::S('UI:Login:Error:AccessRestricted');
|
||||
}
|
||||
|
||||
if (!UserRights::IsAdministrator())
|
||||
{
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$oAddon = UserRights::GetModuleInstance();
|
||||
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs'))
|
||||
{
|
||||
if ((empty($this->GetOriginal('contactid')) && !($this->IsNew())) || empty($this->Get('contactid')))
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
|
||||
// A contact is mandatory (an administrator can bypass it but not for himself)
|
||||
if ((!UserRights::IsAdministrator() || $this->IsCurrentUser())
|
||||
&& !$this->IsNew()
|
||||
&& isset($aChanges['contactid'])
|
||||
&& empty($this->Get('contactid'))) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
|
||||
}
|
||||
|
||||
// Allowed orgs must contains the user org (if any)
|
||||
if (!empty($this->Get('org_id')) && !UserRights::IsAdministrator($this)) {
|
||||
// Get the user org and all its parent orgs
|
||||
$aUserOrgs = [$this->Get('org_id')];
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
|
||||
if ($sHierarchicalKeyCode !== false) {
|
||||
$sOrgQuery = 'SELECT Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' ABOVE Root.id WHERE Root.id = :id';
|
||||
$oOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sOrgQuery), [], ['id' => $this->Get('org_id')]);
|
||||
while ($aRow = $oOrgSet->FetchAssoc()) {
|
||||
$oOrg = $aRow['Org'];
|
||||
$aUserOrgs[] = $oOrg->GetKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
|
||||
if (count($aOrgs) > 0)
|
||||
{
|
||||
// Check that the modified User belongs to one of our organization
|
||||
if (!in_array($this->GetOriginal('org_id'), $aOrgs) && !in_array($this->Get('org_id'), $aOrgs))
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:UserOrganizationNotAllowed');
|
||||
}
|
||||
// Check users with restricted organizations when allowed organizations have changed
|
||||
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges))
|
||||
{
|
||||
$oSet = $this->get('allowed_org_list');
|
||||
if ($oSet->Count() == 0)
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
|
||||
}
|
||||
else
|
||||
{
|
||||
$aModifiedLinks = $oSet->ListModifiedLinks();
|
||||
foreach ($aModifiedLinks as $oLink)
|
||||
{
|
||||
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs))
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
|
||||
}
|
||||
}
|
||||
// Check the allowed orgs list
|
||||
$oSet = $this->get('allowed_org_list');
|
||||
if ($oSet->Count() > 0) {
|
||||
$bFound = false;
|
||||
while ($oOrg = $oSet->Fetch()) {
|
||||
if (in_array($oOrg->Get('allowed_org_id'), $aUserOrgs)) {
|
||||
$bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$bFound) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AllowedOrgsMustContainUserOrg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!UserRights::IsAdministrator()) {
|
||||
$oUser = UserRights::GetUserObject();
|
||||
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs')) {
|
||||
$aOrgs = $oAddon->GetUserOrgs($oUser, ''); // Modifier allowed orgs
|
||||
if (count($aOrgs) > 0) {
|
||||
// Check that the modified User belongs to one of our organization
|
||||
if (!in_array($this->GetOriginal('org_id'), $aOrgs) && !in_array($this->Get('org_id'), $aOrgs)) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:UserOrganizationNotAllowed');
|
||||
}
|
||||
// Check users with restricted organizations when allowed organizations have changed
|
||||
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges)) {
|
||||
$oSet = $this->get('allowed_org_list');
|
||||
if ($oSet->Count() == 0) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
|
||||
} else {
|
||||
$aModifiedLinks = $oSet->ListModifiedLinks();
|
||||
foreach ($aModifiedLinks as $oLink) {
|
||||
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs)) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,14 +471,26 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function DoCheckToDelete(&$oDeletionPlan)
|
||||
{
|
||||
parent::DoCheckToDelete($oDeletionPlan);
|
||||
|
||||
// A user cannot suppress himself
|
||||
if ($this->IsCurrentUser()) {
|
||||
$this->m_bSecurityIssue = true;
|
||||
$this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete');
|
||||
}
|
||||
}
|
||||
|
||||
function GetGrantAsHtml($sClass, $iAction)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this))
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this)) {
|
||||
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
|
||||
}
|
||||
}
|
||||
@@ -528,6 +598,19 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
parent::DBDeleteSingleObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \OQLException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function IsCurrentUser(): bool
|
||||
{
|
||||
if (is_null(UserRights::GetUserId())) {
|
||||
return false;
|
||||
}
|
||||
return UserRights::GetUserId() == $this->GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1011,6 +1094,30 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Person $oPerson Person we try to match against Users contact (also Person objects)
|
||||
* @param bool $bMustBeUnique If true, return null when 2+ Users matching this Person were found. Otherwise return the first one
|
||||
*
|
||||
* @return \DBObject|null
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetUserFromPerson(Person $oPerson, bool $bMustBeUnique = true): ?DBObject
|
||||
{
|
||||
$sUserSearch = 'SELECT User WHERE contactid = :id';
|
||||
$oUserSearch = DBObjectSearch::FromOQL($sUserSearch);
|
||||
$oUserSearch->AllowAllData();
|
||||
$oUserSet = new DBObjectSet($oUserSearch, array(), array('id' => $oPerson->GetKey()));
|
||||
if($oUserSet->Count() > 0 && !($oUserSet->Count() > 1 && $bMustBeUnique)){
|
||||
return $oUserSet->Fetch();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -1110,13 +1217,15 @@ class UserRights
|
||||
}
|
||||
}
|
||||
}
|
||||
} // If no contact, check if user has a placeholder in they preferences
|
||||
else {
|
||||
}
|
||||
// If no contact & empty login, check if current user has a placeholder in they preferences
|
||||
elseif ('' === $sLogin) {
|
||||
$sPlaceholderPictureFilename = appUserPreferences::GetPref($sUserPicturePlaceholderPrefKey, null, static::GetUserId($sLogin));
|
||||
if (!empty($sPlaceholderPictureFilename)) {
|
||||
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().$sUserPicturesFolder.$sPlaceholderPictureFilename;
|
||||
}
|
||||
}
|
||||
// Else, no contact and no login, then it's for an unknown origin (system, extension, ...)
|
||||
|
||||
// Update cache
|
||||
static::$m_aCacheContactPictureAbsUrl[$sLogin] = $sPictureUrl;
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
*/
|
||||
|
||||
@import "block-csv";
|
||||
@import "block-list";
|
||||
|
||||
@@ -8,10 +8,4 @@ $ibo-block-csv--textarea--margin-top: 10px !default;
|
||||
min-height: $ibo-block-csv--textarea--min-height;
|
||||
margin-top: $ibo-block-csv--textarea--margin-top;
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-block-csv--download-link{
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-alternative;
|
||||
@extend .ibo-is-primary;
|
||||
}
|
||||
14
css/backoffice/application/display-block/_block-list.scss
Normal file
14
css/backoffice/application/display-block/_block-list.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-block-list--create-icon--margin-right: 0.5rem !default;
|
||||
|
||||
.ibo-block-list--empty-text, .ibo-block-list--create-action{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ibo-block-list--create-icon {
|
||||
margin-right: $ibo-block-list--create-icon--margin-right;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
$ibo-scrollbar--scrollbar-width: 8px !default;
|
||||
$ibo-scrollbar--scrollbar-height: $ibo-scrollbar--scrollbar-width !default; /* For horizontal scrollbars */
|
||||
$ibo-scrollbar--scrollbar-track-background-color: $ibo-color-transparent !default;
|
||||
$ibo-scrollbar--scrollbar-thumb-background-color: $ibo-color-grey-300 !default;
|
||||
$ibo-scrollbar--scrollbar-thumb-border: none !default;
|
||||
@@ -34,6 +35,7 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
|
||||
/* CSS variables */
|
||||
:root{
|
||||
--ibo-scrollbar--scrollbar-width: #{$ibo-scrollbar--scrollbar-width};
|
||||
--ibo-scrollbar--scrollbar-height: #{$ibo-scrollbar--scrollbar-height};
|
||||
--ibo-scrollbar--scrollbar-track-background-color: #{$ibo-scrollbar--scrollbar-track-background-color};
|
||||
--ibo-scrollbar--scrollbar-thumb-background-color: #{$ibo-scrollbar--scrollbar-thumb-background-color};
|
||||
--ibo-scrollbar--scrollbar-thumb-border: #{$ibo-scrollbar--scrollbar-thumb-border};
|
||||
@@ -60,6 +62,7 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
|
||||
/* - For Chrome/Edge/Safari */
|
||||
&::-webkit-scrollbar {
|
||||
width: var(--ibo-scrollbar--scrollbar-width);
|
||||
height: var(--ibo-scrollbar--scrollbar-height);
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: var(--ibo-scrollbar--scrollbar-track-background-color);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
.ibo-blocklist--empty-text, .ibo-blocklist--create-new{
|
||||
text-align: center;
|
||||
}
|
||||
.ibo-blocklist--medallion{
|
||||
.ibo-block-list--medallion{
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> .ibo-medallion-icon--image{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
$ibo-panel-within-main-content--margin-bottom: 200px !default;
|
||||
|
||||
$ibo-panel-within-main-content--sticky-sentinel-top--top: -1 * $ibo-main-content--padding-top !default;
|
||||
$ibo-panel-within-main-content--sticky-sentinel-top--height: $ibo-main-content--padding-top !default;
|
||||
@@ -17,6 +18,8 @@ $ibo-panel-within-main-content--header--top--is-sticky: -1 * $ibo-main-content--
|
||||
* - Unlike in JS, there no easy way to find the closest descendant
|
||||
*/
|
||||
.ibo-panel.ibo-has-sticky-header {
|
||||
margin-bottom: $ibo-panel-within-main-content--margin-bottom; /* Add a margin below the panel so the dropdown lists can open without problem (N°4039) */
|
||||
|
||||
/* Stickable header rules */
|
||||
> .ibo-sticky-sentinel-top {
|
||||
top: $ibo-panel-within-main-content--sticky-sentinel-top--top;
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
@import "button";
|
||||
@import "button-group";
|
||||
@import "breadcrumbs";
|
||||
@import "collapsible-section";
|
||||
@import "quick-create";
|
||||
@import "global-search";
|
||||
@import "popover-menu/popover-menu";
|
||||
@import "popover-menu/popover-menu-item";
|
||||
@import "newsroom-menu";
|
||||
@import "panel";
|
||||
@import "collapsible-section";
|
||||
@import "modal";
|
||||
@import "dashlet/all";
|
||||
@import "input/all";
|
||||
|
||||
@@ -17,21 +17,21 @@
|
||||
*/
|
||||
|
||||
/* SCSS variables */
|
||||
$ibo-collapsible-section--margin-top: 3rem;
|
||||
$ibo-collapsible-section--title--color: $ibo-color-grey-900 !default;
|
||||
$ibo-collapsible-section--body--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-collapsible-section--margin-top: 3rem !default;
|
||||
$ibo-collapsible-section--title--color: $ibo-panel--title--color !default;
|
||||
$ibo-collapsible-section--body--background-color: $ibo-panel--body--background-color !default;
|
||||
$ibo-collapsible-section--highlight--height: 8px !default;
|
||||
|
||||
$ibo-collapsible-section--maximize-minimize-button--right: 5px !default;
|
||||
$ibo-collapsible-section--maximize-minimize-button--color: $ibo-panel--collapsible-toggler--color !default;
|
||||
$ibo-collapsible-section--maximize-minimize-button--right: $ibo-panel--collapsible-toggler--margin-right !default;
|
||||
|
||||
|
||||
$ibo-collapsible-section--body--padding-bottom: 16px !default;
|
||||
$ibo-collapsible-section--body--padding-top: $ibo-collapsible-section--body--padding-bottom + $ibo-collapsible-section--highlight--height !default;
|
||||
$ibo-collapsible-section--body--padding-x: 16px !default;
|
||||
$ibo-collapsible-section--body--border-radius: $ibo-border-radius-500 !default;
|
||||
$ibo-collapsible-section--body--border-radius: $ibo-panel--body--border-radius !default;
|
||||
$ibo-collapsible-section--body--border-size: 1px !default;
|
||||
$ibo-collapsible-section--body--border-color: $ibo-color-grey-400 !default;
|
||||
$ibo-collapsible-section--body--border-color: $ibo-panel--base-border-color !default;
|
||||
|
||||
|
||||
/* Rules */
|
||||
@@ -78,6 +78,7 @@ $ibo-collapsible-section--body--border-color: $ibo-color-grey-400 !default;
|
||||
|
||||
.ibo-collapsible-section--action-button {
|
||||
&.ibo-collapsible-section--maximize-button, &.ibo-collapsible-section--minimize-button {
|
||||
color: $ibo-collapsible-section--maximize-minimize-button--color;
|
||||
margin-right: $ibo-collapsible-section--maximize-minimize-button--right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
|
||||
|
||||
/* Avoid value to overflow from its container with very long strings (typically URLs) */
|
||||
/* Note: Some types of attribute must be excluding as it can alter their rendering */
|
||||
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not(.ibo-input-file-select--container) {
|
||||
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not([data-attribute-type="AttributeCustomFields"]):not(.ibo-input-file-select--container) {
|
||||
/* We need the rule to apply for the class and all its descendants */
|
||||
.ibo-field--value {
|
||||
* {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,3 +205,7 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
|
||||
.multi_values {
|
||||
background-color: #c33;
|
||||
}
|
||||
|
||||
.form_field ~ .form_field {
|
||||
margin-top: $ibo-field--sibling-spacing;
|
||||
}
|
||||
@@ -2,3 +2,7 @@
|
||||
* copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
.ibo-prop-header {
|
||||
@extend %ibo-font-size-150;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ $ibo-global-search--head--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-global-search--icon-padding-x: 16px !default;
|
||||
$ibo-global-search--icon-padding-y: 0 !default;
|
||||
|
||||
$ibo-global-search--input--padding: 0 default;
|
||||
$ibo-global-search--input--padding: 0 !default;
|
||||
$ibo-global-search--input--padding-x--is-opened: 8px !default;
|
||||
$ibo-global-search--input--padding-y--is-opened: 8px !default;
|
||||
$ibo-global-search--input--width: 0 !default;
|
||||
@@ -102,6 +102,7 @@ $ibo-global-search--compartment--placeholder-hint--text-color: $ibo-color-grey-7
|
||||
padding: $ibo-global-search--input--padding;
|
||||
width: $ibo-global-search--input--width;
|
||||
color: $ibo-global-search--input--text-color;
|
||||
background-color: transparent;
|
||||
@extend %ibo-font-ral-nor-300;
|
||||
|
||||
border: none;
|
||||
|
||||
@@ -237,26 +237,28 @@ $ibo-panel-colors: (
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ibo-panel--collapsible-toggler--opened {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ibo-panel--collapsible-toggler--closed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Collapsible rules */
|
||||
.ibo-panel:not(.ibo-is-opened) {
|
||||
.ibo-panel--collapsible-toggler--closed {
|
||||
.ibo-panel {
|
||||
.ibo-panel--collapsible-toggler--opened {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ibo-panel--collapsible-toggler--opened {
|
||||
.ibo-panel--collapsible-toggler--closed {
|
||||
display: none;
|
||||
}
|
||||
&:not(.ibo-is-opened) {
|
||||
.ibo-panel--collapsible-toggler--closed {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ibo-panel--body {
|
||||
display: none;
|
||||
.ibo-panel--collapsible-toggler--opened {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ibo-panel--body {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,25 +4,43 @@ $ibo-prop--cancel--padding-left: 7px !default;
|
||||
$ibo-prop--apply-cancel--span--height: 28px !default;
|
||||
$ibo-prop--apply-cancel--span--width: 32px !default;
|
||||
|
||||
$ibo-prop--apply-cancel--height: $ibo-prop--apply-cancel--span--height !default;
|
||||
$ibo-prop--apply--width: calc(#{$ibo-prop--apply-cancel--span--width} + #{$ibo-prop--apply--padding-left}) !default;
|
||||
$ibo-prop--cancel--width: calc(#{$ibo-prop--apply-cancel--span--width} + #{$ibo-prop--cancel--padding-left}) !default;
|
||||
|
||||
$ibo-prop--apply--error--color: $ibo-color-grey-800 !default;
|
||||
|
||||
.ibo-prop--apply{
|
||||
width: $ibo-prop--apply--width;
|
||||
padding-left: $ibo-prop--apply--padding-left;
|
||||
> span{
|
||||
@extend .ibo-is-green;
|
||||
}
|
||||
&.ui-state-error{
|
||||
&:after {
|
||||
color: $ibo-prop--apply--error--color;
|
||||
content: '\f071';
|
||||
vertical-align: bottom;
|
||||
@extend %fa-solid-base;
|
||||
}
|
||||
> span{
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-prop--cancel{
|
||||
padding-left: $ibo-prop--cancel--padding-left;
|
||||
width: $ibo-prop--cancel--width;
|
||||
padding-left: $ibo-prop--cancel--padding-left;
|
||||
> span{
|
||||
@extend .ibo-is-red;
|
||||
}
|
||||
}
|
||||
.ibo-prop--apply, .ibo-prop--cancel{
|
||||
> span{
|
||||
height: $ibo-prop--apply-cancel--height;
|
||||
> span{
|
||||
display: block;
|
||||
height: $ibo-prop--apply-cancel--span--height;
|
||||
width: $ibo-prop--apply-cancel--span--width;
|
||||
text-align: center;
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-alternative;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,19 @@ $ibo-search-form-panel--body--padding-top: 18px !default;
|
||||
$ibo-search-form-panel--body--padding-bottom: 10px !default;
|
||||
$ibo-search-form-panel--body--padding-x: 14px !default;
|
||||
|
||||
$ibo-search-form-panel--criteria--color: $ibo-color-grey-900 !default;
|
||||
$ibo-search-form-panel--criteria--background-color: $ibo-color-white-200 !default;
|
||||
$ibo-search-form-panel--criteria--border-color: $ibo-color-grey-300 !default;
|
||||
$ibo-search-form-panel--criteria--locked--background-color: $ibo-color-grey-300 !default;
|
||||
|
||||
$ibo-search-form-panel--more-criteria--color: $ibo-color-blue-grey-800 !default;
|
||||
$ibo-search-form-panel--more-criteria--background-color: $ibo-color-white-100 !default;
|
||||
$ibo-search-form-panel--more-criteria--icon--color: $ibo-color-primary-600 !default;
|
||||
$ibo-search-form-panel--more-criteria--border-color: $ibo-search-form-panel--criteria--border-color !default;
|
||||
|
||||
$ibo-search-form-panel--misc-button--background-color: $ibo-search-form-panel--more-criteria--background-color !default;
|
||||
$ibo-search-form-panel--misc-button--icon--color: $ibo-search-form-panel--more-criteria--icon--color !default;
|
||||
|
||||
$ibo-search-results-area--z-index: $ibo-search-form-panel--z-index - 2 !default; /* Minus 2 because the criteria expands between the search form panel and the results area */
|
||||
|
||||
$ibo-search-results-area--datatable-toolbar--background-color--is-sticking: $ibo-panel--body--background-color !default;
|
||||
@@ -140,7 +153,6 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
.sf_criterion_area {
|
||||
/*display: none;*/
|
||||
padding: 8px 8px 3px 8px; /* padding-bottom must equals to padding-top - .search_form_criteria:margin-bottom */
|
||||
background-color: $ibo-color-white-100;
|
||||
|
||||
.sf_criterion_row {
|
||||
|
||||
@@ -172,7 +184,7 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
display: inline;
|
||||
.sfc_fg_button,
|
||||
.sfc_header {
|
||||
border: 1px solid #E1E7EC; /* Must be equal to .search_form_criteria:margin-bottom + this:padding-bottom */
|
||||
border: 1px solid $ibo-search-form-panel--criteria--border-color; /* Must be equal to .search_form_criteria:margin-bottom + this:padding-bottom */
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
@@ -201,7 +213,7 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
> * {
|
||||
padding: 7px 8px;
|
||||
vertical-align: top;
|
||||
border: solid 1px $ibo-color-grey-300;
|
||||
border: solid 1px $ibo-search-form-panel--more-criteria--border-color;
|
||||
border-radius: $ibo-border-radius-300;
|
||||
}
|
||||
|
||||
@@ -229,7 +241,7 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
.search_form_criteria {
|
||||
/* Non editable criteria */
|
||||
&.locked {
|
||||
background-color: $ibo-color-grey-200;
|
||||
background-color: $ibo-search-form-panel--criteria--locked--background-color;
|
||||
|
||||
.sfc_title {
|
||||
user-select: none;
|
||||
@@ -278,8 +290,8 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
}
|
||||
|
||||
> * {
|
||||
background-color: $ibo-color-white-200;
|
||||
color: $ibo-color-grey-900;
|
||||
background-color: $ibo-search-form-panel--criteria--background-color;
|
||||
color: $ibo-search-form-panel--criteria--color;
|
||||
}
|
||||
|
||||
/* Top left corner icons */
|
||||
@@ -646,8 +658,8 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
}
|
||||
|
||||
> * {
|
||||
background-color: $ibo-color-white-100;
|
||||
color: $ibo-color-blue-grey-800;
|
||||
background-color: $ibo-search-form-panel--more-criteria--background-color;
|
||||
color: $ibo-search-form-panel--more-criteria--color;
|
||||
}
|
||||
|
||||
.sfm_toggler {
|
||||
@@ -702,8 +714,8 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
cursor: pointer;
|
||||
|
||||
> * {
|
||||
background-color: $ibo-color-white-100;
|
||||
color: $ibo-color-primary-600;
|
||||
background-color: $ibo-search-form-panel--misc-button--background-color;
|
||||
color: $ibo-search-form-panel--misc-button--icon--color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -835,9 +847,6 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
|
||||
button {
|
||||
margin-top: 8px;
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-primary;
|
||||
@extend .ibo-is-regular;
|
||||
> span {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
@@ -864,16 +873,6 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
|
||||
padding-top: $ibo-panel--spacing-top ;
|
||||
}
|
||||
|
||||
.sfc_fg_button{
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-neutral;
|
||||
&.sfc_fg_search, &.sfc_fg_apply, &.sfc_fg_cancel {
|
||||
@extend .ibo-is-regular;
|
||||
}
|
||||
&.sfc_fg_more {
|
||||
@extend .ibo-is-alternative;
|
||||
}
|
||||
}
|
||||
.sfm_tg_title{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ $ibo-dashlet--elements-spacing-y: 24px !default;
|
||||
|
||||
/* Rules */
|
||||
.ibo-dashlet {
|
||||
position: relative;
|
||||
width: calc(#{$ibo-dashlet--width} - #{$ibo-dashlet--elements-spacing-x});
|
||||
margin: calc(#{$ibo-dashlet--elements-spacing-y} / 2) calc(#{$ibo-dashlet--elements-spacing-x} / 2);
|
||||
|
||||
@@ -27,4 +28,13 @@ $ibo-dashlet--elements-spacing-y: 24px !default;
|
||||
}
|
||||
.ibo-details{
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ibo-dashlet-blocker{
|
||||
position: absolute;
|
||||
z-index: 9; /* To be above calendar links & all, but below .close-box (9) */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -25,9 +25,7 @@ $ibo-input-datetime--ui-tpicker-slider--padding-right: 18px !default;
|
||||
color: $ibo-input-datetime--action-button--color;
|
||||
}
|
||||
.ui-datepicker-current, .ui-datepicker-close{
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-regular;
|
||||
@extend .ibo-is-secondary;
|
||||
@extend .ibo-button, .ibo-is-regular, .ibo-is-secondary;
|
||||
}
|
||||
.ui_tpicker_hour_slider, .ui_tpicker_minute_slider, .ui_tpicker_second_slider{
|
||||
@extend .ibo-input-wrapper;
|
||||
|
||||
@@ -35,10 +35,13 @@ $ibo-input-select--action-button--padding-left: 6px !default;
|
||||
$ibo-input-select--action-button--padding-right: 2px !default;
|
||||
|
||||
.ibo-input-select {
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
min-width: $ibo-input-select--value--min-midth;
|
||||
|
||||
&:not(.ibo-input-select-autocomplete):not(.ibo-input-selectize) {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
&.ibo-input-selectize {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
@@ -56,6 +59,10 @@ $ibo-input-select--action-button--padding-right: 2px !default;
|
||||
padding-left: $ibo-input--padding-x;
|
||||
}
|
||||
}
|
||||
|
||||
&[size]{
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.ibo-input-select-autocomplete{
|
||||
min-width: $ibo-input-select-autocomplete--value--min-midth !important;
|
||||
|
||||
@@ -98,7 +98,7 @@ $ibo-navigation-menu--menu-group-title--text-color--is-active: $ibo-color-blue-g
|
||||
$ibo-navigation-menu--drawer--width: 312px !default;
|
||||
$ibo-navigation-menu--drawer--padding-x: 20px !default;
|
||||
$ibo-navigation-menu--drawer--padding-y: 32px !default;
|
||||
$ibo-navigation-menu--drawer--background-color: $ibo-color-grey-100 !default;
|
||||
$ibo-navigation-menu--drawer--background-color: $ibo-navigation-menu--menu-group--background-color--is-active !default;
|
||||
$ibo-navigation-menu--drawer--border-right: 1px solid $ibo-color-grey-300 !default;
|
||||
|
||||
/* TODO: Refactor this into the standard field input */
|
||||
|
||||
@@ -56,12 +56,17 @@ $ibo-activity-entry--sub-information--margin-top: 4px !default;
|
||||
$ibo-activity-entry--sub-information--margin-bottom: $ibo-activity-entry--sub-information--margin-top !default;
|
||||
$ibo-activity-entry--sub-information--text-color: $ibo-color-grey-700 !default;
|
||||
|
||||
$ibo-activity-entry--author-name--sibling-spacing: 0.2rem !default;
|
||||
$ibo-activity-entry--sub-information--sibling-spacing: 0.5rem !default;
|
||||
$ibo-activity-entry--sub-information-sibling-separator--size: 4px !default;
|
||||
$ibo-activity-entry--sub-information-sibling-separator--border-radius: 100% !default;
|
||||
$ibo-activity-entry--sub-information-sibling-separator--background-color: $ibo-color-grey-600 !default;
|
||||
|
||||
$ibo-activity-panel--load-more-entries--size: 32px !default;
|
||||
$ibo-activity-panel--load-more-entries--border-radius: $ibo-border-radius-full !default;
|
||||
$ibo-activity-panel--load-more-entries--background-color: $ibo-activity-entry--main-information--background-color !default;
|
||||
$ibo-activity-panel--load-more-entries--border: $ibo-content-block--border !default;
|
||||
$ibo-activity-panel--load-entries-button--size: 32px !default;
|
||||
$ibo-activity-panel--load-entries-button--border-radius: $ibo-border-radius-full !default;
|
||||
$ibo-activity-panel--load-entries-button--background-color: $ibo-activity-entry--main-information--background-color !default;
|
||||
$ibo-activity-panel--load-entries-button--border: $ibo-content-block--border !default;
|
||||
|
||||
$ibo-activity-panel--load-all-entries--is-hover--margin-left: ($ibo-activity-panel--load-entries-button--size + 10px) * 2 !default; /* 2x is necessary here as the elements are centered */
|
||||
|
||||
/* Entry group */
|
||||
.ibo-activity-panel--entry-group{
|
||||
@@ -198,7 +203,7 @@ $ibo-activity-panel--load-more-entries--border: $ibo-content-block--border !defa
|
||||
|
||||
/* Avoid pre (code snippets) to overflow outside the entry */
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* Avoid table to overflow outside the entry (see N°2127) */
|
||||
@@ -228,26 +233,52 @@ $ibo-activity-panel--load-more-entries--border: $ibo-content-block--border !defa
|
||||
color: $ibo-activity-entry--sub-information--text-color;
|
||||
|
||||
@extend %ibo-font-size-50;
|
||||
}
|
||||
|
||||
.ibo-activity-entry--author-name {
|
||||
&:after {
|
||||
content: "-";
|
||||
margin-left: $ibo-activity-entry--author-name--sibling-spacing;
|
||||
margin-right: $ibo-activity-entry--author-name--sibling-spacing;
|
||||
> *:not(:last-child):after {
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: $ibo-activity-entry--sub-information--sibling-spacing;
|
||||
margin-right: $ibo-activity-entry--sub-information--sibling-spacing;
|
||||
width: $ibo-activity-entry--sub-information-sibling-separator--size;
|
||||
height: $ibo-activity-entry--sub-information-sibling-separator--size;
|
||||
border-radius: $ibo-activity-entry--sub-information-sibling-separator--border-radius;
|
||||
background-color: $ibo-activity-entry--sub-information-sibling-separator--background-color;
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-activity-panel--load-more-entries-container {
|
||||
position: relative;
|
||||
@extend %ibo-fully-centered-content;
|
||||
|
||||
&:hover {
|
||||
.ibo-activity-panel--load-all-entries {
|
||||
margin-left: $ibo-activity-panel--load-all-entries--is-hover--margin-left;
|
||||
}
|
||||
}
|
||||
&:not(:hover) {
|
||||
.ibo-activity-panel--load-all-entries {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-activity-panel--load-more-entries {
|
||||
width: $ibo-activity-panel--load-more-entries--size;
|
||||
height: $ibo-activity-panel--load-more-entries--size;
|
||||
border-radius: $ibo-activity-panel--load-more-entries--border-radius;
|
||||
background-color: $ibo-activity-panel--load-more-entries--background-color;
|
||||
border: $ibo-activity-panel--load-more-entries--border;
|
||||
.ibo-activity-panel--load-entries-button {
|
||||
width: $ibo-activity-panel--load-entries-button--size;
|
||||
height: $ibo-activity-panel--load-entries-button--size;
|
||||
border-radius: $ibo-activity-panel--load-entries-button--border-radius;
|
||||
background-color: $ibo-activity-panel--load-entries-button--background-color;
|
||||
border: $ibo-activity-panel--load-entries-button--border;
|
||||
@extend %ibo-fully-centered-content;
|
||||
@extend %ibo-hyperlink-inherited-colors;
|
||||
}
|
||||
.ibo-activity-panel--load-more-entries {
|
||||
z-index: 1;
|
||||
}
|
||||
.ibo-activity-panel--load-all-entries {
|
||||
position: absolute;
|
||||
z-index: 0; /* Must be below the other button as it will reveal later */
|
||||
top: 0;
|
||||
margin-left: 0;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
@@ -34,7 +34,9 @@ $ibo-activity-panel--togglers--elements-spacing: 0.75rem !default;
|
||||
|
||||
/* - Tabs togglers*/
|
||||
$ibo-activity-panel--tabs-togglers--padding-x: $ibo-activity-panel--padding-x * 3 !default; /* We need to increase this so the size toggler which will be set in abs. pos. can overlap it nicely */
|
||||
$ibo-activity-panel--tab-toolbar-action--color: $ibo-color-grey-900 !default;
|
||||
|
||||
$ibo-activity-panel--tab-toolbar-info--color: $ibo-activity-panel--tab-toolbar-action--color !default;
|
||||
/* - Tab toggler */
|
||||
$ibo-activity-panel--tab-toggler--caselog-highlight-colors: $ibo-caselog-highlight-colors !default;
|
||||
$ibo-activity-panel--tab-toggler--is-active--background-color: $ibo-color-grey-200 !default;
|
||||
@@ -278,7 +280,8 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
|
||||
}
|
||||
.ibo-activity-panel--tab-toolbar-right-actions {
|
||||
.ibo-activity-panel--tab-toolbar-info {
|
||||
> .ibo-activity-panel--tab-toolbar-info-icon {
|
||||
color: $ibo-activity-panel--tab-toolbar-info--color;
|
||||
> .ibo-activity-panel--tab-toolbar-info-icon {
|
||||
margin-left: $ibo-activity-panel--tab-toolbar-info-icon--margin-left;
|
||||
}
|
||||
|
||||
@@ -289,6 +292,7 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
|
||||
}
|
||||
.ibo-activity-panel--tab-toolbar-action{
|
||||
position: relative;
|
||||
color: $ibo-activity-panel--tab-toolbar-action--color;
|
||||
@extend %ibo-fully-centered-content;
|
||||
}
|
||||
.ibo-activity-panel--filter{
|
||||
|
||||
@@ -44,15 +44,17 @@ $ibo-dashboard-editor--delete-dashlet-icon--z-index: 21 !default;
|
||||
flex-direction: column;
|
||||
padding-bottom: 20px;
|
||||
table{
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
td{
|
||||
margin-bottom: 14px;
|
||||
.ibo-field{
|
||||
@extend %ibo-font-size-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
td, th {
|
||||
margin-bottom: 14px;
|
||||
|
||||
.ibo-field {
|
||||
@extend %ibo-font-size-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-dashboard-editor--properties-title{
|
||||
padding-bottom: $ibo-dashboard-editor--properties-title--padding-bottom;
|
||||
@@ -98,7 +100,9 @@ $ibo-dashboard-editor--delete-dashlet-icon--z-index: 21 !default;
|
||||
right: $ibo-dashboard-editor--delete-dashlet-icon--right;
|
||||
padding: $ibo-dashboard-editor--delete-dashlet-icon--padding;
|
||||
z-index: $ibo-dashboard-editor--delete-dashlet-icon--z-index;
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-alternative;
|
||||
@extend .ibo-is-danger;
|
||||
}
|
||||
.ibo-dashboard-editor .itop-dashboard{
|
||||
a{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,10 @@ $ibo-display-graph--search-box--criterion--content--checkbox--margin-right: 10px
|
||||
padding-right: 0;
|
||||
}
|
||||
.ibo-display-graph--search-box {
|
||||
|
||||
.sf_criterion_area{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sf_criterion_row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -103,6 +106,6 @@ $ibo-display-graph--search-box--criterion--content--checkbox--margin-right: 10px
|
||||
}
|
||||
|
||||
#ReloadMovieBtn {
|
||||
float: right;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +142,19 @@ body.ibo-has-fullscreen-descendant {
|
||||
@extend %ibo-font-code-150;
|
||||
}
|
||||
|
||||
/*
|
||||
* A single class to handle WYSIWYG generated content, where only HTML tags are available
|
||||
* See https://bulma.io/documentation/elements/content/
|
||||
*/
|
||||
.ibo-is-html-content {
|
||||
@extend .content;
|
||||
|
||||
code {
|
||||
color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
/* Sticky headers */
|
||||
/* */
|
||||
|
||||
1
css/backoffice/vendors/_all.scss
vendored
1
css/backoffice/vendors/_all.scss
vendored
@@ -19,6 +19,7 @@
|
||||
@import "bulma-variables-overload";
|
||||
@import "../../../node_modules/bulma-scss/bulma";
|
||||
@import "ckeditor";
|
||||
@import "tippy";
|
||||
@import "jqueryui";
|
||||
@import "jquery-multiselect";
|
||||
@import "datatables";
|
||||
|
||||
@@ -23,4 +23,15 @@ $body-font-size: $ibo-font-size-100 !default;
|
||||
$body-weight: $ibo-font-weight-500 !default;
|
||||
|
||||
$body-overflow-x: hidden !default;
|
||||
$body-overflow-y: auto !default;
|
||||
$body-overflow-y: auto !default;
|
||||
|
||||
|
||||
/**
|
||||
* customize Bulma content variables
|
||||
* See https://bulma.io/documentation/elements/content/
|
||||
*/
|
||||
$content-table-cell-border: 1px solid black;
|
||||
$content-table-cell-border-width: 1px;
|
||||
$content-table-head-cell-border-width: 1px;
|
||||
$content-table-foot-cell-border-width: 1px;
|
||||
$content-table-foot-cell-color: black;
|
||||
|
||||
9
css/backoffice/vendors/_ckeditor.scss
vendored
9
css/backoffice/vendors/_ckeditor.scss
vendored
@@ -17,7 +17,7 @@ $ibo-vendors-ckeditor--autocomplete-panel--background-color: $ibo-color-white-10
|
||||
|
||||
$ibo-vendors-ckeditor--autocomplete-item-image--size: 25px !default;
|
||||
$ibo-vendors-ckeditor--autocomplete-item-image--margin-right: 0.5rem !default;
|
||||
$ibo-vendors-ckeditor--autocomplete-item-image--background-color: $ibo-color-grey-200 !default;
|
||||
$ibo-vendors-ckeditor--autocomplete-item-image--background-color: $ibo-color-blue-100 !default;
|
||||
$ibo-vendors-ckeditor--autocomplete-item-image--border: 1px solid $ibo-color-grey-600 !default;
|
||||
|
||||
$ibo-vendors-ckeditor--autocomplete-item-title--text-color: #3A3A3A !default;
|
||||
@@ -37,6 +37,7 @@ $ibo-vendors-ckeditor--autocomplete-item-title--text-color: #3A3A3A !default;
|
||||
background-position: center center !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 100% !important;
|
||||
background-image: url('../../../../images/full-screen.png') !important;
|
||||
|
||||
&:hover {
|
||||
background-color: $ibo-vendors-ckeditor--toolbar-fullscreen-button--background-color--on-hover;
|
||||
@@ -48,7 +49,7 @@ $ibo-vendors-ckeditor--autocomplete-item-title--text-color: #3A3A3A !default;
|
||||
padding: $ibo-vendors-highlightjs--padding !important;
|
||||
box-shadow: 0 0px 3px 2px inset rgba(0, 0, 0, 0.4);
|
||||
border-radius: $ibo-vendors-highlightjs--border-radius;
|
||||
white-space: pre-wrap;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* Mentions in caselogs */
|
||||
@@ -67,6 +68,9 @@ ul.cke_autocomplete_panel{
|
||||
.ibo-vendors-ckeditor--autocomplete-item-image{
|
||||
width: $ibo-vendors-ckeditor--autocomplete-item-image--size;
|
||||
height: $ibo-vendors-ckeditor--autocomplete-item-image--size;
|
||||
/* min-xxx are here to avoid medallion to be horizontally compressed when the title is to long */
|
||||
min-width: $ibo-vendors-ckeditor--autocomplete-item-image--size;
|
||||
min-height: $ibo-vendors-ckeditor--autocomplete-item-image--size;
|
||||
background-position: center center;
|
||||
background-size: 100%;
|
||||
border-radius: 100%;
|
||||
@@ -77,6 +81,7 @@ ul.cke_autocomplete_panel{
|
||||
@extend %ibo-fully-centered-content;
|
||||
}
|
||||
.ibo-vendors-ckeditor--autocomplete-item-title{
|
||||
white-space: nowrap; /* Here we don't want to truncate the text as in an autocomplete we might have similar values and we need the user to see the entire text to be able to differenciate them */
|
||||
color: $ibo-vendors-ckeditor--autocomplete-item-title--text-color;
|
||||
@extend %ibo-font-weight-700;
|
||||
}
|
||||
|
||||
9
css/backoffice/vendors/_jqueryui.scss
vendored
9
css/backoffice/vendors/_jqueryui.scss
vendored
@@ -167,9 +167,7 @@ $ibo-vendors-jqueryui--ui-slider--ui-slider-handle--hover--border-color: $ibo-co
|
||||
top: 0;
|
||||
}
|
||||
.ui-button {
|
||||
@extend .ibo-button;
|
||||
@extend .ibo-is-regular;
|
||||
@extend .ibo-is-secondary;
|
||||
@extend .ibo-button, .ibo-is-regular, .ibo-is-secondary;
|
||||
> .ui-icon {
|
||||
background-image: none;
|
||||
float: unset;
|
||||
@@ -301,7 +299,7 @@ $ibo-vendors-jqueryui--ui-slider--ui-slider-handle--hover--border-color: $ibo-co
|
||||
background-color: $ibo-vendors-jqueryui--ui-datepicker--background-color;
|
||||
border-radius: $ibo-vendors-jqueryui--ui-datepicker--border-radius;
|
||||
box-shadow: $ibo-vendors-jqueryui--ui-datepicker--box-shadow;
|
||||
z-index: 21 !important;
|
||||
z-index: 32 !important;
|
||||
padding: 0px 8px 5px 8px;
|
||||
|
||||
.ui-datepicker-header {
|
||||
@@ -569,6 +567,9 @@ $ibo-vendors-jqueryui--ui-slider--ui-slider-handle--hover--border-color: $ibo-co
|
||||
left: 0;
|
||||
cursor: default;
|
||||
z-index: 100;
|
||||
.ui-menu-item{
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.ui-autocomplete-input{
|
||||
@extend .ibo-input;
|
||||
|
||||
9
css/backoffice/vendors/_tippy.scss
vendored
Normal file
9
css/backoffice/vendors/_tippy.scss
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
// Allow plain text (opposite of HTML) multi-lines tooltips to be displayed correctly, otherwise it will all be on a single line.
|
||||
.tippy-content {
|
||||
white-space: pre-line;
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
@CHARSET "UTF-8";
|
||||
|
||||
*, ::before, ::after {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
html,body {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -107,10 +111,9 @@ a:hover {
|
||||
}
|
||||
|
||||
#login-form-content .divider hr {
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
margin: 0.5rem auto;
|
||||
width:40%;
|
||||
color: #E1E7EC;
|
||||
background-color: #E1E7EC;
|
||||
}
|
||||
|
||||
#login-form-content .divider hr.left {
|
||||
|
||||
756
css/setup.css
756
css/setup.css
File diff suppressed because one or more lines are too long
@@ -36,6 +36,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'abgelaufen',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'einmaliges Passwort',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
|
||||
|
||||
|
||||
@@ -49,10 +49,13 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'Expired',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'One-time Password',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension'
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
|
||||
));
|
||||
|
||||
@@ -27,16 +27,19 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Validité du mot de passe',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Statut du mot de passe (nécessite une extension pour avoir un effet)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'à durée limitée',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Durée limitée',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire' => 'à validité permanente',
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire' => 'Permanente',
|
||||
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'à changer',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'A changer à la prochaine connexion',
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'Usage unique',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Mot de passe changé le',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Dernière date à laquelle le mot de passe a été changé',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 8 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
|
||||
|
||||
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension'
|
||||
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impossible de mettre "Usage unique" comme validité du mot de passe pour son propre utilisateur.',
|
||||
));
|
||||
|
||||
@@ -68,7 +68,8 @@ class UserLocal extends UserInternal
|
||||
const EXPIRE_CAN = 'can_expire';
|
||||
const EXPIRE_NEVER = 'never_expire';
|
||||
const EXPIRE_FORCE = 'force_expire';
|
||||
|
||||
const EXPIRE_ONE_TIME_PWD = 'otp_expire';
|
||||
|
||||
/** @var UserLocalPasswordValidity|null */
|
||||
protected $m_oPasswordValidity = null;
|
||||
|
||||
@@ -90,8 +91,8 @@ class UserLocal extends UserInternal
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeOneWayPassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
$sExpireEnum = implode(',', array(self::EXPIRE_CAN, self::EXPIRE_NEVER, self::EXPIRE_FORCE));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("expiration", array("allowed_values"=>new ValueSetEnum($sExpireEnum), "sql"=>"expiration", "default_value"=>'never_expire', "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
$sExpireEnum = implode(',', array(self::EXPIRE_CAN, self::EXPIRE_NEVER, self::EXPIRE_FORCE, self::EXPIRE_ONE_TIME_PWD));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("expiration", array("allowed_values"=>new ValueSetEnum($sExpireEnum), "sql"=>"expiration", "default_value"=>self::EXPIRE_NEVER, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeDate("password_renewed_date", array("allowed_values"=>null, "sql"=>"password_renewed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
@@ -136,6 +137,10 @@ class UserLocal extends UserInternal
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if($this->Get('expiration') == self::EXPIRE_ONE_TIME_PWD)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -184,6 +189,8 @@ class UserLocal extends UserInternal
|
||||
protected function OnInsert()
|
||||
{
|
||||
parent::OnInsert();
|
||||
$sToday = date(\AttributeDate::GetInternalFormat());
|
||||
$this->Set('password_renewed_date', $sToday);
|
||||
|
||||
$this->OnWrite();
|
||||
}
|
||||
@@ -202,6 +209,15 @@ class UserLocal extends UserInternal
|
||||
|
||||
$sNow = date(\AttributeDate::GetInternalFormat());
|
||||
$this->Set('password_renewed_date', $sNow);
|
||||
|
||||
// Reset the "force" expiration flag when the user updates her/his own password!
|
||||
if ($this->IsCurrentUser())
|
||||
{
|
||||
if (($this->Get('expiration') == self::EXPIRE_FORCE))
|
||||
{
|
||||
$this->Set('expiration', self::EXPIRE_CAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function IsPasswordValid()
|
||||
@@ -278,7 +294,13 @@ class UserLocal extends UserInternal
|
||||
{
|
||||
$this->m_aCheckIssues[] = $this->m_oPasswordValidity->getPasswordValidityMessage();
|
||||
}
|
||||
|
||||
|
||||
// A User cannot force a one-time password on herself/himself
|
||||
if ($this->IsCurrentUser()) {
|
||||
if (array_key_exists('expiration', $this->ListChanges()) && ($this->Get('expiration') == self::EXPIRE_ONE_TIME_PWD)) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed');
|
||||
}
|
||||
}
|
||||
parent::DoCheckToWrite();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -24,6 +24,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'DBTools:Class' => 'Klasse',
|
||||
'DBTools:Title' => 'Datenbankpflege-Tools',
|
||||
'DBTools:ErrorsFound' => 'Fehler gefunden',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Fehler',
|
||||
'DBTools:Count' => 'Anzahl',
|
||||
'DBTools:SQLquery' => 'SQL Query',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
|
||||
'DBTools:Class' => 'Clase',
|
||||
'DBTools:Title' => 'Herramientas de mantenimiento de base de datos~~',
|
||||
'DBTools:ErrorsFound' => 'Errores encontrados',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error',
|
||||
'DBTools:Count' => 'Cantidad',
|
||||
'DBTools:SQLquery' => 'Consulta SQL',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -29,6 +29,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'DBTools:Class' => 'Klasse',
|
||||
'DBTools:Title' => 'Onderhoudstools voor de database~~',
|
||||
'DBTools:ErrorsFound' => 'Fouten gevonden',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Fout',
|
||||
'DBTools:Count' => 'Aantal',
|
||||
'DBTools:SQLquery' => 'SQL-query',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'DBTools:Class' => 'Classe',
|
||||
'DBTools:Title' => 'Manutenção da Base de Dados',
|
||||
'DBTools:ErrorsFound' => 'Erros Encontrados',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Erros',
|
||||
'DBTools:Count' => 'Quantidade',
|
||||
'DBTools:SQLquery' => 'Query SQL',
|
||||
|
||||
@@ -14,6 +14,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'DBTools:Class' => 'Класс',
|
||||
'DBTools:Title' => 'Инструменты обслуживания базы данных~~',
|
||||
'DBTools:ErrorsFound' => 'Найденные ошибки',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Ошибка',
|
||||
'DBTools:Count' => 'Количество',
|
||||
'DBTools:SQLquery' => 'SQL-запрос',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => 'Database Maintenance Tools~~',
|
||||
'DBTools:ErrorsFound' => 'Errors Found~~',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => 'Error~~',
|
||||
'DBTools:Count' => 'Count~~',
|
||||
'DBTools:SQLquery' => 'SQL query~~',
|
||||
|
||||
@@ -27,6 +27,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'DBTools:Class' => 'Class~~',
|
||||
'DBTools:Title' => '数据库维护工具',
|
||||
'DBTools:ErrorsFound' => '发现错误',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
|
||||
'DBTools:Error' => '错误',
|
||||
'DBTools:Count' => '个数',
|
||||
'DBTools:SQLquery' => 'SQL 查询',
|
||||
|
||||
@@ -32,8 +32,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Attachments:PreviewNotAvailable' => '该附件类型不支持预览.',
|
||||
'Attachments:Error:FileTooLarge' => '上传的文件过大. %1$s',
|
||||
'Attachments:Error:UploadedFileEmpty' => '收到的文件为空,无法添加. 可能是因为您发送的是空文件,或者咨询 iTop 管理员服务器磁盘是否已满. ',
|
||||
|
||||
|
||||
'Attachments:Render:Icons' => '显示为图标',
|
||||
'Attachments:Render:Table' => '显示为列表',
|
||||
'UI:Attachments:DropYourFileHint' => '将文件拖放到此区域的任意位置',
|
||||
|
||||
@@ -170,10 +170,6 @@ JS
|
||||
require_once(dirname(__FILE__).'/dbrestore.class.inc.php');
|
||||
|
||||
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
|
||||
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
|
||||
IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'");
|
||||
$oRestoreMutex->Lock();
|
||||
IssueLog::Info('Backup Restore - LOCK acquired, executing...');
|
||||
try
|
||||
{
|
||||
set_time_limit(0);
|
||||
@@ -203,7 +199,6 @@ JS
|
||||
finally
|
||||
{
|
||||
unlink($tokenRealPath);
|
||||
$oRestoreMutex->Unlock();
|
||||
}
|
||||
|
||||
$oPage->output();
|
||||
|
||||
@@ -30,9 +30,6 @@ if (!defined('APPROOT'))
|
||||
}
|
||||
}
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
require_once(APPROOT.'application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'application/csvpage.class.inc.php');
|
||||
require_once(APPROOT.'application/clipage.class.inc.php');
|
||||
require_once(APPROOT.'application/ajaxwebpage.class.inc.php');
|
||||
|
||||
require_once(APPROOT.'core/log.class.inc.php');
|
||||
|
||||
@@ -28,8 +28,8 @@ class DBRestore extends DBBackup
|
||||
{
|
||||
parent::__construct($oConfig);
|
||||
|
||||
$this->sDBUser = $oConfig->Get('db_user');
|
||||
$this->sDBPwd = $oConfig->Get('db_pwd');
|
||||
$this->sDBUser = $this->oConfig->Get('db_user');
|
||||
$this->sDBPwd = $this->oConfig->Get('db_pwd');
|
||||
}
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
@@ -127,79 +127,88 @@ class DBRestore extends DBBackup
|
||||
*/
|
||||
public function RestoreFromCompressedBackup($sFile, $sEnvironment = 'production')
|
||||
{
|
||||
$this->LogInfo("Starting restore of ".basename($sFile));
|
||||
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
|
||||
IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'");
|
||||
$oRestoreMutex->Lock();
|
||||
|
||||
$sNormalizedFile = strtolower(basename($sFile));
|
||||
if (substr($sNormalizedFile, -4) == '.zip')
|
||||
{
|
||||
$this->LogInfo('zip file detected');
|
||||
$oArchive = new ZipArchiveEx();
|
||||
$oArchive->open($sFile);
|
||||
}
|
||||
elseif (substr($sNormalizedFile, -7) == '.tar.gz')
|
||||
{
|
||||
$this->LogInfo('tar.gz file detected');
|
||||
$oArchive = new TarGzArchive($sFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BackupException('Unsupported format for a backup file: '.$sFile);
|
||||
}
|
||||
try {
|
||||
IssueLog::Info('Backup Restore - LOCK acquired, executing...');
|
||||
$bReadonlyBefore = SetupUtils::EnterReadOnlyMode(MetaModel::GetConfig());
|
||||
|
||||
// Load the database
|
||||
//
|
||||
$sDataDir = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
|
||||
try {
|
||||
//safe zone for db backup => cron is stopped/ itop in readonly
|
||||
$this->LogInfo("Starting restore of ".basename($sFile));
|
||||
|
||||
SetupUtils::builddir($sDataDir); // Here is the directory
|
||||
$oArchive->extractTo($sDataDir);
|
||||
|
||||
$sDataFile = $sDataDir.'/itop-dump.sql';
|
||||
$this->LoadDatabase($sDataFile);
|
||||
$sNormalizedFile = strtolower(basename($sFile));
|
||||
if (substr($sNormalizedFile, -4) == '.zip') {
|
||||
$this->LogInfo('zip file detected');
|
||||
$oArchive = new ZipArchiveEx();
|
||||
$oArchive->open($sFile);
|
||||
} elseif (substr($sNormalizedFile, -7) == '.tar.gz') {
|
||||
$this->LogInfo('tar.gz file detected');
|
||||
$oArchive = new TarGzArchive($sFile);
|
||||
} else {
|
||||
throw new BackupException('Unsupported format for a backup file: '.$sFile);
|
||||
}
|
||||
|
||||
// Update the code
|
||||
//
|
||||
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
|
||||
// Load the database
|
||||
//
|
||||
$sDataDir = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
|
||||
|
||||
if (is_file($sDataDir.'/delta.xml'))
|
||||
{
|
||||
// Extract and rename delta.xml => <env>.delta.xml;
|
||||
rename($sDataDir.'/delta.xml', $sDeltaFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
@unlink($sDeltaFile);
|
||||
}
|
||||
if (is_dir(APPROOT.'data/production-modules/'))
|
||||
{
|
||||
try
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT.'data/production-modules/');
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new BackupException("Can't remove production-modules dir", 0, $e);
|
||||
SetupUtils::builddir($sDataDir); // Here is the directory
|
||||
$oArchive->extractTo($sDataDir);
|
||||
|
||||
$sDataFile = $sDataDir.'/itop-dump.sql';
|
||||
$this->LoadDatabase($sDataFile);
|
||||
|
||||
// Update the code
|
||||
//
|
||||
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
|
||||
|
||||
if (is_file($sDataDir.'/delta.xml')) {
|
||||
// Extract and rename delta.xml => <env>.delta.xml;
|
||||
rename($sDataDir.'/delta.xml', $sDeltaFile);
|
||||
} else {
|
||||
@unlink($sDeltaFile);
|
||||
}
|
||||
if (is_dir(APPROOT.'data/production-modules/')) {
|
||||
try {
|
||||
SetupUtils::rrmdir(APPROOT.'data/production-modules/');
|
||||
} catch (Exception $e) {
|
||||
throw new BackupException("Can't remove production-modules dir", 0, $e);
|
||||
}
|
||||
}
|
||||
if (is_dir($sDataDir.'/production-modules')) {
|
||||
rename($sDataDir.'/production-modules', APPROOT.'data/production-modules/');
|
||||
}
|
||||
|
||||
$sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php';
|
||||
@chmod($sConfigFile, 0770); // Allow overwriting the file
|
||||
rename($sDataDir.'/config-itop.php', $sConfigFile);
|
||||
@chmod($sConfigFile, 0440); // Read-only
|
||||
|
||||
try {
|
||||
SetupUtils::rrmdir($sDataDir);
|
||||
} catch (Exception $e) {
|
||||
throw new BackupException("Can't remove data dir", 0, $e);
|
||||
}
|
||||
|
||||
$oEnvironment = new RunTimeEnvironment($sEnvironment);
|
||||
$oEnvironment->CompileFrom($sEnvironment);
|
||||
} finally {
|
||||
if (! $bReadonlyBefore) {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
} else {
|
||||
//we are in the scope of main process that needs to handle/keep readonly mode.
|
||||
$this->LogInfo("Keep readonly mode after restore");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_dir($sDataDir.'/production-modules'))
|
||||
finally
|
||||
{
|
||||
rename($sDataDir.'/production-modules', APPROOT.'data/production-modules/');
|
||||
IssueLog::Info('Backup Restore - LOCK released.');
|
||||
$oRestoreMutex->Unlock();
|
||||
}
|
||||
|
||||
$sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php';
|
||||
@chmod($sConfigFile, 0770); // Allow overwriting the file
|
||||
rename($sDataDir.'/config-itop.php', $sConfigFile);
|
||||
@chmod($sConfigFile, 0440); // Read-only
|
||||
|
||||
try
|
||||
{
|
||||
SetupUtils::rrmdir($sDataDir);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new BackupException("Can't remove data dir", 0, $e);
|
||||
}
|
||||
|
||||
$oEnvironment = new RunTimeEnvironment($sEnvironment);
|
||||
$oEnvironment->CompileFrom($sEnvironment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'bkp-status-backups-auto' => 'Scheduled backups',
|
||||
'bkp-status-backups-manual' => 'Manual backups',
|
||||
'bkp-status-backups-none' => 'No backup yet',
|
||||
'bkp-next-backup' => 'The next backup will occur on <b>%1$s</b> (%2$s) at %3$s',
|
||||
'bkp-next-backup' => 'The next backup will occur on <b>%1$s</b> (%2$s) at %3$s.',
|
||||
'bkp-next-backup-unknown' => 'The next backup is <b>not scheduled</b> yet.',
|
||||
'bkp-button-backup-now' => 'Backup now!',
|
||||
'bkp-button-restore-now' => 'Restore!',
|
||||
'bkp-confirm-backup' => 'Please confirm that you do request the backup to occur right now.',
|
||||
|
||||
203
datamodels/2.x/itop-backup/restore.php
Normal file
203
datamodels/2.x/itop-backup/restore.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
|
||||
if (!defined('APPROOT'))
|
||||
{
|
||||
if (file_exists(__DIR__.'/../../approot.inc.php'))
|
||||
{
|
||||
require_once __DIR__.'/../../approot.inc.php'; // When in env-xxxx folder
|
||||
}
|
||||
else
|
||||
{
|
||||
require_once __DIR__.'/../../../approot.inc.php'; // When in datamodels/x.x folder
|
||||
}
|
||||
}
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
require_once(APPROOT.'application/ajaxwebpage.class.inc.php');
|
||||
require_once(APPROOT.'core/log.class.inc.php');
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
require_once(dirname(__FILE__).'/dbrestore.class.inc.php');
|
||||
|
||||
class MyDBRestore extends DBRestore
|
||||
{
|
||||
/** @var Page used to send log */
|
||||
protected $oPage;
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
$this->oPage->p($sMsg);
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
$this->oPage->p('Error: '.$sMsg);
|
||||
ToolsLog::Error($sMsg);
|
||||
}
|
||||
|
||||
public function __construct($oPage)
|
||||
{
|
||||
$this->oPage = $oPage;
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a parameter (possibly empty) was specified when calling this page
|
||||
*/
|
||||
function CheckParam($sParamName)
|
||||
{
|
||||
global $argv;
|
||||
|
||||
if (isset($_REQUEST[$sParamName])) return true; // HTTP parameter either GET or POST
|
||||
if (!is_array($argv)) return false;
|
||||
foreach($argv as $sArg)
|
||||
{
|
||||
if ($sArg == '--'.$sParamName) return true; // Empty command line parameter, long unix style
|
||||
if ($sArg == $sParamName) return true; // Empty command line parameter, Windows style
|
||||
if ($sArg == '-'.$sParamName) return true; // Empty command line parameter, short unix style
|
||||
if (preg_match('/^--'.$sParamName.'=(.*)$/', $sArg, $aMatches)) return true; // Command parameter with a value
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function Usage($oP)
|
||||
{
|
||||
$oP->p('Restore an iTop from a backup file');
|
||||
$oP->p('Parameters:');
|
||||
if (utils::IsModeCLI())
|
||||
{
|
||||
$oP->p('auth_user: login, must be administrator');
|
||||
$oP->p('auth_pwd: ...');
|
||||
}
|
||||
$oP->p('backup_file [optional]: name of the file to store the backup into. Follows the PHP strftime format spec. The following placeholders are available: __HOST__, __DB__, __SUBNAME__');
|
||||
$oP->p('mysql_bindir [optional]: specify the path for mysql executable');
|
||||
|
||||
if (utils::IsModeCLI())
|
||||
{
|
||||
$oP->p('Example: php -q restore.php --auth_user=admin --auth_pwd=myPassw0rd --backup_file=/tmp/backup.zip');
|
||||
$oP->p('Known limitation: the current directory must be the directory of backup.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
$oP->p('Example: .../restore.php?backup_file=/tmp/backup.zip');
|
||||
}
|
||||
}
|
||||
|
||||
function ExitError($oP, $sMessage)
|
||||
{
|
||||
ToolsLog::Error($sMessage);
|
||||
$oP->p($sMessage);
|
||||
$oP->output();
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
function ReadMandatoryParam($oP, $sParam)
|
||||
{
|
||||
$sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, 'raw_data');
|
||||
if (is_null($sValue))
|
||||
{
|
||||
ExitError($oP, "ERROR: Missing argument '$sParam'");
|
||||
}
|
||||
return trim($sValue);
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////
|
||||
// Main program
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
if (utils::IsModeCLI())
|
||||
{
|
||||
$oP = new CLIPage("iTop - iTop Restore");
|
||||
|
||||
SetupUtils::CheckPhpAndExtensionsForCli($oP);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oP = new WebPage("iTop - iTop Restore");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
utils::UseParamFile();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
ExitError($oP, $e->GetMessage());
|
||||
}
|
||||
|
||||
if (utils::IsModeCLI())
|
||||
{
|
||||
$oP->p(date('Y-m-d H:i:s')." - running backup utility");
|
||||
$sAuthUser = ReadMandatoryParam($oP, 'auth_user');
|
||||
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd');
|
||||
$sBackupFile = ReadMandatoryParam($oP, 'backup_file');
|
||||
$bDownloadBackup = false;
|
||||
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd))
|
||||
{
|
||||
UserRights::Login($sAuthUser); // Login & set the user's language
|
||||
}
|
||||
else
|
||||
{
|
||||
ExitError($oP, "Access restricted or wrong credentials ('$sAuthUser')");
|
||||
}
|
||||
|
||||
if (!is_file($sBackupFile) && is_readable($sBackupFile)){
|
||||
ExitError($oP, "Cannot access backup file ('$sBackupFile')");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
require_once(APPROOT.'application/loginwebpage.class.inc.php');
|
||||
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
|
||||
}
|
||||
|
||||
if (!UserRights::IsAdministrator())
|
||||
{
|
||||
ExitError($oP, "Access restricted to administors");
|
||||
}
|
||||
|
||||
if (CheckParam('?') || CheckParam('h') || CheckParam('help'))
|
||||
{
|
||||
Usage($oP);
|
||||
$oP->output();
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// Interpret strftime specifications (like %Y) and database placeholders
|
||||
$oRestore = new MyDBRestore($oP);
|
||||
$oRestore->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
|
||||
|
||||
$res = false;
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
$oP->p("Sorry, iTop is in demonstration mode: the feature is disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
|
||||
$oRestore->RestoreFromCompressedBackup($sBackupFile, $sEnvironment);
|
||||
}
|
||||
|
||||
$oP->output();
|
||||
@@ -337,18 +337,29 @@ try {
|
||||
);
|
||||
}
|
||||
|
||||
// Do backup now
|
||||
// Next occurrence
|
||||
//
|
||||
$oBackupExec = new BackupExec();
|
||||
$oNext = $oBackupExec->GetNextOccurrence();
|
||||
$sNextOccurrence = Dict::Format('bkp-next-backup', $aWeekDayToString[$oNext->Format('N')], $oNext->Format('Y-m-d'),
|
||||
$oNext->Format('H:i'));
|
||||
/** @var \BackgroundTask $oTask */
|
||||
$oTask = MetaModel::GetObjectByName(BackgroundTask::class, BackupExec::class, false);
|
||||
if ($oTask)
|
||||
{
|
||||
$oTimezone = new DateTimeZone(MetaModel::GetConfig()->Get('timezone'));
|
||||
$oNext = new DateTime($oTask->Get('next_run_date'), $oTimezone);
|
||||
$sNextOccurrence = Dict::Format('bkp-next-backup', $aWeekDayToString[$oNext->Format('N')], $oNext->Format('Y-m-d'),
|
||||
$oNext->Format('H:i'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sNextOccurrence = Dict::S('bkp-next-backup-unknown');
|
||||
}
|
||||
$oFieldsetBackupNow->AddSubBlock(
|
||||
AlertUIBlockFactory::MakeForInformation('', $sNextOccurrence)
|
||||
->SetIsClosable(false)
|
||||
->SetIsCollapsible(false)
|
||||
);
|
||||
|
||||
// Do backup now
|
||||
//
|
||||
$oLaunchBackupButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('bkp-button-backup-now'));
|
||||
$oLaunchBackupButton->SetOnClickJsCode('LaunchBackupNow();');
|
||||
$oFieldsetBackupNow->AddSubBlock($oLaunchBackupButton);
|
||||
|
||||
@@ -23,6 +23,7 @@ class ConfigNodesVisitor extends NodeVisitorAbstract
|
||||
Node\Name::class,
|
||||
|
||||
Node\Const_::class,
|
||||
Node\Identifier::class,
|
||||
|
||||
Node\Expr\Array_::class,
|
||||
Node\Expr\ArrayDimFetch::class,
|
||||
@@ -44,6 +45,7 @@ class ConfigNodesVisitor extends NodeVisitorAbstract
|
||||
Node\Expr\PreDec::class,
|
||||
Node\Expr\PreInc::class,
|
||||
Node\Expr\Print_::class,
|
||||
Node\Stmt\Expression::class,
|
||||
Node\Expr\Ternary::class,
|
||||
Node\Expr\UnaryMinus::class,
|
||||
Node\Expr\UnaryPlus::class,
|
||||
|
||||
@@ -115,3 +115,5 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:ModuleInstallation/Attribute:version' => 'Version',
|
||||
'Class:ModuleInstallation/Attribute:comment' => 'Kommentar',
|
||||
));
|
||||
|
||||
|
||||
|
||||
@@ -83,3 +83,4 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'iTopHub:InstallationStatus:Version_NotInstalled' => 'Version %1$s <b>未</b> 安装.',
|
||||
));
|
||||
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Dieses Objekt schließen',
|
||||
'Portal:Form:Close:Warning' => 'Soll diese Eingabemaske verlassen werden? Eingegebene Daten werden nicht gespeichert.',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -70,6 +70,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Ben je zeker dat je dit venster wil sluiten? Ingevoerde gegevens kunnen verloren gaan.',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Você deseja abandonar esta página? Os dados digitados podem ser perdidos.',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -76,6 +76,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Вы действительно хотите закрыть эту форму? Введённые данные могут быть утеряны.',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -67,6 +67,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry~~',
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost~~',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.~~',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.~~',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -1630,6 +1630,21 @@ table .group-actions {
|
||||
.cke_toolbox_collapser, .cke_toolbox_collapser .cke_arrow {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
.cke_toolbox_collapser.cke_toolbox_collapser_min ~ .editor-fullscreen-button {
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 11px;
|
||||
border: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
/* !important so it overrides the .cke_reset_all style */
|
||||
background-position: center center !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 100% !important;
|
||||
background-image: url('../../../../../images/full-screen.png') !important;
|
||||
}
|
||||
.cke_toolbox_collapser.cke_toolbox_collapser_min ~ .editor-fullscreen-button:hover {
|
||||
background-color: #E5E5E5;
|
||||
}
|
||||
/* DataTables : Selection inputs */
|
||||
.dataTable.table th span.row_input, .dataTable.table td span.row_input {
|
||||
display: inline-block;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user